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:
parent
dee5bac2c6
commit
6a602edafb
4 changed files with 229 additions and 7 deletions
|
|
@ -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)."
|
||||
]
|
||||
}
|
||||
|
|
@ -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
41
tests/test_bao_session.py
Normal 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()
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue