# 2026-07-13 -- D-113(a2) step 1: a thin OPNsense REST API client

First delivery under the D-113 ruling (ADOPTED: option (a2) -- stay on OPNsense, move
configuration OFF hand-authored `config.xml` and ONTO the documented REST API).

## What shipped

- `scripts/opnsense-api.sh` -- thin REST client. `[--dry-run] <GET|POST> <api-path> [json-body]`.
- `tests/opnsense-api/run-tests.sh` -- offline harness, **21 PASS**, no network, no real key.
- Gauntlet ALL GREEN (**53** harnesses, was 52). `repo-lint` 0 fail.

## Why this exists (the D-113 argument, in one line)

Hand-authoring the appliance's GUI-owned `config.xml` was the SINGLE root cause of DOCFIX-191
(no sshd/key -> lockout), DOCFIX-192 (no console), DOCFIX-193 (no DHCP), and the 667-element
migration self-heal we ended up depending on. **None of those is expressible through the API**,
where sshd, DHCP and firewall rules are typed resources with defaults. You cannot forget to
enable sshd in a format where sshd is a typed field.

## Design decisions worth keeping

**The secret never reaches argv.** Credentials are read from a file and passed to `curl` via
`--config` on STDIN. `curl -u key:secret` would put the secret in the process's argv, where
ANY user on the box can read it out of `ps`. Harness case **T9** stubs `curl` with an argv
recorder and asserts the secret is absent from argv and present on stdin -- so a future
"simplification" to `curl -u` turns the harness red. That test is the point of the file.

**The API host is never inferred** (hard rule 2). `$OPNSENSE_API_HOST` unset is a loud failure,
not a default of `10.10.0.1`. Harness T3.

**`--insecure` is deliberate and scoped.** The edge presents a self-signed cert that OPNsense
REGENERATES ON EVERY BOOT (measured: "Created web GUI TLS certificate" appears in the config
revision trail after each reboot), so pinning it is pointless. Acceptable ONLY because the
transport is a private lab LAN leg (virbr2). Not to be copied to anything tenant-facing.

**Dry-run reads no credentials** (T11), which is what lets the harness prove URL construction
offline.

## Not done yet -- the blocking step

**The API is alive but has no key.** Measured on the live edge 2026-07-13: lighttpd IS listening
on 443/80 and the API answers 401 (i.e. it is up and demanding auth), and `root` has **0 API
keys**.

Minting a key must happen in the **GUI** (System > Access > Users > root > API keys > "+").
That is deliberate: writing `apikeys` into `config.xml` by hand would be the exact anti-pattern
D-113 just retired. The downloaded key/secret file goes to
`~/vr1-office1-creds/opnsense-api.txt` (operator-only; never read into agent context -- SEC-007
covers that directory).

Once the key exists, the proof-of-path is: drive the DHCP subnet we pushed by hand yesterday
through the API instead, and confirm it round-trips. If the API can express it, the template
retires to a MINIMAL bootstrap config (sshd + root key + console + the API key) and everything
else -- DHCP, firewall, interfaces -- moves to the API. If it cannot, that is the signal that
OPNsense is not buying the abstraction we are paying for, and D-113 gets revisited.

## Revert

    git rm scripts/opnsense-api.sh
    git rm -r tests/opnsense-api/

Nothing live is touched by this change; the client has never been run against the edge (no key
exists yet).
