# ops-mason-build — the authority ops-mason actually needs, and no more. # # SCOPE.md says ops-mason "never touches secret values, even transiently". Until # now that was a promise in a document, kept by whoever was driving. This policy # makes OpenBao enforce it: the builder can create policies, auth roles and KV # path structure, and is denied every read of secret data on any mount. # # Granted through scripts/bao-session.sh as a 45-minute, named token, so an # action in the audit log is attributable to a task rather than to whoever's # operator session happened to be open. # --- survey ---------------------------------------------------------------- # Phase 2 of the construction process is an existing-structure survey, and the # plan for the state-hub lane was written without one because no session was # available. That omission is why an AppRole was proposed on a cluster that # already had kubernetes auth enabled. path "sys/mounts" { capabilities = ["read", "list"] } path "sys/auth" { capabilities = ["read", "list"] } path "sys/policies/acl" { capabilities = ["list"] } path "sys/policies/acl/*" { capabilities = ["create", "read", "update", "list"] } # --- auth roles ------------------------------------------------------------ path "auth/kubernetes/role/*" { capabilities = ["create", "read", "update", "list"] } path "auth/approle/role/*" { capabilities = ["create", "read", "update", "list"] } # --- KV structure, never KV values ----------------------------------------- # Metadata carries versions, timestamps and custom_metadata — enough to confirm # a path exists and that a paste-once delivery landed. It does not carry the # value. # create/update so a path can be *described* — custom_metadata is documentation, # not a value. delete is absent: deleting a metadata entry destroys every # version of the secret under it, which is a destructive act, not a build one. path "platform/metadata/*" { capabilities = ["create", "read", "update", "list"] } path "operators/metadata/*" { capabilities = ["create", "read", "update", "list"] } # --- verification ---------------------------------------------------------- # Minting a short-lived test token and asking what it can reach is how a lane is # proven correctly scoped, positively and negatively. path "auth/token/create" { capabilities = ["create", "update"] } path "auth/token/revoke" { capabilities = ["update"] } path "auth/token/lookup-self" { capabilities = ["read"] } path "sys/capabilities" { capabilities = ["create", "update"] } path "sys/capabilities-self" { capabilities = ["create", "update"] } # --- the grant cannot rewrite its own scope -------------------------------- # Without this, everything below is advisory: a token that can write policies # can delete its own denials and then read anything. An exact path outranks the # sys/policies/acl/* glob above, so this stanza wins. # # The consequence is deliberate — changing what ops-mason may do is an operator # act, performed with an operator session, and visible as such in the audit log. # It cannot be done quietly from inside a build. path "sys/policies/acl/ops-mason-build" { capabilities = ["read", "deny"] } # --- the line, stated as a denial ------------------------------------------ # Explicit deny outranks any grant, including one added here later by mistake. # If ops-mason needs to prove a credential works, the consumer proves it, or a # human does — see plans/state-hub-forge-derivation-read.md §8 for the one time # this line was crossed and why it was recorded rather than glossed. path "platform/data/*" { capabilities = ["deny"] } path "operators/data/*" { capabilities = ["deny"] } path "secret/data/*" { capabilities = ["deny"] } # Mount management is deliberately absent. Enabling or tuning a secrets engine # is a railiance-platform act; ops-mason builds inside mounts that already # exist. A grant that needed sys/mounts/* would be a different, broader thing # and should be recognised as such rather than folded in here.