Dynamic PLC testing without reaching the customer's device: when a PLC/SPS
target ships control logic but no reachable live URL, instantiate that logic
ourselves on a throwaway OpenPLC container in-cluster, load + start it, probe
the provisioned Modbus endpoint, and tear it down. No customer network access,
sandboxed, and reproducible.
This is the phase-1 foundation of epic #183 (OpenPLC substrate). It covers:
- provision: ephemeral container lifecycle (docker CLI). Resource-capped
(memory/cpus/pids), hardened (no-new-privileges), labelled, joined to the
agent's own network with no host port exposure, and swept by a stale reaper
for anything a crashed run leaks. The `docker` argv is built by pure functions
so it is unit-tested without a daemon.
- openplc: drives the OpenPLC web UI to load a program — login → upload →
save → compile (MatIEC) → start_plc (which opens Modbus/TCP 502).
- runtime::provision_and_test: composes them under a hard deadline with
guaranteed teardown on every path (success / error / timeout), then runs the
existing ICS probe against the provisioned endpoint. extract_program picks the
best loadable program (complete ST > largest ST > PLCopen XML).
- orchestrator: for a PlcSps target with control logic and no live URL, run
provision-and-test after the static PLC scan. Gated by PlcRuntimeConfig
(PLC_RUNTIME_ENABLED, default off — needs Docker access in the agent).
DAST-against-WebVisu and CODESYS-runtime fidelity are follow-ups.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/admin/tenants lists tenant DBs; DELETE /api/admin/tenants/{tenant_id} drops them (GDPR). Behind a separate auth path that rejects customer realm tokens.