Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 327203@bnt-lap001 Assistant-Session: 55028ae3-4914-4bd1-8f5c-815f3527aef0
155 lines
5.1 KiB
HCL
155 lines
5.1 KiB
HCL
# ops-mason-survey — read-only survey and verification. Nothing else.
|
|
#
|
|
# Founder decision 2026-10-07 under MASON-IN-0004, option (c)+(a): ops-mason
|
|
# designs, surveys and verifies; it does not build. Policy writes, auth-role
|
|
# writes, AppRole secret-id minting and token creation are applied by the
|
|
# founder through the railiance-platform delegated applier
|
|
# (scripts/credential-change.py applier-apply under
|
|
# `warden access openbao-platform-admin-login --exec`). This replaces
|
|
# ops-mason-build (policies/retired/), whose policy-write + AppRole + token
|
|
# authority could be chained into a token carrying none of its denies.
|
|
#
|
|
# There is no write capability anywhere in this file except the two
|
|
# query-shaped endpoints (sys/capabilities*, revoke-self) that grant nothing.
|
|
# Granted through scripts/bao-session.sh as a 45-minute, named token.
|
|
#
|
|
# Path priority note: OpenBao uses the single most specific matching path
|
|
# within a policy, and a path with a `+` segment loses to one without. A deny
|
|
# on `auth/approle/role/+/secret-id/*` would therefore LOSE to a broad
|
|
# `auth/approle/role/*` grant. So AppRole is granted narrowly (list the roles,
|
|
# read `role/<name>` only) and the sub-endpoints are never granted at all; the
|
|
# explicit denies below are a second line, not the only one.
|
|
|
|
# --- survey: mounts, auth methods, policies ---------------------------------
|
|
# Phase 2 of the construction process. `bao secrets list` / `bao auth list`.
|
|
path "sys/mounts" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
path "sys/auth" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
# `bao policy list`.
|
|
path "sys/policies/acl" {
|
|
capabilities = ["list"]
|
|
}
|
|
|
|
# `bao policy read <name>` — policy text, never a secret value. read only:
|
|
# no create/update/delete, so this grant cannot write or remove any policy.
|
|
path "sys/policies/acl/*" {
|
|
capabilities = ["read"]
|
|
}
|
|
|
|
# --- survey: auth role definitions (read only) ------------------------------
|
|
# Kubernetes roles hold bindings and policy names, no credential material.
|
|
path "auth/kubernetes/role/*" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
# AppRole: list role names, read a role's definition. Deliberately not
|
|
# `auth/approle/role/*` — see the priority note above.
|
|
path "auth/approle/role" {
|
|
capabilities = ["list"]
|
|
}
|
|
|
|
path "auth/approle/role/+" {
|
|
capabilities = ["read"]
|
|
}
|
|
|
|
# OIDC/JWT roles used in this estate (netkingdom OIDC mount, jwt mount):
|
|
# bound claims, redirect URIs and policy names, no credential material.
|
|
path "auth/netkingdom/role/*" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
path "auth/jwt/role/*" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
# Service-JWT lane (CCR-2026-0038); role definitions only, never secret-ids or tokens.
|
|
path "auth/keycape-services/role/*" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
# --- survey: 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. Read/list only: this
|
|
# grant cannot describe, create or delete a path.
|
|
path "platform/metadata/*" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
path "operators/metadata/*" {
|
|
capabilities = ["read", "list"]
|
|
}
|
|
|
|
# --- verification -----------------------------------------------------------
|
|
# `bao token capabilities <path>` asks what a token can reach. The endpoints
|
|
# take a POST, hence update; they answer a question and grant nothing.
|
|
path "sys/capabilities" {
|
|
capabilities = ["update"]
|
|
}
|
|
|
|
path "sys/capabilities-self" {
|
|
capabilities = ["update"]
|
|
}
|
|
|
|
# The survey checks its own token's policies before running.
|
|
path "auth/token/lookup-self" {
|
|
capabilities = ["read"]
|
|
}
|
|
|
|
# End the grant early when the task is done.
|
|
path "auth/token/revoke-self" {
|
|
capabilities = ["update"]
|
|
}
|
|
|
|
# Not granted, on purpose: auth/token/create (no token minting of any kind),
|
|
# auth/token/revoke (other tokens), any auth/*/role write, sys/mounts/*.
|
|
|
|
# --- the grant cannot read or rewrite its own scope -------------------------
|
|
# It has no policy write at all, so this is belt and braces: if a write on
|
|
# sys/policies/acl/* is ever added here by mistake, this exact path still
|
|
# outranks the glob and the grant cannot edit its own denials.
|
|
path "sys/policies/acl/ops-mason-survey" {
|
|
capabilities = ["deny"]
|
|
}
|
|
|
|
# --- the AppRole escalation, stated as denials ------------------------------
|
|
# Reading a role-id or minting a secret-id is exactly how a role becomes a
|
|
# login. None of these is granted above; deny makes that explicit and also
|
|
# overrides any other policy attached to the same token.
|
|
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"]
|
|
}
|
|
|
|
# --- the line on secret values, stated as a denial --------------------------
|
|
# Explicit deny outranks any grant, including one added here later by mistake.
|
|
path "platform/data/*" {
|
|
capabilities = ["deny"]
|
|
}
|
|
|
|
path "operators/data/*" {
|
|
capabilities = ["deny"]
|
|
}
|
|
|
|
path "secret/data/*" {
|
|
capabilities = ["deny"]
|
|
}
|