MASON-IN-0005: restore ops-mason as the OpenBao builder (ops-mason-construct)
Founder decision 2026-10-18, "build, but never log in": a plan-scoped grant
that writes workload-kv-read-* policies, external-secrets-* and AppRole role
definitions, and create-only placeholder KV under platform/{workloads,suppliers},
and denies role-id, secret-id, token creation and all value reads, so the
MASON-IN-0004 chain stays broken. Adds bao-session.sh build, policy tests,
INTENT/AGENTS alignment and MASON-WP-0015. Not installed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 278674@bnt-lap001
Assistant-Session: 64827407-e196-4ce3-b70c-76e7b537493f
This commit is contained in:
parent
e2ac6448c2
commit
e7a368af0f
7 changed files with 406 additions and 21 deletions
49
AGENTS.md
49
AGENTS.md
|
|
@ -285,9 +285,9 @@ it as `BAO_TOKEN=$(cat ~/.claude-bao-token) bao <cmd>`.
|
|||
### What the grant authorises
|
||||
|
||||
Policy `ops-mason-survey` (`policies/ops-mason-survey.hcl`), 45 minutes, max
|
||||
one hour, named `claude-<task>`. Read-only, by founder decision 2026-10-07
|
||||
(MASON-IN-0004, option (c)+(a)); it replaces `ops-mason-build`, now in
|
||||
`policies/retired/` and never to be installed.
|
||||
one hour, named `claude-<task>`. Read-only (MASON-IN-0004, 2026-10-07); used
|
||||
for surveys. Building uses the separate `build` grant below (MASON-IN-0005).
|
||||
`ops-mason-build` stays in `policies/retired/`, never to be installed.
|
||||
|
||||
| Allowed | Denied / not granted |
|
||||
|---|---|
|
||||
|
|
@ -333,15 +333,40 @@ action to a piece of work rather than to a person. Use the workplan task where
|
|||
there is one — `grant MASON-WP-0003-T02` — otherwise something a reader would
|
||||
recognise later.
|
||||
|
||||
### The build grant (MASON-IN-0005)
|
||||
|
||||
ops-mason is the builder. After a plan is approved, the founder mints the
|
||||
build grant:
|
||||
|
||||
```bash
|
||||
./scripts/bao-session.sh build <plan-task> # ops-mason-construct, 45m, claude-<task>
|
||||
```
|
||||
|
||||
Policy `ops-mason-construct` (`policies/ops-mason-construct.hcl`). It
|
||||
includes everything the survey grant can do, plus:
|
||||
|
||||
| Allowed | Still denied |
|
||||
|---|---|
|
||||
| create/update/delete `sys/policies/acl/workload-kv-read-*` | every other policy; `default`, `root`, `ops-mason-*` (explicit deny) |
|
||||
| create/update/delete `auth/kubernetes/role/external-secrets-*` and AppRole role definitions, with `token_policies` limited to `workload-kv-read-*` | AppRole `role-id`, `secret-id*`, `custom-secret-id`; `auth/token/create*` |
|
||||
| `create` (no read or update) on `platform/data/{workloads,suppliers}/*`, so placeholders only | reading any `*/data/*`; overwriting an existing value |
|
||||
| full metadata on `platform/metadata/{workloads,suppliers}/*` (describe, tear down) | `sys/mounts/*`, `sys/auth/*`, `operators/` and `secret/` writes |
|
||||
|
||||
The grant can build a lane but can never log in as one, which keeps the
|
||||
MASON-IN-0004 chain broken. AppRole secret-id delivery stays a founder act;
|
||||
Kubernetes-auth lanes need no secret-id. After every build, compare the
|
||||
written policy text with the plan's policy file (MASON-WP-0015-T04).
|
||||
|
||||
Install it the same way as the survey policy, with `ops-mason-construct` and
|
||||
its file path.
|
||||
|
||||
### When a broader grant is genuinely needed
|
||||
|
||||
Writing a policy or auth role, minting a secret-id or a token, enabling a
|
||||
mount, reading a value, or touching another subsystem's paths is out of
|
||||
`ops-mason-survey` on purpose. Do not widen the policy to get past a refusal.
|
||||
Builds that need writes 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`), as
|
||||
for CCR-2026-0033/0034; ops-mason supplies the plan and verifies afterwards.
|
||||
Anything else belongs to another repo or needs its own named policy and its
|
||||
own founder decision.
|
||||
Minting a secret-id or a token, enabling a mount, reading a value, a policy
|
||||
outside `workload-kv-read-*`, or another subsystem's paths is outside both
|
||||
grants on purpose. Do not widen a policy to get past a refusal. Those acts go
|
||||
through the founder and the railiance-platform delegated applier
|
||||
(`scripts/credential-change.py applier-apply` under `warden access
|
||||
openbao-platform-admin-login --exec`), or need their own named policy and
|
||||
founder decision.
|
||||
|
||||
|
|
|
|||
17
INTENT.md
17
INTENT.md
|
|
@ -79,10 +79,12 @@ tooling_contacts:
|
|||
intended_owner: secrets-engine
|
||||
blocked_on: >-
|
||||
No engine exposes a policy / auth-role construction surface. Since
|
||||
MASON-IN-0004 (founder, 2026-10-07) ops-mason holds no build grant:
|
||||
these writes are applied by the founder through the railiance-platform
|
||||
delegated applier; ops-mason surveys and verifies under the read-only
|
||||
ops-mason-survey token.
|
||||
MASON-IN-0005 (founder, 2026-10-18) ops-mason builds these writes itself
|
||||
under the founder-minted, plan-scoped ops-mason-construct token:
|
||||
workload-kv-read-* policies, external-secrets-* Kubernetes roles, AppRole
|
||||
role definitions, and placeholder KV structure. It can never read a
|
||||
role-id, mint a secret-id or token, or read a value (MASON-IN-0004 stays
|
||||
closed). Survey-only sessions use ops-mason-survey.
|
||||
review: "2026-12-21"
|
||||
gap_state: accepted
|
||||
accepted_exception: *plan-approval-gate
|
||||
|
|
@ -97,7 +99,10 @@ tooling_contacts:
|
|||
intended_owner: secrets-engine
|
||||
blocked_on: >-
|
||||
No engine exposes credential issuance for AppRole lanes; secrets-engine
|
||||
owns credential lifecycle but fronts no issuance API today.
|
||||
owns credential lifecycle but fronts no issuance API today. Since
|
||||
MASON-IN-0005 this step is a founder act: ops-mason-construct denies
|
||||
role-id and secret-id, so ops-mason prepares the delivery and the founder
|
||||
runs it. Kubernetes-auth lanes need no such step.
|
||||
review: "2026-12-21"
|
||||
gap_state: accepted
|
||||
accepted_exception: *plan-approval-gate
|
||||
|
|
@ -167,7 +172,7 @@ tooling_contacts:
|
|||
tooling: OpenBao
|
||||
operation: "Operator OIDC login; mint a scoped, bounded token under the operator's own session"
|
||||
write: true
|
||||
capability: "Hand an agent session a task-scoped, read-only ops-mason-survey token instead of the operator's session"
|
||||
capability: "Hand an agent session a task-scoped token instead of the operator's session: read-only ops-mason-survey, or plan-scoped ops-mason-construct after approval (MASON-IN-0005)"
|
||||
supplied_authority: none
|
||||
note: >-
|
||||
Runs under the operator's own authority and narrows it; ops-mason holds
|
||||
|
|
|
|||
|
|
@ -189,3 +189,41 @@ installed: >-
|
|||
A follow-up read shows ops-mason-build absent and ops-mason-survey present.
|
||||
state_hub_intake_id: "01a11118-1f35-7ad5-ad51-65dd785c932f"
|
||||
```
|
||||
|
||||
## MASON-IN-0005 — Restore ops-mason as the OpenBao builder without reopening MASON-IN-0004
|
||||
|
||||
```yaml
|
||||
id: MASON-IN-0005
|
||||
kind: intake
|
||||
title: Restore ops-mason as the OpenBao builder without reopening MASON-IN-0004
|
||||
status: open
|
||||
origin: review
|
||||
origin_ref: MASON-IN-0004; INTENT.md review of ops-mason and ops-warden, 2026-10-18
|
||||
priority: high
|
||||
owner: ops-mason
|
||||
requested_by: founder
|
||||
description: >-
|
||||
Founder, 2026-10-18: ops-mason exists to set up and organize secrets
|
||||
structure in OpenBao systematically; ops-warden finds and provides it. The
|
||||
read-only resolution of MASON-IN-0004 returned every build to the founder,
|
||||
which contradicts ops-mason INTENT.md (phase 4, success criterion 1: no
|
||||
founder hand-running bao) and leaves ops-warden's INTENT (route and assist,
|
||||
ops-mason the peer lane owner that builds) with nothing built to route to.
|
||||
Both INTENT bodies already agree; the grant and the INTENT frontmatter
|
||||
blocked_on text do not.
|
||||
resolution: >-
|
||||
Founder decision (Bernd Worsch, 2026-10-18, this session): "Build, but
|
||||
never log in". New policy policies/ops-mason-construct.hcl: create, update and
|
||||
delete workload-kv-read-* policies; external-secrets-* Kubernetes auth roles
|
||||
and AppRole role definitions limited to workload-kv-read-* token policies;
|
||||
create-only placeholder KV data and full metadata under
|
||||
platform/{workloads,suppliers}/; explicit denies on role-id, secret-id,
|
||||
token creation, every */data/* read, and the default, root and ops-mason-* policies.
|
||||
ops-mason can build a lane but never log in as one, which breaks the
|
||||
MASON-IN-0004 chain at step three. AppRole secret-id delivery stays a
|
||||
founder act; Kubernetes auth (the current default) needs none. A post-build
|
||||
check comparing policy text with the plan file is the detective control for
|
||||
over-broad content. ops-mason-survey stays for survey-only sessions.
|
||||
Implementation and install: MASON-WP-0015.
|
||||
updated: '2026-10-18T00:00:00Z'
|
||||
```
|
||||
|
|
|
|||
133
policies/ops-mason-construct.hcl
Normal file
133
policies/ops-mason-construct.hcl
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# 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 <plan-task>
|
||||
# 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 glob support on list-valued parameters must be
|
||||
# confirmed by the negative tests in MASON-WP-0015-T03 before this is relied on.
|
||||
|
||||
# --- 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" = []
|
||||
}
|
||||
}
|
||||
|
||||
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" = []
|
||||
}
|
||||
}
|
||||
|
||||
# --- 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.
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
# ./scripts/bao-session.sh status what session exists, if any
|
||||
# ./scripts/bao-session.sh login refresh YOUR operator session (OIDC)
|
||||
# ./scripts/bao-session.sh grant <task> mint a scoped, time-boxed token for Claude
|
||||
# ./scripts/bao-session.sh build <task> same, with ops-mason-construct (approved plan only)
|
||||
# ./scripts/bao-session.sh revoke end the granted token immediately
|
||||
#
|
||||
# Why `grant` exists
|
||||
|
|
@ -16,8 +17,12 @@
|
|||
# and read mounts, auth methods, policies, auth role definitions
|
||||
# and KV *metadata*, and query capabilities. It cannot write a
|
||||
# policy or role, mint a secret-id or a token, or read a secret
|
||||
# value (MASON-IN-0004, founder decision 2026-10-07). Builds go
|
||||
# through the founder-attended railiance-platform applier.
|
||||
# value (MASON-IN-0004, founder decision 2026-10-07).
|
||||
# `build` carries ops-mason-construct instead (MASON-IN-0005,
|
||||
# 2026-10-18): it writes workload-kv-read-* policies, ESO/AppRole
|
||||
# role definitions and placeholder KV structure, and still cannot
|
||||
# read a role-id, mint a secret-id or token, or read a value.
|
||||
# Mint it only after the plan is approved.
|
||||
# * bounded — 45 minutes, no renewal past one hour.
|
||||
# * named — display_name claude-<task>, so the audit log says which task an
|
||||
# action belonged to, not just that platform-root did it.
|
||||
|
|
@ -130,6 +135,7 @@ case "${1:-status}" in
|
|||
status) cmd_status ;;
|
||||
login) cmd_login ;;
|
||||
grant) shift; cmd_grant "${1:-}" ;;
|
||||
build) shift; GRANT_POLICY=ops-mason-construct; cmd_grant "${1:-}" ;;
|
||||
revoke) cmd_revoke ;;
|
||||
*) echo "usage: $0 status|login|grant <task>|revoke" >&2; exit 2 ;;
|
||||
*) echo "usage: $0 status|login|grant <task>|build <task>|revoke" >&2; exit 2 ;;
|
||||
esac
|
||||
|
|
|
|||
68
tests/test_construct_policy.py
Normal file
68
tests/test_construct_policy.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""ops-mason-construct can build a lane but never log in as one (MASON-IN-0005)."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
POLICY = Path(__file__).resolve().parents[1] / "policies" / "ops-mason-construct.hcl"
|
||||
|
||||
|
||||
def _stanzas() -> dict[str, str]:
|
||||
text = "\n".join(line.split("#", 1)[0] for line in POLICY.read_text().splitlines())
|
||||
return {
|
||||
m.group(1): m.group(2)
|
||||
for m in re.finditer(r'path\s+"([^"]+)"\s*\{(.*?)\n?\s*\}\s*(?=path|\Z)', text, re.S)
|
||||
}
|
||||
|
||||
|
||||
def _caps(body: str) -> set[str]:
|
||||
m = re.search(r"capabilities\s*=\s*\[([^\]]*)\]", body)
|
||||
return set(re.findall(r'"(\w+)"', m.group(1))) if m else set()
|
||||
|
||||
|
||||
def test_login_and_value_paths_are_denied():
|
||||
stanzas = _stanzas()
|
||||
for path in [
|
||||
"auth/approle/role/+/role-id",
|
||||
"auth/approle/role/+/secret-id",
|
||||
"auth/approle/role/+/secret-id/*",
|
||||
"auth/approle/role/+/custom-secret-id",
|
||||
"auth/token/create",
|
||||
"auth/token/create/*",
|
||||
"auth/token/create-orphan",
|
||||
"platform/data/*",
|
||||
"operators/data/*",
|
||||
"secret/data/*",
|
||||
"sys/policies/acl/ops-mason-construct",
|
||||
"sys/policies/acl/ops-mason-survey",
|
||||
"sys/policies/acl/default",
|
||||
"sys/policies/acl/root",
|
||||
]:
|
||||
assert _caps(stanzas[path]) == {"deny"}, path
|
||||
|
||||
|
||||
def test_kv_data_is_create_only():
|
||||
stanzas = _stanzas()
|
||||
for path in ["platform/data/workloads/*", "platform/data/suppliers/*"]:
|
||||
assert _caps(stanzas[path]) == {"create"}, path
|
||||
for path, body in stanzas.items():
|
||||
if "/data/" in path:
|
||||
assert not _caps(body) & {"read", "update", "list"}, path
|
||||
|
||||
|
||||
def test_policy_writes_limited_to_workload_read_prefix():
|
||||
for path, body in _stanzas().items():
|
||||
if path.startswith("sys/policies/acl/") and _caps(body) & {"create", "update", "delete"}:
|
||||
assert path == "sys/policies/acl/workload-kv-read-*", path
|
||||
|
||||
|
||||
def test_role_writes_restrict_token_policies():
|
||||
for path, body in _stanzas().items():
|
||||
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
|
||||
|
||||
|
||||
def test_no_mount_or_auth_method_writes():
|
||||
for path, body in _stanzas().items():
|
||||
if path.startswith(("sys/mounts/", "sys/auth/")):
|
||||
assert _caps(body) <= {"read", "list", "deny"}, path
|
||||
110
workplans/MASON-WP-0015-construct-grant.md
Normal file
110
workplans/MASON-WP-0015-construct-grant.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
id: MASON-WP-0015
|
||||
type: workplan
|
||||
title: "Restore ops-mason as the OpenBao builder: the ops-mason-construct grant"
|
||||
domain: infotech
|
||||
repo: ops-mason
|
||||
status: active
|
||||
owner: claude
|
||||
topic_slug: netkingdom-security-architecture
|
||||
created: "2026-10-18"
|
||||
updated: "2026-10-18"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
---
|
||||
|
||||
# ops-mason-construct grant
|
||||
|
||||
Decision: `intakes/intakes.md` MASON-IN-0005 (founder, 2026-10-18, "Build, but
|
||||
never log in"). ops-mason builds structure in OpenBao; ops-warden finds and
|
||||
provides it. The grant breaks the MASON-IN-0004 chain at the login step.
|
||||
|
||||
## Review both INTENTs and draft the grant
|
||||
|
||||
```task
|
||||
id: MASON-WP-0015-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Reviewed ops-mason and ops-warden INTENT.md. The bodies agree (ops-mason
|
||||
builds, ops-warden routes and assists); the read-only grant did not. Wrote
|
||||
`policies/ops-mason-construct.hcl` and `tests/test_construct_policy.py`, added
|
||||
`bao-session.sh build <task>`, and updated INTENT.md frontmatter and the
|
||||
AGENTS.md OpenBao section.
|
||||
|
||||
## Install the policy (founder act)
|
||||
|
||||
```task
|
||||
id: MASON-WP-0015-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Same silent wrapper and hash receipt as the survey policy (AGENTS.md), with
|
||||
`ops-mason-construct` and
|
||||
`/home/worsch/ops-mason/policies/ops-mason-construct.hcl`. Confirm your OIDC
|
||||
session can mint a token carrying it (`bao-session.sh build MASON-WP-0015-T03`).
|
||||
|
||||
## Live negative and positive tests
|
||||
|
||||
```task
|
||||
id: MASON-WP-0015-T03
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Under the build grant, against throwaway names (`workload-kv-read-mason-probe`,
|
||||
`external-secrets-mason-probe`, `platform/suppliers/mason/probe`): policy and
|
||||
role create/delete succeed; a role with `token_policies=default`, or with
|
||||
legacy `policies`, is refused (this confirms glob support in
|
||||
`allowed_parameters`); role-id read, secret-id write, token create, data read,
|
||||
and a write over the existing probe path are all refused; a policy write
|
||||
outside `workload-kv-read-*` is refused. Record the results as a
|
||||
names-only receipt. If the glob check fails, stop and redesign the role
|
||||
stanzas before any build.
|
||||
|
||||
## Executor and post-build check
|
||||
|
||||
```task
|
||||
id: MASON-WP-0015-T04
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`src/ops_mason/executor.py` `build_approle_kv_lane` mints the role-id and
|
||||
secret-id itself (lines ~420-426). Split it so that it stops after the role
|
||||
definition and emits the founder delivery step. Add a post-build check that
|
||||
reads each written policy and compares it with the plan's policy file
|
||||
(normalising the trailing newline). Run the check in every build; a mismatch
|
||||
fails the build record.
|
||||
|
||||
## Align ops-warden and railiance-platform references
|
||||
|
||||
```task
|
||||
id: MASON-WP-0015-T05
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
ops-warden `registry/routing/catalog.yaml` and
|
||||
`wiki/playbooks/openbao-structure-survey.md` describe ops-mason as read-only.
|
||||
The railiance-platform references are in
|
||||
`docs/credential-lane-designs/application-backup-delivery-2026-10-08.md` and
|
||||
RPF-WP-0053 and RPF-WP-0057. Message each owner with MASON-IN-0005, pointers
|
||||
only. They edit their own files.
|
||||
|
||||
## Close the Kubernetes variant
|
||||
|
||||
```task
|
||||
id: MASON-WP-0015-T06
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
A Kubernetes role ops-mason builds can be logged in to by anyone holding a
|
||||
token for the bound ServiceAccount. Confirm that the kube identity ops-mason
|
||||
applies with cannot `create serviceaccounts/token` or Pods in namespaces it
|
||||
binds roles to (`kubectl auth can-i`). `kubernetes_plane` already refuses
|
||||
the Pod and Secret kinds. Record the result in INTENT.md's
|
||||
`kubernetes-plane-apply` entry.
|
||||
Loading…
Add table
Add a link
Reference in a new issue