ops-mason/policies/ops-mason-survey.hcl
tegwick 2ef0101412 WP-0009-T05 invariant (ii) verified; survey policy covers keycape-services roles; Forgejo plan re-rendered
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
2026-10-10 14:13:02 +02:00

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"]
}