Skip to content

Operaciones de la API del DUT — registrando dispositivos y el worker de polling

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. Esta es la guía orientada al operador para la integración con la API del DUT. Lee primero DUT_API_INTEGRATION.es.md para entender la arquitectura; lee API_FEATURE_CATALOG.es.md para saber qué funcionalidades desbloquea la integración con la API. Este documento explica cómo USARLA realmente: cómo registrar un dispositivo, cómo se comporta el worker de polling, cómo disparar un snapshot manual y cómo depurar.

Worker de polling — qué hace

Una vez que un dispositivo está registrado, un worker de polling dentro del pod del dashboard lo consulta automáticamente según una cadencia. El worker:

  1. Se ejecuta cada 30 segundos (intervalo de barrido — no significa que cada dispositivo sea consultado cada 30s)
  2. Selecciona dispositivos donde last_poll_at + poll_interval_seconds < now() O last_poll_at IS NULL
  3. Para cada dispositivo elegible:
  4. Descifra la contraseña usando DUT_CRED_ENC_KEY
  5. Instancia el adapter apropiado (Cisco FTD, Cisco Nexus, Cisco UCS, Fortinet)
  6. Llama al método collectAll*() del adapter
  7. Persiste cada snapshot en dut_api_snapshots con SHA-256
  8. Actualiza los campos last_poll_at, last_poll_status, last_poll_error de la fila del dispositivo
  9. Seguro con múltiples réplicas mediante advisory lock de Postgres: si dos pods del dashboard están corriendo, solo uno hace polling a la vez

Deshabilitando el worker

# Set the env var in the dashboard's K8s Deployment:
DUT_API_POLLER_DISABLE=1

El worker registra un aviso de deshabilitación único en el arranque y retorna. Los snapshots manuales vía endpoint de la API siguen funcionando.

Intervalo de polling por defecto

poll_interval_seconds tiene valor por defecto de 300 (5 min) al momento del registro. Se permite override por dispositivo (mínimo 30 s). Valores recomendados:

Caso de uso Intervalo
Refresh de inventario en background 300 s (por defecto)
Engagement activo, dashboard "DUT Live State" 60 s
Ejecuciones de baseline forense (SOAK largo) 30 s
Momento de pre-flight check (one-shot, no periódico) usa el endpoint POST /snapshot en su lugar

Setup único — clave de cifrado

Las credenciales se almacenan cifradas con AES-256-GCM usando DUT_CRED_ENC_KEY (env var). Antes de registrar el primer dispositivo, aprovisiona la clave:

# 1. Generate the key
KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")

# 2. Provision via K8s Secret
kubectl create secret generic tlsstress-dut-cred \
  --from-literal=DUT_CRED_ENC_KEY="$KEY" \
  --namespace web-agents

# 3. Wire to the dashboard Deployment env
kubectl patch deployment dashboard --namespace web-agents \
  --patch='{"spec":{"template":{"spec":{"containers":[{"name":"dashboard","env":[{"name":"DUT_CRED_ENC_KEY","valueFrom":{"secretKeyRef":{"name":"tlsstress-dut-cred","key":"DUT_CRED_ENC_KEY"}}}]}]}}}}'

# 4. Restart the dashboard to pick up the env var
kubectl rollout restart deployment dashboard --namespace web-agents

API — registrando dispositivos

Todos los endpoints requieren autenticación de admin (la misma autenticación que usa el resto de la UI de admin).

Listar vendors + dispositivos

GET /api/admin/dut/devices

Respuesta:

{
  "devices": [
    { "id": "...", "hostname": "ftd-1.lab", "vendor": "cisco-ftd", "lastPollStatus": "ok", ... }
  ],
  "vendors": [
    { "key": "cisco-ftd",          "displayName": "Cisco FTD (FDM API)",          "available": true },
    { "key": "cisco-nexus",        "displayName": "Cisco Nexus 9000 (NX-API DME)", "available": true },
    { "key": "cisco-ucs-cimc",     "displayName": "Cisco UCS C-Series CIMC",       "available": true },
    { "key": "fortinet-fortigate", "displayName": "Fortinet FortiGate (REST v2)",  "available": true },
    { "key": "palo-alto-panos",    "displayName": "Palo Alto (not yet implemented)","available": false }
  ]
}

Registrar un Cisco FTD

curl -X POST "https://dashboard.example/api/admin/dut/devices" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "hostname": "ftd-1.lab.example.com",
    "vendor": "cisco-ftd",
    "deviceRole": "ngfw",
    "baseUrl": "https://10.10.10.1",
    "username": "admin",
    "password": "<the-ftd-password>",
    "tlsVerifyMode": "self-signed",
    "pollIntervalSeconds": 300,
    "notes": "Cisco FTD 7.4 on FPR1010 — primary in HA pair"
  }'
# {"ok": true, "deviceId": "..."}

Registrar un Fortinet FortiGate (con API token)

curl -X POST "https://dashboard.example/api/admin/dut/devices" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
    "hostname": "fortigate-1.lab.example.com",
    "vendor": "fortinet-fortigate",
    "deviceRole": "ngfw",
    "baseUrl": "https://10.10.10.5",
    "username": "apitoken",
    "password": "<the-api-token>",
    "tlsVerifyMode": "strict",
    "pollIntervalSeconds": 300
  }'

Nota: definir username con el literal apitoken le indica al adapter que use autenticación por API-token (Bearer); de lo contrario, recae en autenticación de sesión basada en cookie.

Registrar un Cisco Nexus 9000

# Pre-requisite on the switch:
#   configure terminal
#   feature nxapi
#   nxapi https port 443
#   nxapi use-vrf management
#   end

curl -X POST "https://dashboard.example/api/admin/dut/devices" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
    "hostname": "nexus-1.lab.example.com",
    "vendor": "cisco-nexus",
    "deviceRole": "switch",
    "baseUrl": "https://10.10.10.10",
    "username": "admin",
    "password": "<password>",
    "tlsVerifyMode": "self-signed",
    "pollIntervalSeconds": 300
  }'

Probar la conexión

curl -X POST "https://dashboard.example/api/admin/dut/devices/<id>/test" \
  -H "Authorization: Bearer $ADMIN_TOKEN"
# {"ok": true, "detail": "auth OK; CPU monitor reachable in 142ms", "latencyMs": 142}

Si ok: false, el campo detail indica qué falló (auth, TLS, timeout, error HTTP).

Disparar un snapshot manual

Para inspección ad-hoc o justo antes de una ejecución crítica:

curl -X POST "https://dashboard.example/api/admin/dut/devices/<id>/snapshot" \
  -H "Authorization: Bearer $ADMIN_TOKEN"
# {
#   "ok": true,
#   "lastPollStatus": "ok",
#   "lastPollError": "collected 7 snapshot(s)",
#   "lastPollAt": "2026-05-06T14:35:00.000Z"
# }

Esto llama a pollOneDevice() de forma síncrona; la respuesta confirma cuántos snapshots fueron recolectados.

# Latest 100 snapshots across all devices
curl "https://dashboard.example/api/admin/dut/snapshots" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

# Filter by device + endpoint label
curl "https://dashboard.example/api/admin/dut/snapshots?deviceId=...&endpointLabel=decrypt_policy" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

# Snapshots for a specific test run
curl "https://dashboard.example/api/admin/dut/snapshots?testRunExecutionId=<uuid>" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

La respuesta incluye payloadSha256 para cada uno — el hash ancla los anexos del Test Run Report.

Actualizar un dispositivo

# Disable polling temporarily
curl -X PATCH "https://dashboard.example/api/admin/dut/devices/<id>" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"enabled": false}'

# Rotate the password
curl -X PATCH "https://dashboard.example/api/admin/dut/devices/<id>" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"password": "<new-password>"}'

# Change polling cadence
curl -X PATCH "https://dashboard.example/api/admin/dut/devices/<id>" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"pollIntervalSeconds": 60}'

Eliminar un dispositivo

curl -X DELETE "https://dashboard.example/api/admin/dut/devices/<id>" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

El cascade también elimina todos los snapshots históricos del dispositivo (FK ON DELETE CASCADE en la migration).

Debugging

El poller no está tomando nuevos dispositivos

  • Revisa kubectl logs deployment/dashboard | grep '\[dut-api/poller\]' buscando la línea de startup
  • Verifica que DUT_API_POLLER_DISABLE no esté seteado en 1
  • Verifica que DUT_CRED_ENC_KEY esté aprovisionada (ver el setup único arriba)

El dispositivo muestra last_poll_status: auth_failed

  • Prueba la conexión manualmente con el endpoint /test
  • Verifica que el usuario de API del dispositivo tenga los permisos correctos (FTD: API user role; Nexus: NX-API habilitado en el profile del usuario; Fortinet: API admin profile)
  • Revisa los logs del lado del dispositivo (p.ej. FTD /var/log/audit.log)

El dispositivo muestra last_poll_status: tls_error

  • Define tlsVerifyMode: 'self-signed' para dispositivos con certificados self-signed (el strict por defecto los rechaza)
  • Para self-signed, el SHA-256 del cert queda fijado (pinned) en la primera conexión; si el cert rota, retornan errores de TLS — el operador debe limpiar pinned_cert_sha256 para aceptar el nuevo cert

Los snapshots dejan de llegar pero no hay error logueado

  • Revisa contención del advisory lock de Postgres: si dos pods del dashboard están compitiendo, solo uno hace polling. Confirma replicas=1 O acepta que los snapshots lleguen al doble del intervalo configurado (cada pod toma el lock la mitad del tiempo).
  • Revisa el espacio en disco del volumen de Postgres — dut_api_snapshots crece en modo append-only.

Modelo de seguridad

  • Credenciales en reposo: AES-256-GCM en BYTEA de Postgres. La clave es la env var DUT_CRED_ENC_KEY (32 bytes), almacenada en K8s Secret.
  • Credenciales en tránsito (dashboard → dispositivo): HTTPS. tlsVerifyMode es por dispositivo (strict / self-signed / insecure).
  • Credenciales en memoria: solo durante un ciclo de polling; limpiadas después de disconnect().
  • Los endpoints de la API requieren autenticación de admin (la misma que el resto de la UI de admin).
  • Los endpoints Test Connection / Manual Snapshot son acciones de write — la auth de admin se re-verifica en cada llamada.

Performance

La cadencia por defecto (1 dispositivo cada 5 min) genera aproximadamente:

  • 7 snapshots por FTD por poll → 84 snapshots/hora/dispositivo
  • 6 snapshots por Nexus por poll → 72 snapshots/hora/dispositivo
  • 8 snapshots por UCS por poll → 96 snapshots/hora/dispositivo

Para un lab típico de 4 dispositivos (1 NGFW + 1 switch + 2 UCS), espera ~350 snapshots/hora, ~8400/día, ~250k/mes. A ~250 bytes por snapshot (con payload), esto da ~63 MB/mes — trivial para Postgres.

Para una cadencia agresiva de 30 s en el mismo lab: ~3.5k snapshots/hora, ~84k/día, ~2.5M/mes, ~625 MB/mes — todavía está bien, pero vale una política de retention para dut_api_snapshots.

Retention

Hoy no hay retention automático. Operadores que quieran podar snapshots antiguos:

DELETE FROM dut_api_snapshots
WHERE collected_at < now() - interval '30 days';

Ejecuta esto como un CronJob de Postgres si tu lab genera alto volumen.

Futuro: un campo configurable dut_api_snapshot_retention_days por dispositivo + prune automático (alcance del PR-D).

Relacionados