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.mdfor 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.json → Set-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/deployto commit - Reset interface counters on Cisco Nexus:
POSTMO withclearaction - 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
Related¶
SYSLOG_OPERATIONS.md— second pillar (events)MONITORING_TEST_VALIDITY.md— first pillar (metrics) frameworkTLS_DECRYPT_MODE_VERIFICATION.en.md— independent decrypt-active probe; with API the cross-check becomes "probe says decrypt active AND/policy/sslpoliciesshows enabled rules"TIME_SYNC.md— without accurate clocks the snapshot timestamps are misleadingUSAGE_POLICY.md— license restrictions apply to API data collected here
References¶
- Cisco FTD REST API reference — https://developer.cisco.com/docs/ftd-api-reference/latest/
- Cisco FTD API intro — https://www.cisco.com/c/en/us/td/docs/security/firepower/ftd-api/guide/ftd-rest-api/ftd-rest-api-intro.html
- Cisco Nexus 9000 NX-API SDK — https://developer.cisco.com/docs/nx-os-n3k-n9k-api-ref/
- Cisco Nexus 9000 Programmability Guide — https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/93x/progammability/guide/b-cisco-nexus-9000-series-nx-os-programmability-guide-93x/