# ops-mason-construct — build security structure; never become what it builds. # # Founder decision 2026-10-18 (MASON-IN-0005, supersedes the read-only # resolution of MASON-IN-0004): ops-mason is the builder (INTENT.md, phase 4). # It creates, changes and tears down policies, auth roles and KV structure # for an approved construction plan. ops-warden routes to what it built. # # How the MASON-IN-0004 escalation stays closed: that chain was # write policy -> bind role -> read role-id -> mint secret-id -> log in. # This grant keeps the first two steps (the job) and holds none of the steps # that turn a role into a login: no role-id, no secret-id, no token creation, # no KV value reads. ops-mason can build a lane but cannot use one. The # remaining risk, a built policy broader than its plan, is caught after the # fact by a post-build check, policy text vs plan file (MASON-WP-0015-T04), and the # audit log (display_name claude-*). # # Granted only by the founder's own OIDC session through # GRANT_POLICY=ops-mason-construct ./scripts/bao-session.sh grant # as a 45-minute named token, after the plan is approved. Survey-only sessions # keep using ops-mason-survey. # # Path priority (same note as ops-mason-survey.hcl): within one policy the most # 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 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"] } # Every policy stays readable for the survey and for verification. path "sys/policies/acl/*" { capabilities = ["read"] } # --- build: workload read policies ------------------------------------------ # The lane convention (railiance-platform/openbao/policies). Content is not # constrainable in OpenBao; the post-build check compares it with the plan. path "sys/policies/acl/workload-kv-read-*" { capabilities = ["create", "read", "update", "delete"] } # --- build: Kubernetes auth roles for External Secrets ---------------------- # 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" = [] "token_policies" = ["*,*"] } } path "auth/kubernetes/role/*" { capabilities = ["read", "list"] } # --- build: AppRole role definitions (never credentials) -------------------- # Definition only. role-id and secret-id are denied below, so an AppRole # 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" = [] "token_policies" = ["*,*"] } } # --- build: KV structure on the platform mount ------------------------------ # create without read/update: a new path can get placeholder values (`xxx`), # an existing value can be neither read nor overwritten. path "platform/data/workloads/*" { capabilities = ["create"] } path "platform/data/suppliers/*" { capabilities = ["create"] } # Describe, inventory and tear down paths (custom_metadata, metadata delete). path "platform/metadata/workloads/*" { capabilities = ["create", "read", "update", "delete", "list"] } path "platform/metadata/suppliers/*" { capabilities = ["create", "read", "update", "delete", "list"] } path "platform/metadata/*" { capabilities = ["read", "list"] } # --- 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"] } # --- 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"] } # --- never read a value -------------------------------------------------------- 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.