MASON-WP-0009-T04 done: live capability verification of the KeyCape secrets-engine-openbao lane

Value-free evidence under the read-only ops-mason-survey grant (revoked after
use). Adds T05 for live-token checks and the pointer route. Fixes
bao-session.sh grant misreporting an unreadable policy as absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
This commit is contained in:
tegwick 2026-10-07 13:47:14 +02:00
parent dee5bac2c6
commit 6a602edafb
4 changed files with 229 additions and 7 deletions

View file

@ -0,0 +1,134 @@
{
"workplan": "MASON-WP-0009",
"task": "MASON-WP-0009-T04",
"date": "2026-10-07",
"kind": "capability verification (metadata and policy text only; value-free)",
"status": "pass",
"grant": {
"policy": "ops-mason-survey",
"extra_policies": ["default"],
"display_name": "token-claude-MASON-WP-0009-T04",
"bao_addr": "http://127.0.0.1:18200",
"founder_session_used": false,
"revoked_self": true,
"lookup_after_revoke_failed": true,
"token_file_shredded": true
},
"kv_path": "platform/workloads/keycape/secrets-engine-openbao",
"secret_value_read": false,
"checks": {
"1_policies": {
"result": "pass",
"workload-kv-read-keycape-secrets-engine-openbao-eso": {
"live_matches_repo_file": true,
"repo_file": "railiance-platform/openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl",
"paths": {
"platform/data/workloads/keycape/secrets-engine-openbao": ["read"],
"auth/token/lookup-self": ["read"],
"auth/token/revoke-self": ["update"]
}
},
"workload-kv-read-keycape-secrets-engine-openbao-operator": {
"live_matches_repo_file": true,
"repo_file": "railiance-platform/openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-operator.hcl",
"paths": {
"platform/data/workloads/keycape/secrets-engine-openbao": ["read"],
"auth/token/revoke-self": ["update"]
}
}
},
"2_roles": {
"result": "pass",
"auth/kubernetes/role/external-secrets-keycape-secrets-engine-openbao": {
"matches": "railiance-platform/credential-change-requests/CCR-2026-0033.yaml",
"bound_service_account_names": ["external-secrets"],
"bound_service_account_namespaces": ["external-secrets"],
"audience": "openbao",
"token_ttl_s": 900, "token_max_ttl_s": 900, "token_explicit_max_ttl_s": 900,
"token_no_default_policy": true,
"token_policies": ["workload-kv-read-keycape-secrets-engine-openbao-eso"]
},
"auth/netkingdom/role/keycape-secrets-engine-openbao-operator-workload-kv-read": {
"matches": "railiance-platform/credential-change-requests/CCR-2026-0034.yaml",
"role_type": "oidc",
"bound_claims_keys": ["groups", "/assurance/level"],
"bound_groups": ["net-kingdom-admins"],
"bound_assurance_level": "aal2",
"bound_claims_type": "string",
"user_claim": "sub", "groups_claim": "groups",
"oidc_scopes": ["openid", "profile", "email", "groups"],
"token_ttl_s": 300, "token_max_ttl_s": 300, "token_explicit_max_ttl_s": 300,
"token_num_uses": 4,
"token_type": "service",
"token_no_default_policy": true,
"token_policies": ["workload-kv-read-keycape-secrets-engine-openbao-operator"],
"allowed_redirect_uris_match_ccr": true,
"note": "includes the known harmless retired redirect https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback, as in the CCR"
}
},
"3_kv_metadata": {
"result": "pass",
"method": "bao kv metadata get (never kv get)",
"current_version": 1,
"version_1_deleted": false,
"version_1_destroyed": false,
"cas_required": true,
"max_versions": 2,
"delete_version_after": "0s",
"custom_metadata_keys": ["ccr", "classification", "client_id", "issuer", "owner", "plan", "readers", "rotation"],
"custom_metadata_matches_plan_section_3_1": true
},
"4_circularity": {
"result": "pass",
"a_policies_reaching_data_path": {
"policies_listed": 81,
"policies_read": 79,
"not_readable": {"ops-mason-survey": "denied by its own explicit deny (by design)", "root": "built-in, not readable"},
"lane_policies": ["workload-kv-read-keycape-secrets-engine-openbao-eso", "workload-kv-read-keycape-secrets-engine-openbao-operator"],
"broad_policies": {
"platform-admin": "platform/* create/read/update/delete/list; bound to auth/netkingdom/role/platform-admin (with operator-custody)",
"root": "built-in"
},
"other_grant_on_path": false,
"secrets_engine_policies_reach_path": false,
"secrets_engine_policies_checked": ["secrets-engine-build", "secrets-engine-prod", "secrets-engine-test"],
"secrets-engine-openbao_jwt_policy_exists": false
},
"b_jwt_roles": {
"auth_jwt_mount_present": false,
"keycape_services_jwt_mount_present": false,
"auth_mounts": ["approle", "keycape (oidc)", "kubernetes", "netkingdom (oidc)", "token"],
"roles_surveyed": {"kubernetes": 27, "netkingdom": 12, "approle": 14},
"roles_binding_path_reaching_policies": [
"kubernetes/external-secrets-keycape-secrets-engine-openbao",
"netkingdom/keycape-secrets-engine-openbao-operator-workload-kv-read",
"netkingdom/platform-admin"
]
},
"c_eso_reader_on_kubernetes_auth": true
},
"5_grant_self_check": {
"result": "pass",
"platform/data/workloads/keycape/secrets-engine-openbao": "deny",
"platform/metadata/workloads/keycape/secrets-engine-openbao": "list, read",
"sys/policies/acl/x": "read",
"sys/policies/acl/ops-mason-survey": "deny",
"auth/approle/role/x/secret-id": "deny",
"auth/token/create": "deny"
}
},
"not_permitted_by_ops-mason-survey": [
"auth/keycape/role (list/read) - the KeyCape human OIDC mount's roles were not surveyed; invariant (i)/(ii) cover it only through policy text",
"sys/policies/acl/ops-mason-survey (own policy, explicit deny)"
],
"observations_for_owners": [
"ops-mason-build (retired, 'never to be installed') is still installed. It denies platform/data/* but grants create/update on platform/metadata/*, which could change cas_required/max_versions of this entry. No surveyed role binds it. Founder/platform should delete it.",
"secrets-engine-prod may write sys/policies/acl/se-prod-* with unconstrained content and bind it via auth/approle/role/se-prod-*; it could author a policy reaching this path. No such policy exists now; this is an indirect route outside invariant (ii) as worded.",
"agent-high-risk-boundary does not list this path among its explicit denies, unlike the sibling secrets-engine approval-client/requester entries."
],
"not_covered_here": [
"Plan section 4 checks 1-3 and 6 need live lane tokens (operator OIDC login, ESO token, non-member identity, secrets-engine service-jwt run). The survey grant cannot mint or use them; policy text and role definitions are verified equivalent instead.",
"The secrets-engine-openbao service-JWT mount (keycape-services) and its policy do not exist yet (RPF-WP-0035-T02); invariant (ii) must be rechecked once they are applied.",
"Pointer-only ops-warden route for the operator lane (MASON-WP-0009-T05)."
]
}

View file

@ -83,11 +83,21 @@ cmd_grant() {
if [[ -z "$task" ]]; then
red "usage: $0 grant <task-name> e.g. 'operators-mount'"; exit 2
fi
if ! bao policy read "$GRANT_POLICY" >/dev/null 2>&1; then
red "policy $GRANT_POLICY does not exist yet."
echo "Create it first — see docs, or ask Claude to draft it. Refusing to"
echo "fall back to a broader policy: an unscoped grant is the thing this"
echo "script exists to avoid."
local probe_err
if ! probe_err="$(bao policy read "$GRANT_POLICY" 2>&1 >/dev/null)"; then
# Only bao's own "No policy named" means absent. A 403 or a missing
# token means your session cannot read it (expired, or not permitted),
# which says nothing about whether the policy exists.
if [[ "$probe_err" == *"No policy named"* ]]; then
red "policy $GRANT_POLICY does not exist yet."
echo "Create it first — see docs, or ask Claude to draft it. Refusing to"
echo "fall back to a broader policy: an unscoped grant is the thing this"
echo "script exists to avoid."
else
red "cannot read policy $GRANT_POLICY (session expired or not permitted)."
echo "Check '$0 status'; refresh with '$0 login' and retry. This is not"
echo "evidence that the policy is missing."
fi
exit 1
fi
umask 077

41
tests/test_bao_session.py Normal file
View file

@ -0,0 +1,41 @@
"""bao-session.sh grant: distinguish an absent policy from an unreadable one."""
import os
import subprocess
from pathlib import Path
SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "bao-session.sh"
def _run_grant(tmp_path, stub_stderr, stub_rc):
bindir = tmp_path / "bin"
bindir.mkdir()
stub = bindir / "bao"
stub.write_text(
"#!/usr/bin/env bash\n"
f"echo {stub_stderr!r} >&2\n"
f"exit {stub_rc}\n"
)
stub.chmod(0o755)
env = dict(os.environ)
env["PATH"] = f"{bindir}:{env['PATH']}"
env["HOME"] = str(tmp_path)
env["GRANT_FILE"] = str(tmp_path / "grant")
return subprocess.run(
["bash", str(SCRIPT), "grant", "t"], env=env, capture_output=True, text=True
)
def test_absent_policy_reported_as_absent(tmp_path):
r = _run_grant(tmp_path, "No policy named: ops-mason-survey", 2)
assert r.returncode == 1
assert "does not exist yet" in r.stdout
assert not (tmp_path / "grant").exists()
def test_permission_denied_not_reported_as_absent(tmp_path):
r = _run_grant(tmp_path, "Code: 403. Errors: * permission denied", 2)
assert r.returncode == 1
assert "does not exist" not in r.stdout
assert "session expired or not permitted" in r.stdout
assert not (tmp_path / "grant").exists()

View file

@ -9,7 +9,7 @@ flavor: planning
owner: claude
topic_slug: netkingdom
created: "2026-10-06"
updated: "2026-10-06"
updated: "2026-10-07"
state_hub_workstream_id: "d9be08f5-a0f5-5cd1-8f87-0a6868661d77"
---
@ -73,7 +73,7 @@ state_hub_task_id: "f59fae58-397a-5bf5-9d1d-a33dc4aaa2ac"
```task
id: MASON-WP-0009-T04
status: todo
status: done
priority: medium
state_hub_task_id: "01f55a4d-06de-5d48-a3db-a0746dcf9892"
```
@ -87,3 +87,40 @@ Note (2026-10-07, MASON-IN-0004): the capability verification runs under the
read-only `ops-mason-survey` grant, which replaces `ops-mason-build`. Installing
that policy is a founder act (AGENTS.md, "Installing the survey policy");
ops-mason cannot install or widen it.
Done 2026-10-07 under grant `claude-MASON-WP-0009-T04` (`ops-mason-survey`,
revoked and shredded after use). Evidence, value-free:
`docs/evidence/2026-10-07-keycape-secrets-engine-openbao-capabilities.json`.
All five capability checks pass: both lane policies match the platform repo
files; both roles match CCR-2026-0033/0034 (retired redirect noted); KV metadata
is v1, `cas_required`, `max_versions=2`, `delete_version_after=0s`, custom
metadata as in plan 3.1; only the two lane policies plus `platform-admin`/root
reach the data path, no `auth/jwt` (or `keycape-services`) mount exists, the
ESO reader is on Kubernetes auth; the grant itself is `deny` on the data path
and read-only on `sys/policies/acl/*`. Not permitted by the survey grant:
`auth/keycape/role`. Owner observations (retired `ops-mason-build` still
installed; `secrets-engine-prod` can author `se-prod-*` policies; this path is
missing from `agent-high-risk-boundary`) are in the evidence file. Plan checks
1-3 and 6 need live lane tokens and the route proposal is still owed: T05.
Side finding, fixed: `scripts/bao-session.sh grant` reported "policy does not
exist yet" whenever `bao policy read` failed, including when the founder's
`~/.vault-token` session was expired or unauthorised. It now says "cannot read
(session expired or not permitted)" unless bao answers "No policy named"
(`tests/test_bao_session.py`).
## Live lane proof and pointer route
```task
id: MASON-WP-0009-T05
status: wait
blocked_on: message-from:railiance-platform
blocking_reason: "Plan section 4 checks 1-3 and 6 need live lane tokens and the secrets-engine-openbao service-JWT mount and policy (RPF-WP-0035-T02), which are not applied yet; then recheck invariant (ii) and propose the pointer-only ops-warden route."
priority: medium
```
Once the service-JWT lane exists: re-run invariant (ii) against its policy,
collect owner-run evidence for checks 1-3 and 6 (operator and ESO token
capabilities, non-member refusal, a secrets-engine `service-jwt` run), then
propose the pointer-only ops-warden entry for the operator lane
(`resolvable: false` until those pass).