diff --git a/.custodian-brief.md b/.custodian-brief.md index a59ffeb..dcf104a 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,19 +2,18 @@ # Custodian Brief — ops-mason **Domain:** infotech -**Last synced:** 2026-10-18 09:49 UTC +**Last synced:** 2026-10-18 09:50 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams ### Restore ops-mason as the OpenBao builder: the ops-mason-construct grant -Progress: 1/6 done | workplan_id: `384b3efc-f9a5-5ea9-b224-0ce900e79804` +Progress: 2/6 done | workplan_id: `384b3efc-f9a5-5ea9-b224-0ce900e79804` **Open tasks:** - ! Live negative and positive tests `8b512822` - · Install the policy (founder act) `1c8893bd` - · Executor and post-build check `7b7a436f` -- · Align ops-warden and railiance-platform references `8fbac212` - · Close the Kubernetes variant `feb17422` ### Describe every stored credential so the store is navigable diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 5718566..be00d07 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -99,7 +99,7 @@ | task | MASON-WP-0015-T02 | todo | — | workplans/MASON-WP-0015-construct-grant.md | | task | MASON-WP-0015-T03 | wait | — | workplans/MASON-WP-0015-construct-grant.md | | task | MASON-WP-0015-T04 | todo | — | workplans/MASON-WP-0015-construct-grant.md | -| task | MASON-WP-0015-T05 | todo | — | workplans/MASON-WP-0015-construct-grant.md | +| task | MASON-WP-0015-T05 | done | — | workplans/MASON-WP-0015-construct-grant.md | | task | MASON-WP-0015-T06 | todo | — | workplans/MASON-WP-0015-construct-grant.md | | intake | MASON-IN-0001 | closed | green | intakes/intakes.md | | intake | MASON-IN-0002 | closed | — | intakes/intakes.md | diff --git a/policies/ops-mason-construct.hcl b/policies/ops-mason-construct.hcl index c71aafc..1f8f84c 100644 --- a/policies/ops-mason-construct.hcl +++ b/policies/ops-mason-construct.hcl @@ -23,22 +23,59 @@ # specific path wins, and a `+` segment loses to a literal one. The denies below # are literal or more specific than the grants they bound. # -# allowed_parameters: OpenBao glob support on list-valued parameters must be -# confirmed by the negative tests in MASON-WP-0015-T03 before this is relied on. +# allowed_parameters: OpenBao matches a comma string ("workload-kv-read-x,default") +# against the glob as one value before splitting it (MASON-WP-0015-T03 probe, +# 2026-10-18), so token_policies containing a comma is denied outright. A JSON +# list is checked element by element; pass several policies that way. # --- everything the survey grant can read ----------------------------------- -path "sys/mounts" { capabilities = ["read", "list"] } -path "sys/auth" { capabilities = ["read", "list"] } -path "sys/policies/acl" { capabilities = ["list"] } -path "auth/approle/role" { capabilities = ["list"] } -path "auth/netkingdom/role/*" { capabilities = ["read", "list"] } -path "auth/jwt/role/*" { capabilities = ["read", "list"] } -path "auth/keycape-services/role/*" { capabilities = ["read", "list"] } -path "operators/metadata/*" { capabilities = ["read", "list"] } -path "sys/capabilities" { capabilities = ["update"] } -path "sys/capabilities-self" { capabilities = ["update"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } +path "sys/mounts" { + capabilities = ["read", "list"] +} + +path "sys/auth" { + capabilities = ["read", "list"] +} + +path "sys/policies/acl" { + capabilities = ["list"] +} + +path "auth/approle/role" { + capabilities = ["list"] +} + +path "auth/netkingdom/role/*" { + capabilities = ["read", "list"] +} + +path "auth/jwt/role/*" { + capabilities = ["read", "list"] +} + +path "auth/keycape-services/role/*" { + capabilities = ["read", "list"] +} + +path "operators/metadata/*" { + capabilities = ["read", "list"] +} + +path "sys/capabilities" { + capabilities = ["update"] +} + +path "sys/capabilities-self" { + capabilities = ["update"] +} + +path "auth/token/lookup-self" { + capabilities = ["read"] +} + +path "auth/token/revoke-self" { + capabilities = ["update"] +} # Every policy stays readable for the survey and for verification. path "sys/policies/acl/*" { @@ -56,12 +93,15 @@ path "sys/policies/acl/workload-kv-read-*" { # Role may carry only workload-kv-read-* policies; legacy `policies` refused. path "auth/kubernetes/role/external-secrets-*" { capabilities = ["create", "read", "update", "delete", "list"] + allowed_parameters = { "token_policies" = ["workload-kv-read-*"] "*" = [] } + denied_parameters = { - "policies" = [] + "policies" = [] + "token_policies" = ["*,*"] } } @@ -74,12 +114,15 @@ path "auth/kubernetes/role/*" { # lane's credential delivery stays a founder act. path "auth/approle/role/+" { capabilities = ["create", "read", "update", "delete"] + allowed_parameters = { "token_policies" = ["workload-kv-read-*"] "*" = [] } + denied_parameters = { - "policies" = [] + "policies" = [] + "token_policies" = ["*,*"] } } @@ -108,26 +151,69 @@ path "platform/metadata/*" { } # --- the grant cannot widen itself or every token --------------------------- -path "sys/policies/acl/ops-mason-construct" { capabilities = ["deny"] } -path "sys/policies/acl/ops-mason-survey" { capabilities = ["deny"] } -path "sys/policies/acl/default" { capabilities = ["deny"] } -path "sys/policies/acl/root" { capabilities = ["deny"] } +path "sys/policies/acl/ops-mason-construct" { + capabilities = ["deny"] +} + +path "sys/policies/acl/ops-mason-survey" { + capabilities = ["deny"] +} + +path "sys/policies/acl/default" { + capabilities = ["deny"] +} + +path "sys/policies/acl/root" { + capabilities = ["deny"] +} # --- never become what it builds --------------------------------------------- -path "auth/approle/role/+/role-id" { capabilities = ["deny"] } -path "auth/approle/role/+/secret-id" { capabilities = ["deny"] } -path "auth/approle/role/+/secret-id/*" { capabilities = ["deny"] } -path "auth/approle/role/+/secret-id-accessor/*" { capabilities = ["deny"] } -path "auth/approle/role/+/custom-secret-id" { capabilities = ["deny"] } -path "auth/token/create" { capabilities = ["deny"] } -path "auth/token/create/*" { capabilities = ["deny"] } -path "auth/token/create-orphan" { capabilities = ["deny"] } +path "auth/approle/role/+/role-id" { + capabilities = ["deny"] +} + +path "auth/approle/role/+/secret-id" { + capabilities = ["deny"] +} + +path "auth/approle/role/+/secret-id/*" { + capabilities = ["deny"] +} + +path "auth/approle/role/+/secret-id-accessor/*" { + capabilities = ["deny"] +} + +path "auth/approle/role/+/custom-secret-id" { + capabilities = ["deny"] +} + +path "auth/token/create" { + capabilities = ["deny"] +} + +path "auth/token/create/*" { + capabilities = ["deny"] +} + +path "auth/token/create-orphan" { + capabilities = ["deny"] +} # --- never read a value -------------------------------------------------------- -path "platform/data/*" { capabilities = ["deny"] } -path "operators/data/*" { capabilities = ["deny"] } -path "secret/data/*" { capabilities = ["deny"] } +path "platform/data/*" { + capabilities = ["deny"] +} + +path "operators/data/*" { + capabilities = ["deny"] +} + +path "secret/data/*" { + capabilities = ["deny"] +} # Not granted, on purpose: sys/mounts/* and sys/auth/* (no new mounts or auth # methods), auth/token/revoke (other tokens), operators/ and secret/ writes, # any policy outside workload-kv-read-*, any role outside the two shapes above. + diff --git a/tests/test_construct_policy.py b/tests/test_construct_policy.py index cdc138b..d1beffb 100644 --- a/tests/test_construct_policy.py +++ b/tests/test_construct_policy.py @@ -60,6 +60,8 @@ def test_role_writes_restrict_token_policies(): if path.startswith("auth/") and "/role/" in path and _caps(body) & {"create", "update"}: assert '"token_policies" = ["workload-kv-read-*"]' in body, path assert re.search(r'denied_parameters\s*=\s*\{\s*"policies"\s*=\s*\[\]', body), path + # Comma strings bypass the glob (MASON-WP-0015-T03). + assert re.search(r'"token_policies"\s*=\s*\["\*,\*"\]', body), path def test_no_mount_or_auth_method_writes():