Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 3775@bnt-lap001 Assistant-Session: cfacb6a5-ad5b-4f79-ac67-053e3b0d34a3 |
||
|---|---|---|
| .. | ||
| check_request_allow_forgejo_admin_api_token.json | ||
| check_request_allow_openrouter_llm_connect.json | ||
| check_request_deny_non_secret_type.json | ||
| check_request_deny_unknown_need.json | ||
| check_request_deny_wrong_system.json | ||
| policy_fixtures.yaml | ||
| policy_package.md | ||
| README.md | ||
| registry_snapshot.json | ||
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:
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/flex-auth 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
go run ./cmd/flex-auth test-policy --file examples/ops-warden-fetch/policy_package.md # 6 Rego tests, 15 fixtures
go run ./cmd/flex-auth load-registry -file examples/ops-warden-fetch/registry_snapshot.json
go test ./cmd/flex-auth -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.