Skip to content

The control plane

Everything the gateway trusts arrives as a signed file. obsign-control is where those files come from, and the reason a rule change is a dated, reviewed pull request instead of a click in a UI.

Terminal window
# the source tree is a git checkout:
# policies/*.cedar tools.json fail-mode.json identity/{provider,jwks}.json
obsign-control publish \
--source ~/acme-policies --key /tmp/ops.hex --key-id ops-2026 \
--dist /srv/obsign/dist
# [control] compiled policies@847d4fca5754 — 1 rule file(s), 2 tool(s), ...
# [control] published release 847d4fca5754 -> /srv/obsign/dist/releases/847d4fca5754

The version is the commit sha, resolved by reading .git directly; no git binary is required on the build host. Every decision recorded in the log cites policies@<sha>; replaying it months later means checking out that sha.

Compilation validates with the gateway’s own code paths, so what passes in CI cannot fail at startup across the fleet: Cedar syntax, the mandatory @id on every rule, duplicate tools, fail-mode overrides naming tools that do not exist, unusable or forbidden JWKS keys. The JWKS is a file in git and is reviewed like a rule, because it decides who can mint identities.

Publication holds two invariants:

  • A version is immutablereleases/<sha>/ is written once; publishing different content under an existing sha is refused. A crash mid-publish is repaired on the next run; a changed source is not.
  • The current files change atomically — write-then-rename on the files the gateways hot-reload: a reader sees the old release or the new one, never a torn file. Rollback needs no tooling: republish the old sha.

The release manifest is signed, but the artifact hashes inside it are plain SHA-256 of the file bytes, deliberately: “is the bundle my gateway loaded the one the manifest names?” must be answerable with nothing but sha256sum.

obsign-ledger export produces one pack for one chain; an auditor asks for a period. With the HTTP transport every agent session is its own chain, so “what did your agents do in Q3” is dozens of packs:

Terminal window
obsign-control export \
--wal /srv/obsign/wal --store /srv/obsign/ledger \
--out /tmp/dossier --key /tmp/ops.hex --key-id ops-2026

Every chain is exported, verified on the way out, and listed in a signed export manifest, so the dossier cannot lose a pack in transit without the loss being visible. A pack that fails verification is written and flagged, and neither repaired nor filtered: an export that fixed things on the way out would do exactly what the product exists to make impossible. The exit code says so.

Terminal window
obsign-control console \
--wal /srv/obsign/wal --store /srv/obsign/ledger --dist /srv/obsign/dist

Three server-rendered HTML pages on std::net: current release with its signature verdict, chains with their sealing state (each re-verified on request), records. No JavaScript, no template engine, no cache: what the console shows is what the files say now.

Read-only by construction. The only accepted method is GET, so the console can never become a second write path around git. It binds to localhost by default; authentication is the commercial layer’s job, not a reason to weaken the core.