Skip to content

Fallbacks de sincronización de tiempo — cuando el lab no puede alcanzar una fuente NTP pública

Lee en tu idioma: English · Português · Español

Estado del alcance (post-congelación de alcance 2026-05-10) — Ver ARCHITECTURE.md para los 37 MÓDULOs canónicos + 7 Test Kinds + arquitectura de safety DOM/CPOS/PIE-PA. ADRs 0014, 0019-0025 cubren adiciones post-Freeze. Este documento complementa TIME_SYNC.es.md. Lee primero el documento principal — eso es lo que todo operador necesita. Este archivo cubre los casos más difíciles:

  • El lab está air-gapped a nivel UCS / Nexus / NGFW
  • Pero UN elemento (típicamente el Cloner) tiene un camino hacia internet pública mediante un enlace ISP o proxy
  • O ningún elemento tiene internet, y el operador quiere usar el laptop que accede al Dashboard como fuente de tiempo

Tres opciones, ordenadas por confiabilidad

# Opción ¿Confiable? Cuándo usar
1 Appliance stratum-1 GPS-disciplined + lab stratum-2 ✅ forensic-grade Instalaciones clasificadas, engagements de larga duración, cumplimiento regulatorio
2 Relay NTP TLSStress.Art corriendo en un nodo con internet (p. ej. host del Cloner) ✅ bueno para la mayoría de labs Air-gap a nivel UCS, pero al menos un nodo puede alcanzar NTP público vía un enlace ISP
3 Fallback de reloj del navegador (laptop del operador) 🟡 NOT forensic Último recurso. Solo timing relativo a la ejecución; no cites los timestamps resultantes en informes

La Opción 1 es el estándar de oro y está documentada en TIME_SYNC.es.md. Este archivo se enfoca en las Opciones 2 y 3.


Opción 2 — Relay NTP TLSStress.Art

Un pequeño deployment Kubernetes que ejecuta chronyd en un nodo elegido, sincroniza con NTP público mediante el camino de internet de ese nodo y sirve tiempo en UDP/123 a todos los demás elementos del lab (hosts UCS, Nexus 9000, NGFW DUT) sobre la red de gestión.

Cuándo encaja

  • El Cloner está en un nodo con camino ISP (porque el Cloner necesita clonar sitios públicos reales)
  • Los demás hosts UCS, Nexus y NGFW están todos aislados de internet pública
  • Quieres que hereden un reloj razonable sin comprar hardware GPS

Arquitectura

Public NTP servers
(time.cloudflare.com,
 time.google.com,
 pool.ntp.org)
        │
        ▼
[ Node X — has ISP path ]
   chronyd (relay)
   Pod hostNetwork=true
   Listens on UDP/123 of Node X's host IP
        │
        ▼
   ┌────────────────────────────┐
   │  Lab management network    │
   │  192.168.90.0/24           │
   └────────────────────────────┘
        │              │            │
        ▼              ▼            ▼
   UCS host(s)    Nexus 9000   NGFW DUT
   (chrony)       (NTP client)  (NTP client)

El nodo del relay es el "ancla stratum 2" del lab. Todos los demás elementos apuntan a él.

Configuración

Paso 1 — elige y etiqueta el nodo del relay.

# The node must already have a route to public NTP via its primary interface.
# Verify before applying:
ssh <relay-node>
chronyc -h time.cloudflare.com sources    # if chrony is preinstalled
# or just:
nc -uv time.cloudflare.com 123 < /dev/null
# expect "succeeded" / "open"

# Then label the node:
kubectl label node <relay-node-name> tlsstress.art/ntp-relay=true

Paso 2 — aplica el manifiesto del relay.

kubectl apply -f k8s/optional/ntp-relay.yaml

Esto crea: - Un namespace tlsstress-ntp-relay (PSA: privileged — requerido para la capability SYS_TIME) - Un ConfigMap con chrony.conf - Un Deployment que corre en el nodo etiquetado con hostNetwork: true y expone UDP/123 en la IP primaria del host - Un Service headless para discovery dentro del cluster

Paso 3 — apunta los elementos del lab al relay.

En cada uno de los demás hosts UCS (/etc/chrony/chrony.conf):

server <relay-node-host-ip> iburst prefer minpoll 4 maxpoll 6
makestep 1.0 3
rtcsync
sudo systemctl restart chronyd
sudo chronyc waitsync 30 0.05

En el Cisco Nexus 9000:

configure terminal
ntp server <relay-node-host-ip> prefer
ntp source-interface mgmt0
end
write memory
show ntp peer-status

En el NGFW DUT — específico del fabricante. Para Cisco FTD/ASA:

configure terminal
ntp server <relay-node-host-ip>
write memory
show ntp associations

Para Palo Alto:

set deviceconfig system ntp-servers primary-ntp-server-address <relay-node-host-ip>
commit

Para Fortinet:

config system ntp
  set ntpsync enable
  set type custom
  config ntpserver
    edit 1
      set server <relay-node-host-ip>
    end
  end
end

Paso 4 — verifica.

# In Grafana, open: TLSStress.Art — Time Sync Status
# Confirm all lab hosts show green (drift < 100 ms)

# From any UCS:
./scripts/check-time-sync.sh --strict
# expect exit 0

Trade-offs

Pros: - Sin hardware adicional - Reusa un nodo ya conectado a internet (el host del Cloner) — sin superficie de ataque extra - Recuperación automática: si el nodo del relay reinicia, resincroniza y el lab sigue - Defendible forensemente — la fuente NTP de cada host queda registrada, el audit log muestra la cadena

⚠️ Contras: - Single point of failure — si el nodo del relay pierde internet, los relojes del lab van lentamente a drift (chrony mantiene el offset previo por horas, no días) - Requiere claves de autenticación NTP para confianza production-grade (sin claves, cualquiera en la red de gestión puede responder consultas NTP; es un ataque de bajo esfuerzo) - El nodo del relay ahora tiene un DaemonSet privilegiado (capability SYS_TIME) — riesgo ligeramente elevado

Hardening — añade autenticación NTP (opcional pero recomendado):

Edita el ConfigMap chrony.conf para añadir una clave compartida que los clientes del lab también usen:

keyfile /etc/chrony/keys
authselectmode require

Luego monta el archivo de clave vía un Secret. Documentación sobre esto está en docs/TIME_SYNC.es.md#authentication (mejora futura).


Opción 3 — Fallback de reloj del navegador (NOT forensic)

⚠️ Lee con atención. Esta opción está documentada porque los operadores la piden, pero acarrea reservas serias. El Dashboard no se entrega con esto habilitado por defecto.

Qué haría

El operador abre el Dashboard desde su laptop. El Dashboard lee Date.now() del navegador, lo postea a un endpoint backend privilegiado, que entonces ejecuta un DaemonSet privilegiado en todos los nodos para ajustar (step) sus relojes al tiempo del navegador. Esto hace que todo el lab quede "sincronizado con el laptop del operador".

Por qué esto es riesgoso

Riesgo Explicación
Los relojes del navegador son poco confiables El laptop del operador puede estar sincronizado vía NTP, o puede estar desfasado por minutos (proxy corporativo interceptando NTP, VM con reloj virtual pausado, hotspot móvil, hardware con jet lag). El Dashboard no puede saberlo.
Operadores distintos fijan tiempos distintos Si dos ingenieros acceden al lab en días distintos desde laptops distintos, el reloj del lab camina. La comparación cross-engagement se rompe silenciosamente.
Superficie de ataque privilegiada Un endpoint POST que fija el tiempo del sistema en cada nodo es un blanco de alto valor. Sesión de navegador comprometida = tiempo comprometido en todos los hosts.
Sin valor forense Los timestamps escritos bajo este esquema no pueden citarse en procedimientos legales, registros regulatorios o comparaciones de benchmark de fabricantes. Son "tiempo relativo solamente".
Esconde el problema real Si el lab no puede alcanzar NTP, el operador debería arreglar esa causa raíz (Opción 2 o GPS), no encubrirla.

Cuándo es aceptable

Solo cuando TODO lo siguiente sea verdadero: 1. Ningún nodo del lab tiene camino alguno hacia una fuente NTP pública o privada 2. El engagement es estrictamente interno — no de cara al cliente, no para ningún informe que vaya a salir de la sala 3. El operador acepta explícitamente la reserva "non-forensic" y firma el audit log 4. Los resultados de esta ejecución se marcarán en la base de datos como time_source = browser_fallback y se filtrarán de las consultas de comparación cross-engagement

Lo que construimos (y lo que NO construimos)

Construimos una versión con guarda del lado de petición de este flujo:

  • El Dashboard expone POST /api/time-sync/set-from-browser (solo admin). Acepta { browserTimestampMs, browserTimezone, browserOffsetMinutes, acknowledgement: "NOT_FORENSIC" } y rechaza si el tiempo declarado por el navegador está más de ±24 h fuera del tiempo actual estimado por el servidor.
  • NO ejecuta automáticamente un cambio de reloj. En su lugar, escribe una línea de auditoría (con forensic_grade: false) y devuelve el comando exacto kubectl exec ... chronyc settime para que el operador lo ejecute en el control plane del cluster.
  • La admin UI en /admin/time-sync arma la petición, exige un checkbox explícito "Reconozco que esto NO es forensic" y muestra el comando manual. Ver dashboard/src/app/api/time-sync/set-from-browser/route.ts.

NO construimos (y no vamos a construir) el camino de auto-ejecución:

  • El costo de implementación es alto (DaemonSet privilegiado + enforcement de audit log en el paso de aplicar)
  • El riesgo operativo es real (foot-gun para ingenieros con prisa)
  • La demanda real es baja (¿cuándo tendrías un laptop con acceso al Dashboard pero ni siquiera un nodo con internet para el relay? Casi nunca en la práctica)
  • El camino actual (solo petición) es suficiente: el operador copia-pega un comando kubectl y es dueño del apply

Si tienes un caso de uso fuerte para el camino automatizado completo, abre un issue usando la plantilla de access-request. Lo evaluaremos caso por caso y podríamos construirlo como módulo opt-in con marcadores "non-forensic" hard-coded en las ejecuciones resultantes.

Lo que puedes hacer hoy en lugar de esto

  • Usa el flujo de la admin UI arriba — abre /admin/time-sync, acepta el caveat NOT_FORENSIC, copia el comando kubectl chronyc settime retornado, ejecútalo en el control plane. La línea de auditoría se escribe automáticamente con forensic_grade: false y time_source = browser_fallback.
  • Fija el tiempo manualmente en cada host al instalar por primera vez, usando tu laptop como referencia pero documentándolo:

    # On the laptop:
    date -u  # note the UTC time
    
    # On each UCS:
    sudo timedatectl set-time '2026-05-06 14:35:00 UTC'  # within ~1s of the laptop's note
    
    # Then:
    sudo systemctl stop chronyd  # disable NTP attempts that will fail
    
    Esto es honesto sobre ser non-forensic — el operador registra el ajuste manual en las notas del engagement.

  • Usa la Opción 2 con un teléfono USB-tetherizado como fuente de internet del nodo del relay si incluso el ruteo ISP está prohibido. Hotspot de teléfono a un nodo = un camino = suficiente.


Cómo el panel Grafana muestra esto

El dashboard TLSStress.Art — Time Sync Status (cargado automáticamente cuando se aplica la kustomization) muestra:

  1. Stat panel — clock skew por host: verde < 100 ms · amarillo < 1 s · naranja < 5 s · rojo > 5 s
  2. Stat panel — sync status por host: ✓ synced / ✗ NOT synced
  3. Stat panel — divergencia multi-host: max(offset) − min(offset) a través del cluster
  4. Time-series — drift en el tiempo por host: captura tendencias sostenidas o picos correlacionados con ejecuciones
  5. Stat panel — minutos desde la última actualización de sync por host: verde < 5 min · rojo > 30 min

Abre este dashboard antes de iniciar cualquier plan de pruebas. Si algo no está verde, arregla primero el time-sync — no ejecutes.

Relacionados

  • TIME_SYNC.es.md — lo básico que todo operador necesita saber
  • AIRGAP_INSTALL.es.md — instalación air-gap, que es el escenario padre de la Opción 2
  • MONITORING_TEST_VALIDITY.es.md — el framework más amplio de validity-alerts en el que los alertas de time-sync se enchufan
  • USAGE_POLICY.es.md — las restricciones de licencia se aplican igualmente a las ejecuciones hechas bajo cualquier opción de time-sync