Skip to content

DUT API Integration — third pillar of TLSStress.Art telemetry

Read in your language: English · Português · Español

Scope status (post-Scope-Freeze 2026-05-10) — DUT API integration is now formally MÓDULO API INFRA.Art (MGMT-light plane). 7+ vendor profiles supported (Cisco FTD/SR/NX-OS, Fortinet, PAN-OS, Juniper, VyOS). Production-mode writes pass through RELAY.Art per ADR 0020. See modules/api-infra-art.md for the canonical MÓDULO reference.

This document describes how TLSStress.Art talks to the REST APIs of the lab's network elements — Cisco FTD (NGFW), Cisco Nexus 9000 (switch), Palo Alto, Fortinet — to enrich the Dashboard, Grafana, and the Test Run Report with data that only the API can provide.

It is the third pillar of telemetry in this product, joining SNMP (numerical metrics) and syslog (event correlation):

Source Strength Limitation
SNMP Standardized counters (interface bytes, CPU, memory) Generic; vendor-specific objects sparsely modeled; no config
Syslog Real-time events with full context Reactive only; no current state; no inventory
API (this doc) Live config, version, applied policies, deploy status, LLDP/CDP, HA state Vendor-specific; auth required; rate-limit awareness needed

The three layered together produce the level of forensic completeness the Test Run Report needs.

What the API integration unlocks

Capability Without API With API
Confirm NGFW model + serial number SNMP entPhysicalSerialNum (generic, sometimes wrong on virtual appliances) Cisco FTD /operational/sysinfo returns precise model + hardware revision
Confirm decrypt policy is enabled and which rules apply ❌ not visible from outside ✅ Cisco FTD /policy/sslpolicies lists every rule, action, applied interfaces
Confirm deploy status before starting a run ✅ Cisco FTD /operational/deploy — refuse to start if pending changes are queued
Confirm device's NTP config matches lab expectation ✅ Cisco FTD /devicesettings/default/ntp + Cisco Nexus sys/time
Auto-discover lab topology via LLDP/CDP ✅ Cisco Nexus DME sys/lldp/inst + sys/cdp/inst
Detect HA standby + skip writes ✅ Cisco FTD HA endpoint + 405 detection
Capture chassis inventory + module list partial ✅ Cisco Nexus sys/ch?rsp-subtree=full returns fans, PSUs, line cards with S/Ns
Annex with cryptographic chain-of-custody ✅ Each API response stored with SHA-256 in dut_api_snapshots

Architecture

                                    OOBI mgmt network
                                          │
   ┌─────────────────┐                   │
   │ Cisco FTD       │ ◄────HTTPS Bearer ┤
   │ (FDM API v6)    │                   │
   └─────────────────┘                   │
                                         │
   ┌─────────────────┐                   │
   │ Cisco Nexus 9k  │ ◄────APIC-cookie ─┤
   │ (NX-API DME)    │                   │
   └─────────────────┘                   │
                                         │  Server-side fetch
   ┌─────────────────┐                   │  inside Dashboard pod
   │ Cisco UCS CIMC  │ ◄────Basic Auth ──┤  (poll every N min OR
   │ (Redfish)       │                   │   on-demand at run start)
   └─────────────────┘                   │
                                         │
   ┌─────────────────┐                   │
   │ Fortinet        │ ◄────Bearer ──────┤
   │ (FortiOS REST)  │                   │
   └─────────────────┘                   ▼
                                     ┌──────────────────────┐
   ┌─────────────────┐               │ DUT API Collector    │
   │ Palo Alto       │ ◄────XML ────▶│ (Next.js worker)     │
   │ (PAN-OS) v6.x  │               │                      │
   └─────────────────┘               │ Adapter pattern:     │
                                     │ - cisco-ftd.ts       │
                                     │ - cisco-nexus.ts     │
                                     │ - cisco-ucs-cimc.ts  │
                                     │ - fortinet-fortigate.ts│
                                     │ - (future) palo-alto │
                                     └──────────┬───────────┘
                                                │
                          ┌─────────────────────▼────────────────────┐
                          │ Postgres                                  │
                          │  dut_api_devices    (creds AES-256-GCM)   │
                          │  dut_api_snapshots  (JSONB + SHA-256)     │
                          └─────────────────────┬────────────────────┘
                                                │
                ┌───────────────────────────────┼─────────────────────────┐
                ▼                               ▼                         ▼
     ┌──────────────────┐         ┌───────────────────────┐     ┌─────────────────┐
     │ Grafana          │         │ Test Run Report       │     │ TLS Decrypt     │
     │ "DUT Live State" │         │ Phase 3 Annex B (Nexus)│    │ Probe (cross-   │
     │ dashboard        │         │ Phase 3 Annex C (NGFW) │    │ verifies decryp │
     │                  │         │ + per-snapshot SHA-256│     │ policy)         │
     └──────────────────┘         └───────────────────────┘     └─────────────────┘

Vendor reference summary

Cisco FTD (FDM-managed) — cisco-ftd

Aspect Value
Base URL https://<ftd-mgmt>/api/fdm/v6/
Auth OAuth2 password grant → Bearer token
Token endpoint POST /api/fdm/v6/fdm/token
Token TTL typically 30 min
Refresh on 401 response
HA caveat Standby unit returns 405 on certain reads
Update method PUT only — full object required (no PATCH)
Enable on device nothing — FDM API is on by default

Endpoints we hit (read-only): - GET /operational/sysinfo → model, version, hostname, uptime - GET /devicesettings/default/ntp → configured NTP servers + state - GET /operational/interfaces → live interface state - GET /policy/sslpolicies → SSL/TLS decryption policy rules - GET /operational/deploy → pending/deploying/deployed - GET /policy/intrusionpolicies → applied IPS policy - GET /devices/default/ha → HA pair state

Reference: https://developer.cisco.com/docs/ftd-api-reference/latest/

Cisco Nexus 9000 (NX-API REST DME) — cisco-nexus

Aspect Value
Base URL https://<nexus-mgmt>/api/
Auth POST /api/aaaLogin.jsonSet-Cookie: APIC-cookie
Cookie TTL ~10 min (re-login on 401/403)
Object Model DME — Distinguished Names like sys, sys/ch, sys/intf, sys/lldp/inst
Response shape {"totalCount":"N","imdata":[{"className":{"attributes":{...}}}]}
Enable on device feature nxapi + nxapi https port 443
VRF binding nxapi use-vrf management (recommended)

Endpoints we hit (read-only): - GET /api/node/mo/sys/ch.json?rsp-subtree=full → chassis + modules + S/Ns - GET /api/node/mo/sys/time.json?rsp-subtree=full → NTP config + clock state - GET /api/node/mo/sys/intf.json?rsp-subtree=full → interface config - GET /api/node/mo/sys/lldp/inst.json?query-target=subtree&target-subtree-class=lldpAdjEp → LLDP neighbors - GET /api/node/mo/sys/cdp/inst.json?query-target=subtree&target-subtree-class=cdpAdjEp → CDP neighbors - GET /api/node/mo/sys/vpc.json?rsp-subtree=full → vPC peer state (HA-equivalent)

Reference: https://developer.cisco.com/docs/nx-os-n3k-n9k-api-ref/

Cisco UCS C-Series CIMC (Redfish) — cisco-ucs-cimc

Aspect Value
Base URL https://<cimc-mgmt>/redfish/v1/
Auth HTTP Basic (admin / password). No token TTL — credentials sent on every request
Enable on device Redfish is on by default in CIMC 4.0+; verify under Admin → Communication Services
HA caveat None (single-controller CIMC)
Update method PATCH on /redfish/v1/Systems/{id} (not used; we are read-only)

Endpoints we hit (read-only): - GET /redfish/v1/Systems/<id> → server model, BIOS version, serial, asset tag - GET /redfish/v1/Chassis/<id> → chassis power state, indicators, sku - GET /redfish/v1/Chassis/<id>/Power → PSU status, total wattage, power supplies - GET /redfish/v1/Chassis/<id>/Thermal → fans, inlet/outlet temps, threshold breaches - GET /redfish/v1/Managers/<id> → CIMC firmware version, service health - GET /redfish/v1/Systems/<id>/Memory → DIMM inventory + health (per-slot) - GET /redfish/v1/Systems/<id>/Processors → CPU models + core counts + health

Reference: https://www.dmtf.org/standards/redfish (DMTF Redfish standard, Cisco CIMC implementation).

Fortinet FortiGate (FortiOS REST v2) — fortinet-fortigate

Aspect Value
Base URL https://<fgt-mgmt>/api/v2/
Auth API token (Bearer). Token is generated per-admin in System → Administrators → REST API Admin. No TTL — token is long-lived
Token rotation Manual (operator regenerates token in GUI; then update in our admin UI)
HA caveat Active unit serves the API; standby returns redirect — adapter follows redirect transparently
Update method PUT to /cmdb/<path>, but we are read-only

Endpoints we hit (read-only): - GET /api/v2/monitor/system/status → hostname, version, serial, uptime - GET /api/v2/monitor/system/interface → interface state and counters - GET /api/v2/cmdb/system/ntp → configured NTP servers - GET /api/v2/cmdb/firewall/ssl-ssh-profile → SSL inspection profiles - GET /api/v2/cmdb/firewall/policy → firewall policy table (decryption-relevant rules) - GET /api/v2/monitor/system/ha-status → HA cluster state, primary/secondary

Reference: https://docs.fortinet.com/document/forticonverter/fortios/restapi/

Palo Alto (PAN-OS XML API) — planned v6.x

Backlogged. No adapter scaffold yet. When implemented, auth will be API key generation via /api/?type=keygen&user=...&password=.... Read endpoints follow ?type=op&cmd=<show...> pattern.

Data model

dut_api_devices (one row per registered device)

Column Purpose
hostname Lab-unique identifier
vendor cisco-ftd / cisco-nexus / cisco-ucs-cimc / fortinet-fortigate / palo-alto-panos (planned)
device_role ngfw / switch / future
base_url https://10.0.0.1
username API account
password_enc AES-256-GCM ciphertext (BYTEA) — see Encryption
tls_verify_mode strict (default) / self-signed (pin first cert) / insecure
pinned_cert_sha256 Set on first connect when tls_verify_mode=self-signed
poll_interval_seconds Default 300 (5 min); minimum 30
enabled Toggle without deleting the row
last_poll_at / last_poll_status / last_poll_error Live status surfaced in admin UI
notes Free-form operator notes

dut_api_snapshots (append-only history)

Every successful (or even failed) API call writes one row. Each row is the full JSON payload with SHA-256 of the canonical form for forensic chain-of-custody.

Column Purpose
device_id / device_hostname / vendor Denormalized for fast queries
endpoint_path Vendor-specific path (e.g. /api/fdm/v6/operational/sysinfo)
endpoint_label Vendor-agnostic label — system_info, ntp_config, lldp_neighbors, etc.
http_status API response status
payload_json Full vendor JSON (JSONB column)
payload_sha256 hex digest, cited in Test Run Report annexes
collector_version TLSStress.Art version that pulled the snapshot
test_run_execution_id NULL for background polls; set when the snapshot was tied to a specific run

Vendor-agnostic endpoint_label values: system_info, ntp_config, interfaces, lldp_neighbors, cdp_neighbors, decrypt_policy, deploy_status, ips_policy, qos_state, mac_table, routing_table, ha_status, license_state, config_running.

This lets the Dashboard write queries like "give me the most recent NTP config from any registered NGFW, regardless of vendor" without per-vendor branching.

Encryption of credentials

DUT credentials are stored AES-256-GCM-encrypted in the Postgres BYTEA column. The encryption key is the env var DUT_CRED_ENC_KEY, provisioned via a standard K8s Secret.

Key requirements: - 32 bytes - Provided as 64 hex chars OR base64 encoding - Generate once with node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" - Store in K8s Secret, mount as env var on the dashboard pod

Encryption format: IV (12 bytes) || ciphertext || auth tag (16 bytes) concatenated as one BYTEA value. The decryptPassword() helper splits and verifies the GCM tag — corruption or wrong key throws.

Key rotation: re-encrypt every row under the new key. Migration helper for this is planned.

Operator workflow

Enable APIs on the devices

Cisco FTD — already on by default. Confirm with:

show api-server-info

Cisco Nexus 9000:

configure terminal
feature nxapi
nxapi https port 443
nxapi use-vrf management
end
write memory
show nxapi

Set up the encryption key (one-time)

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

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

# Reference in the dashboard Deployment env block:
#   env:
#     - name: DUT_CRED_ENC_KEY
#       valueFrom:
#         secretKeyRef:
#           name: tlsstress-dut-cred
#           key: DUT_CRED_ENC_KEY

Register a device

(Follow-up PR ships the admin UI. For now, devices are registered via the API endpoint or directly in the database.)

curl -X POST "https://dashboard/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": "<plaintext — will be encrypted server-side>",
    "tlsVerifyMode": "self-signed",
    "pollIntervalSeconds": 300,
    "notes": "Cisco FTD 7.4 on FPR1010 — primary in HA pair"
  }'

Test the connection

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

Trigger a snapshot manually

curl -X POST "https://dashboard/api/admin/dut/devices/<id>/snapshot" \
  -H "Authorization: Bearer $ADMIN_TOKEN"
# returns the IDs of the snapshot rows just written

Background polling

Once enabled, the dashboard's polling worker iterates over registered devices at their configured poll_interval_seconds, runs all supported collect* methods, and writes snapshots. Polling worker implementation lands in PR-B (follow-up).

Tying snapshots to a run

When a Test Plan run starts, the engine triggers an immediate snapshot of every registered device with test_run_execution_id set. This snapshot becomes the run's "DUT state at start". On run end, another snapshot establishes "DUT state at end". Both are referenced in the Test Run Report.

Security model

What the integration DOES

  • Stores DUT credentials encrypted at rest (AES-256-GCM)
  • Uses HTTPS for every device call
  • Supports cert-pinning for self-signed devices
  • Logs every snapshot in an append-only table with SHA-256
  • Audit-logs every device registration / credential change

What the integration does NOT do

  • Does NOT write to the device by default — read-only adapter methods only. Future write methods (e.g. set NTP server via API) will be explicit operator-confirmed flows with separate audit
  • Does NOT cache plaintext credentials beyond the duration of one API call
  • Does NOT support OAuth client_credentials flow on FTD (only password grant) — if your security policy requires client_credentials, file an issue

Threat model

Threat Mitigation
Database leak exposes credentials AES-256-GCM with key in K8s Secret — attacker must also obtain DUT_CRED_ENC_KEY
MITM on adapter↔device path TLS verification (strict default); insecure mode requires explicit operator opt-in and is recorded in tls_verify_mode
Compromised dashboard pod abuses API access Adapter is read-only by default; future write methods will require fresh admin re-auth
Replay of old API responses payload_sha256 + collected_at timestamp; mismatch detectable by anyone who has both the snapshot and a contemporaneous device fetch
Operator typo registers wrong device "Test connection" button validates auth + reachability before saving

Performance + rate-limit considerations

Default poll interval is 5 minutes per device. Empirical observation:

  • Cisco FTD: collecting all 7 read endpoints takes ~3-8 seconds total (token cached, sequential calls)
  • Cisco Nexus 9000: collecting all 6 endpoints takes ~2-5 seconds (cookie cached)
  • Neither vendor has documented rate-limit numbers; both seem to handle 1 call/second comfortably for reads

If you are running multiple devices, the worker poll loop is sequential per device (not parallel) to stay polite. Total cycle time = sum of per-device durations.

Future work — write operations

Once the read foundation is stable, write methods become possible:

  • Set NTP server on Cisco FTD: PUT /api/fdm/v6/devicesettings/default/ntp/{objId} (full object) + POST /api/fdm/v6/operational/deploy to commit
  • Reset interface counters on Cisco Nexus: POST MO with clear action
  • Toggle decrypt policy state for A/B testing during a run
  • Apply test-bed-specific QoS policy on Nexus pre-run, revert post-run

Each write will require: 1. Admin re-auth at request time 2. Explicit operator confirmation in the UI ("about to change NGFW NTP source — proceed?") 3. Audit log entry with before/after snapshots 4. Automatic rollback if the post-write read confirms divergence from intent

Out of scope for this PR; tracked as PR-C in the API integration roadmap.

What ships in this PR

This PR is the foundation: - Database tables + Drizzle schema - AES-256-GCM credential encryption helpers - Adapter interface + ENDPOINT_LABELS catalog - Functional Cisco FTD adapter with 7 read endpoints - Functional Cisco Nexus adapter with 6 read endpoints (covers LLDP/CDP discovery) - This documentation

What ships in PR-B (follow-up): - Admin UI for registering devices - Polling worker (Kubernetes CronJob OR Next.js server interval) - API endpoints /api/admin/dut/devices/* + /api/admin/dut/snapshots/* - Grafana dashboard "TLSStress.Art — DUT Live State" - Integration into Test Plan execution (snapshot at run start + end)

What ships in PR-C (future): - Palo Alto + Fortinet adapters - Test Run Report Phase 3 Annex B/C wired to these snapshots - Write operations (NTP set, decrypt-policy toggle) with operator confirmation flow

References