- cmd/flex-auth -> cmd/access-engine; usage and serving lines say access-engine - cmd/flex-auth: prints the rename and exits 2, with a test - image: entrypoint /usr/local/bin/access-engine, flex-auth stub alongside; live pins pass only args, so their next roll is unaffected - Makefile, CI, verify-posture, tenancy.yaml, live docs updated; workplans, decisions and ADRs left as written - runtime names (module, audience, k8s, image package, /opt paths) unchanged Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 735967@bnt-lap001 Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
84 lines
4.1 KiB
Markdown
84 lines
4.1 KiB
Markdown
# ops-warden secret-read example (`flex-auth-ops-warden-fetch`)
|
|
|
|
Policy, registry and fixtures for ops-warden's **secret-fetch** policy check
|
|
(`check_fetch_policy` in ops-warden `src/warden/policy.py`): before
|
|
`warden access <need> --fetch/--exec` proxies a non-SSH credential read, it asks
|
|
whether `read` on `secret:<need_id>[/<domain>]` owned by `<owner_repo>` is
|
|
allowed. Opened by the-custodian bug `b792c5d0` (CUST-WP-0075): those checks
|
|
were sent to the SSH signing pin `flex-auth-ops-warden`, whose package admits
|
|
only `resource.system == "ops-warden"` and whose caller bindings know only that
|
|
system, so every other owner got 403.
|
|
|
|
| File | What it is |
|
|
| --- | --- |
|
|
| `policy_package.md` | `ops-warden.secret.read` v1, `allow_ttl: 5m`; the `catalog_lanes` block is generated |
|
|
| `registry_snapshot.json` | generated: one system per owner, one `secret:<need_id>` resource per lane |
|
|
| `policy_fixtures.yaml` | 15 fixtures: 4 allows, 11 denial branches |
|
|
| `check_request_*.json` | standalone requests for `POST /v1/check` |
|
|
| `../../values/ops-warden-fetch.yaml` | the pin; caller bindings generated, image digest set by hand after CI |
|
|
|
|
## Why a separate pin
|
|
|
|
One decision class per pin, independently rollable (FLEX-WP-0016). The server
|
|
serves one policy package per process. SSH signing (`sign` on
|
|
`ssh-certificate`, system `ops-warden`) and secret reads (`read` on `secret`,
|
|
system = owner repo) differ in action, resource type, systems and caller
|
|
bindings. Folding reads into the signing pin would redeploy the lane the
|
|
ops-bridge tunnels depend on for every catalog change.
|
|
|
|
## The rule
|
|
|
|
Allow when all hold, otherwise deny with the first failing reason:
|
|
`action == read` (`wrong_action`), `resource.type == secret`
|
|
(`wrong_resource_type`), tenant `tenant:platform` (`wrong_tenant`),
|
|
`subject.type == operator` (`wrong_subject_type`), non-empty subject id
|
|
(`missing_subject`), `context.need_id` present (`missing_need_id`) and in the
|
|
catalog set (`unknown_need`), `resource.system` a catalog owner
|
|
(`unknown_system`) and the owner of that need (`wrong_system`),
|
|
`resource.id == secret:<need_id>` or `secret:<need_id>/<context.domain>`
|
|
(`resource_need_mismatch`), `context.owner_repo == resource.system`
|
|
(`owner_repo_mismatch`).
|
|
|
|
Caller authentication is `enforce`: every owner system is bound to
|
|
`system:serviceaccount:ops-warden:ops-warden`. Another caller, an anonymous
|
|
call, or an owner system missing from the bindings is rejected with 401/403
|
|
before the package runs.
|
|
|
|
For a registered resource the registry's `type` wins over the caller's
|
|
(FLEX-DEC-2026-012), so a non-secret type only reaches the package on an
|
|
unregistered id; the package denies it either way.
|
|
|
|
## Regenerate when the catalog changes
|
|
|
|
The readable set is every ops-warden catalog entry with `status: active`,
|
|
`exec_capable: true` and `lane: secret`. After a lane is added or retired:
|
|
|
|
```bash
|
|
python3 tools/gen_ops_warden_fetch.py # rewrites the three generated files
|
|
python3 tools/gen_ops_warden_fetch.py --check # exit 1 on drift (tests/stage1.sh runs it)
|
|
go run ./cmd/access-engine test-policy --file examples/ops-warden-fetch/policy_package.md
|
|
```
|
|
|
|
`--catalog PATH` overrides the default `~/ops-warden/registry/routing/catalog.yaml`.
|
|
A new **owner system** adds a caller binding, so it needs a pin redeploy. A new
|
|
lane under an existing owner changes only the package and registry, which are
|
|
baked into the image, so it needs a new image too (`deploy/README.md`).
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
go run ./cmd/access-engine test-policy --file examples/ops-warden-fetch/policy_package.md # 6 Rego tests, 15 fixtures
|
|
go run ./cmd/access-engine load-registry -file examples/ops-warden-fetch/registry_snapshot.json
|
|
go test ./cmd/access-engine -run OpsWardenFetch
|
|
```
|
|
|
|
## Not yet deployed
|
|
|
|
`values/ops-warden-fetch.yaml` carries no image digest until CI builds an image
|
|
containing this directory. The deploy steps (founder go required) are in
|
|
`workplans/ADHOC-2026-10-03.md`.
|
|
|
|
Residual (build mode, close-down checklist row 1): `subject.id` is whatever
|
|
ops-warden sends (its subject env or `operator`); it is not bound to an attended
|
|
human identity. The caller binding proves that ops-warden asked, not which
|
|
operator did.
|