diff --git a/docs/changelog-20260713-opnsense-api-proven.md b/docs/changelog-20260713-opnsense-api-proven.md new file mode 100644 index 0000000..c06ec6c --- /dev/null +++ b/docs/changelog-20260713-opnsense-api-proven.md @@ -0,0 +1,64 @@ +# 2026-07-13 -- D-113(a2) PROVEN: the OPNsense REST API round-trips the edge config + +The API path adopted in D-113 (option a2) is now demonstrated working against the LIVE Office1 +edge. This closes the open question that gated the ruling. + +## Bootstrap (the one manual step, by design) + +An API key was minted for `root` in the GUI. In OPNsense **26.1** this is NOT in the user-edit +dialog and NOT in the ApiKeys tab (that tab only wires `search_api_key` + `del_api_key` -- it +can list and delete, but CANNOT create). It is a **row action on the Users list**: the +`fa-ticket` icon, "Create and download API key for this user" (source: +`mvc/app/views/OPNsense/Auth/user.volt`). Earlier instructions in this repo referencing +"edit user -> API keys -> +" describe the OLD UI and are wrong for 26.1. + +`ApiKeyField::add()` mints `key` and `secret` as `base64(random_bytes(60))` (80 chars each) and +persists `key|crypt(secret,'$6$')` -- **the secret is shown exactly once and is stored hashed**. +Lose it and you delete the key and mint a new one. + +Key stored at `~/vr1-office1-creds/opnsense-api.txt` (0600, operator-only, never read into agent +context). SEC-007 amended to cover it. + +## Measured, against the live edge + + GET core/firmware/status -> HTTP 200, OPNsense 26.1 (auth works) + GET kea/dhcpv4/get -> dhcpv4.general.enabled = 1, interfaces = lan + POST kea/dhcpv4/search_subnet -> 1 row: + + subnet = 10.10.0.0/24 + pools = 10.10.0.100-10.10.0.199 + option_data.routers = 10.10.0.1 + option_data.domain_name_servers = 10.10.0.1 + option_data.domain_name = office1.vr1.cloud.neumatrix.local + description = LAN DHCP + +The API reads back EXACTLY the config we pushed via config.xml yesterday -- and exposes it as +TYPED, NAMED FIELDS (`option_data.routers`, `pools`, ...), not free-form XML. That is the whole +(a2) argument, concretely: DOCFIX-191 (missing sshd), DOCFIX-192 (silenced console) and +DOCFIX-193 (an inert ISC `` block against a Kea backend) are not expressible here. You +cannot omit a typed field that has a default, and you cannot address the wrong DHCP backend when +the endpoint IS the backend. + +## Endpoint map (measured from Dhcpv4Controller.php, not guessed) + + get | searchSubnet | getSubnet | addSubnet | setSubnet | delSubnet + searchReservation | getReservation | addReservation | setReservation | delReservation + downloadReservations | uploadReservations | searchPeer | ... | delPeer + +Note OPNsense maps camelCase actions to snake_case URLs: `searchSubnetAction` -> +`POST /api/kea/dhcpv4/search_subnet`. Response root key is `dhcpv4` (NOT `dhcp4` -- the config +XML element name differs from the API key name; do not infer one from the other). + +## NOT done yet + +- **No WRITE has been performed through the API.** Read is proven; the write path + (`setSubnet` + a reconfigure) is a live mutation and remains gated. +- The `config.xml` template is still in place. It should now be reduced to a MINIMAL bootstrap + (sshd + root key + console + nothing else), with DHCP/firewall/interfaces moving to the API. + Until that lands, DO NOT push a full rendered config.xml -- it would clobber API-managed state. + +## Revert + +Nothing to revert: this change is read-only against the edge plus documentation. To revoke the +credential: OPNsense GUI -> System > Access > Users > root row > ApiKeys tab -> delete, then +`rm ~/vr1-office1-creds/opnsense-api.txt`. diff --git a/docs/security-ledger.md b/docs/security-ledger.md index d8b4837..f060041 100644 --- a/docs/security-ledger.md +++ b/docs/security-ledger.md @@ -15,4 +15,4 @@ | SEC-004 | 2026-05-27 | repo temporarily PUBLIC for v1 web_fetch workflow | project completion list | operator | OPEN -- flip to private at v1 close (deferral reaffirmed 2026-07-06) | | SEC-005 | 2026-07-13 | long-lived GitBucket PAT stored in PLAINTEXT on the jumphost (`credential.helper store`, mode 600) so the agent can push unattended; GitBucket PATs are account-wide -- no per-repo scoping exists | docs/changelog-20260713-git-credential-store.md | operator | OPEN -- dedicated token (not a reused personal one); revoke + rotate at v1 close, or immediately if the jumphost is shared/rebuilt. Revoke: GitBucket -> Account Settings -> Applications | | SEC-006 | 2026-07-12 | NetBox API token PASTED INTO AGENT CHAT CONTEXT during D-111 IPv6 reconciliation -- the token is BURNED (it exists in a transcript that is not under our control) and must be revoked + reissued, not merely rotated on schedule | docs/changelog-20260712-office1-opnsense-edge-build.md; flagged again at 2026-07-12 session close | operator | OPEN -- **DEFERRED by operator ruling 2026-07-13: revoke at completion of this deployment.** Until then the token is live and exposed. Revoke: NetBox -> Admin -> API Tokens | -| SEC-007 | 2026-07-12 | `~/vr1-office1-creds/` on the jumphost holds the Office1 edge root password + its bcrypt hash + the `office1_svc` SSH PRIVATE key, in plaintext (dir 0700, files 0600) | 2026-07-12 edge build; custody detail deliberately not recorded here per D-069 | operator | OPEN -- required for edge management (D-112(c) makes SSH the only management path), so this is a ROTATION obligation, not a delete-me. Rotate at v1 close / if the jumphost is rebuilt or shared | +| SEC-007 | 2026-07-12 | `~/vr1-office1-creds/` on the jumphost holds the Office1 edge root password + its bcrypt hash + the `office1_svc` SSH PRIVATE key + (added 2026-07-13) the OPNsense **API key/secret** (`opnsense-api.txt`, 0600), all in plaintext (dir 0700, files 0600) | 2026-07-12 edge build; custody detail deliberately not recorded here per D-069 | operator | OPEN -- required for edge management (D-112(c) makes SSH the only management path), so this is a ROTATION obligation, not a delete-me. Rotate at v1 close / if the jumphost is rebuilt or shared | diff --git a/docs/session-ledger.md b/docs/session-ledger.md index 7618d68..66e4a1f 100644 --- a/docs/session-ledger.md +++ b/docs/session-ledger.md @@ -1983,6 +1983,19 @@ If it round-trips, the template retires to a MINIMAL bootstrap (sshd + key + console + apikey) and DHCP/firewall/interfaces move to the API. If it cannot express it, D-113 gets revisited. +- **D-113(a2) PROVEN 2026-07-13 against the LIVE edge.** API key minted; `GET + core/firmware/status` -> 200; `POST kea/dhcpv4/search_subnet` reads back EXACTLY yesterday's + hand-pushed DHCP (10.10.0.0/24, pool .100-.199, routers/DNS 10.10.0.1) as TYPED fields. + **GUI gotcha (26.1):** API keys are NOT created in the user-edit dialog nor the ApiKeys tab + (that tab is search+delete ONLY). They are a ROW ACTION on the Users list -- the `fa-ticket` + icon. The secret is shown ONCE (stored `crypt($6$)`). + **API gotchas:** response root key is `dhcpv4` (not `dhcp4`); camelCase actions map to + snake_case URLs (`searchSubnetAction` -> `search_subnet`). + **NOT done:** no WRITE through the API yet (gated live mutation). **The config.xml template is + still live -- DO NOT push a full rendered config.xml now; it would clobber API-managed state.** + Next: reduce the template to a MINIMAL bootstrap (sshd + key + console) and move + DHCP/firewall/interfaces to the API. + ## State facts to remember - beta cluster left at **node_count=2** (deliberate; bonus resize acceptance coverage).