Replace ops-mason-build with read-only ops-mason-survey grant (MASON-IN-0004)
Founder decision 2026-10-07, option (c)+(a): ops-mason surveys and verifies only. Policy/role writes, secret-id minting and token creation go through the founder-attended railiance-platform delegated applier. ops-mason-build retired to policies/retired/ (never installed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 132360@bnt-lap001 Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
This commit is contained in:
parent
47171385ce
commit
6b177270d3
10 changed files with 261 additions and 40 deletions
63
AGENTS.md
63
AGENTS.md
|
|
@ -261,21 +261,47 @@ it as `BAO_TOKEN=$(cat ~/.claude-bao-token) bao <cmd>`.
|
|||
|
||||
### What the grant authorises
|
||||
|
||||
Policy `ops-mason-build` (`policies/ops-mason-build.hcl`), 45 minutes, max one
|
||||
hour, named `claude-<task>`:
|
||||
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.
|
||||
|
||||
| Allowed | Denied |
|
||||
| Allowed | Denied / not granted |
|
||||
|---|---|
|
||||
| read `sys/mounts`, `sys/auth`, policy list — the phase-2 survey | **every read of `*/data/*`** on `platform`, `operators`, `secret` |
|
||||
| create/update policies under `sys/policies/acl/*` | enabling or tuning mounts (`sys/mounts/*`) — a railiance-platform act |
|
||||
| create/update `auth/kubernetes/role/*`, `auth/approle/role/*` | anything not listed |
|
||||
| read KV **metadata** — versions and timestamps, enough to confirm a delivery landed | KV **values** |
|
||||
| mint and revoke short-lived test tokens, check capabilities | |
|
||||
| read/list `sys/mounts`, `sys/auth`; list `sys/policies/acl`, read `sys/policies/acl/*` — the phase-2 survey | **any policy write** (no create/update/delete on `sys/policies/acl/*`; explicit deny on its own policy) |
|
||||
| read/list role definitions on `auth/kubernetes/role/*`, `auth/netkingdom/role/*`, `auth/jwt/role/*`; list `auth/approle/role`, read `auth/approle/role/<name>` | **any auth-role write**; AppRole `role-id`, `secret-id`, `secret-id/*`, `secret-id-accessor/*`, `custom-secret-id` (explicit deny) |
|
||||
| read/list KV **metadata** on `platform/`, `operators/` — enough to confirm a delivery landed | **every `*/data/*`** on `platform`, `operators`, `secret` (explicit deny); KV metadata writes |
|
||||
| `sys/capabilities`, `sys/capabilities-self` (query only), `auth/token/lookup-self`, `auth/token/revoke-self` | **token creation of any kind**; revoking other tokens; `sys/mounts/*` |
|
||||
|
||||
The denial is the point. `SCOPE.md` says ops-mason never touches secret values;
|
||||
this makes OpenBao enforce it rather than leaving it to whoever is driving. An
|
||||
explicit `deny` outranks any grant, including one added to this policy later by
|
||||
mistake.
|
||||
The grant can show what exists and prove what a token can reach; it cannot
|
||||
change anything. That removes the escalation MASON-IN-0004 described — write a
|
||||
policy without the denies, bind it to an AppRole, read the role-id, mint a
|
||||
secret-id, log in — because none of those steps is granted.
|
||||
|
||||
### Installing the survey policy (founder act)
|
||||
|
||||
ops-mason cannot install its own grant. The founder runs, silently and with an
|
||||
absolute path (the warden wrapper refuses output and runs with a temporary
|
||||
`HOME`):
|
||||
|
||||
```bash
|
||||
warden access openbao-platform-admin-login --exec -- sh -c \
|
||||
'bao policy write ops-mason-survey /home/worsch/ops-mason/policies/ops-mason-survey.hcl >/dev/null 2>&1'
|
||||
```
|
||||
|
||||
Silent check — the installed policy's hash into a 0600 receipt, compared with
|
||||
the repo file's hash outside the wrapper:
|
||||
|
||||
```bash
|
||||
warden access openbao-platform-admin-login --exec -- sh -c \
|
||||
'umask 077; mkdir -p /home/worsch/.local/state/railiance/receipts; bao policy read ops-mason-survey 2>/dev/null | sha256sum > /home/worsch/.local/state/railiance/receipts/ops-mason-survey.sha256'
|
||||
cat /home/worsch/.local/state/railiance/receipts/ops-mason-survey.sha256
|
||||
sha256sum < /home/worsch/ops-mason/policies/ops-mason-survey.hcl
|
||||
```
|
||||
|
||||
The two hashes match only if OpenBao returns the file byte-for-byte; if they
|
||||
differ, compare the stored text against the file before concluding the install
|
||||
is wrong (OpenBao may normalise a trailing newline).
|
||||
|
||||
### Choosing the task name
|
||||
|
||||
|
|
@ -286,8 +312,13 @@ recognise later.
|
|||
|
||||
### When a broader grant is genuinely needed
|
||||
|
||||
Enabling a mount, reading a value, or touching another subsystem's paths is out
|
||||
of `ops-mason-build` on purpose. Do not widen the policy to get past a refusal.
|
||||
Either the act belongs to another repo, or it needs its own named policy and its
|
||||
own decision — the way `operators/` did.
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -78,8 +78,11 @@ tooling_contacts:
|
|||
capability: "Create policies and auth roles for an approved construction plan (structure only, never secret values)"
|
||||
intended_owner: secrets-engine
|
||||
blocked_on: >-
|
||||
No engine exposes a policy / auth-role construction surface; ops-mason
|
||||
runs the bao CLI under a scoped, time-boxed ops-mason-build token.
|
||||
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.
|
||||
review: "2026-12-21"
|
||||
gap_state: accepted
|
||||
accepted_exception: *plan-approval-gate
|
||||
|
|
@ -164,7 +167,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 ops-mason-build token instead of the operator's session"
|
||||
capability: "Hand an agent session a task-scoped, read-only ops-mason-survey token instead of the operator's session"
|
||||
supplied_authority: none
|
||||
note: >-
|
||||
Runs under the operator's own authority and narrows it; ops-mason holds
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Generate the section rather than gathering it by hand (MASON-WP-0008). The
|
|||
founder mints the scoped grant; the agent then runs the survey with it:
|
||||
|
||||
```bash
|
||||
./scripts/bao-session.sh grant <task> # founder: ops-mason-build, 45m, claude-<task>
|
||||
./scripts/bao-session.sh grant <task> # founder: ops-mason-survey (read-only), 45m, claude-<task>
|
||||
./scripts/plan-survey.py \
|
||||
--kv-path platform/workloads/<consumer>/<name> \
|
||||
--policy workload-kv-read-<consumer>-<name> \
|
||||
|
|
@ -83,10 +83,10 @@ matching ops-warden catalog entries, a UTC timestamp, `BAO_ADDR`, the token
|
|||
draft answer into a decision; it is generated evidence, not a review.
|
||||
|
||||
The survey refuses to run without a grant, refuses a token whose policies
|
||||
are anything but `ops-mason-build` (plus `default`), and never prints the
|
||||
are anything but `ops-mason-survey` (plus `default`), and never prints the
|
||||
token. Every bao call passes an allowlist that rejects `kv get` and any
|
||||
`*/data/*` path before a process starts. A query the grant does not permit
|
||||
is reported as `not permitted by ops-mason-build` and listed under
|
||||
is reported as `not permitted by ops-mason-survey` and listed under
|
||||
"incomplete" rather than read as an empty answer. Policies with names that
|
||||
do not match the demand are not read, so a broadly named policy covering
|
||||
the path is not detected; the output says so.
|
||||
|
|
|
|||
|
|
@ -103,7 +103,8 @@ state_hub_intake_id: "01a06e68-8c29-7230-b0dc-88f1e374afa4"
|
|||
id: MASON-IN-0004
|
||||
kind: intake
|
||||
title: Close the policy-plus-AppRole escalation path in ops-mason-build
|
||||
status: open
|
||||
status: closed
|
||||
outcome: resolved
|
||||
origin: review
|
||||
origin_ref: inbound-library ILIB-WP-0003 (finding raised 2026-10-05 while preparing the first grant)
|
||||
priority: high
|
||||
|
|
@ -152,5 +153,23 @@ notes: >-
|
|||
A survey needs only (c)'s read-only grant. Today MASON-WP-0008 plan-survey runs
|
||||
under the full build grant, so separating them also reduces what a survey
|
||||
session can do.
|
||||
resolution: >-
|
||||
Founder decision (Bernd Worsch, 2026-10-07, given in the key-cape session):
|
||||
(c)+(a), a read-only grant only. ops-mason gets the read-only
|
||||
ops-mason-survey policy (policies/ops-mason-survey.hcl: read/list on mounts,
|
||||
auth methods, policies, auth role definitions and KV metadata; capability
|
||||
queries; lookup-self and revoke-self; explicit denies on */data/*, its own
|
||||
policy, and AppRole role-id / secret-id / secret-id-accessor /
|
||||
custom-secret-id). No policy writes, no auth-role writes, no secret-id
|
||||
minting, no token creation. Those acts go through the founder-attended
|
||||
railiance-platform delegated applier (scripts/credential-change.py
|
||||
applier-apply under warden access openbao-platform-admin-login --exec), as
|
||||
for CCR-2026-0033/0034 on 2026-10-07. ops-mason-build moved to
|
||||
policies/retired/ with a do-not-install header; it was not present in
|
||||
OpenBao at the time. bao-session.sh, plan-survey.py, custody-inventory.py,
|
||||
tests, AGENTS.md, INTENT.md and docs/construction-plan-format.md switched to
|
||||
ops-mason-survey. Installing the policy is a founder act (AGENTS.md,
|
||||
"Installing the survey policy"). Commit: COMMIT_PENDING.
|
||||
updated: '2026-10-07T00:00:00Z'
|
||||
state_hub_intake_id: "01a11118-1f35-7ad5-ad51-65dd785c932f"
|
||||
```
|
||||
|
|
|
|||
150
policies/ops-mason-survey.hcl
Normal file
150
policies/ops-mason-survey.hcl
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# 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"]
|
||||
}
|
||||
|
||||
# --- 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"]
|
||||
}
|
||||
|
|
@ -1,3 +1,19 @@
|
|||
# RETIRED — DO NOT INSTALL.
|
||||
#
|
||||
# Retired 2026-10-07 by founder decision (Bernd Worsch) under MASON-IN-0004,
|
||||
# option (c)+(a): ops-mason holds only the read-only ops-mason-survey grant
|
||||
# (policies/ops-mason-survey.hcl). This policy could write ACL policies and
|
||||
# AppRole roles and mint tokens; together those let a holder create a policy
|
||||
# without its denies, bind it to an AppRole, read the role-id, mint a secret-id
|
||||
# and log in as a token that carries none of the denies below. Policy writes,
|
||||
# auth-role writes, secret-id minting and token creation now go through the
|
||||
# founder-attended railiance-platform delegated applier
|
||||
# (scripts/credential-change.py applier-apply under
|
||||
# `warden access openbao-platform-admin-login --exec`).
|
||||
#
|
||||
# Kept for the record only. It was never present in OpenBao at retirement
|
||||
# (bao-session.sh grant reported "does not exist yet" on 2026-10-07).
|
||||
#
|
||||
# ops-mason-build — the authority ops-mason actually needs, and no more.
|
||||
#
|
||||
# SCOPE.md says ops-mason "never touches secret values, even transiently". Until
|
||||
|
|
@ -12,10 +12,12 @@
|
|||
# for as long as you have it, indistinguishable from you in the audit log. The
|
||||
# grant path instead mints a separate token that is:
|
||||
#
|
||||
# * scoped — carries the ops-mason-build policy, which can create policies,
|
||||
# auth roles and KV *structure* but cannot read secret values.
|
||||
# SCOPE.md's "never touch secret values" stops being a promise in
|
||||
# a document and becomes something OpenBao enforces.
|
||||
# * scoped — carries the read-only ops-mason-survey policy: it can list
|
||||
# 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.
|
||||
# * 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.
|
||||
|
|
@ -30,7 +32,7 @@ set -euo pipefail
|
|||
|
||||
export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}"
|
||||
GRANT_FILE="${GRANT_FILE:-$HOME/.claude-bao-token}"
|
||||
GRANT_POLICY="${GRANT_POLICY:-ops-mason-build}"
|
||||
GRANT_POLICY="${GRANT_POLICY:-ops-mason-survey}"
|
||||
GRANT_TTL="${GRANT_TTL:-45m}"
|
||||
GRANT_MAX_TTL="${GRANT_MAX_TTL:-1h}"
|
||||
OIDC_PATH="${OIDC_PATH:-netkingdom}"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
./scripts/custody-inventory.py operators one mount or prefix
|
||||
./scripts/custody-inventory.py --undescribed only paths missing metadata
|
||||
|
||||
Reads metadata only — never a value — so it runs under ops-mason-build and can
|
||||
Reads metadata only — never a value — so it runs under ops-mason-survey and can
|
||||
be handed to anyone orienting themselves without granting them a single secret.
|
||||
|
||||
A path with no description is a finding, not a formatting problem: it is a
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ auth-role listings, KV metadata existence for the target path, matching
|
|||
ops-warden routing catalog entries, and a draft answer to "does an existing lane
|
||||
already satisfy this demand?".
|
||||
|
||||
Metadata only. It runs under the ops-mason-build grant (~/.claude-bao-token),
|
||||
Metadata only. It runs under the ops-mason-survey grant (~/.claude-bao-token),
|
||||
refuses any other token, and every bao invocation passes `allowed_bao_call`, an
|
||||
allowlist that rejects `kv get` and every `*/data/*` path before a process starts.
|
||||
Policy bodies are read with `bao policy read` (policy text, never a secret value)
|
||||
|
|
@ -33,11 +33,11 @@ from pathlib import Path
|
|||
from typing import Callable
|
||||
|
||||
GRANT_FILE = "~/.claude-bao-token"
|
||||
GRANT_POLICY = "ops-mason-build"
|
||||
GRANT_POLICY = "ops-mason-survey"
|
||||
DEFAULT_ADDR = "http://127.0.0.1:18200"
|
||||
DEFAULT_CATALOG = "~/ops-warden/registry/routing/catalog.yaml"
|
||||
MAX_POLICY_READS = 15
|
||||
NOT_PERMITTED = "not permitted by ops-mason-build"
|
||||
NOT_PERMITTED = "not permitted by ops-mason-survey"
|
||||
# Path segments too generic to identify a consumer.
|
||||
GENERIC = {"platform", "workloads", "operators", "secret", "data", "metadata",
|
||||
"kv", "read", "workload", "role", "token", "auth", "policy"}
|
||||
|
|
@ -133,7 +133,7 @@ def grant_env(environ: dict | None = None) -> dict:
|
|||
with open(grant) as f:
|
||||
env["BAO_TOKEN"] = f.read().strip()
|
||||
if not env.get("BAO_TOKEN"):
|
||||
raise SurveyError("no ops-mason-build grant (~/.claude-bao-token absent and BAO_TOKEN unset); "
|
||||
raise SurveyError("no ops-mason-survey grant (~/.claude-bao-token absent and BAO_TOKEN unset); "
|
||||
"ask the founder to run ./scripts/bao-session.sh grant <task>")
|
||||
return env
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ path "platform/metadata/workloads/inbound-library/imap" {
|
|||
'''
|
||||
|
||||
|
||||
def lookup(policies=("default", "ops-mason-build")):
|
||||
def lookup(policies=("default", "ops-mason-survey")):
|
||||
return {"data": {"id": TOKEN, "accessor": "acc-SECRET", "display_name": "token-claude-mason-wp-0008",
|
||||
"policies": list(policies), "ttl": 2000}}
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ class FakeBao:
|
|||
"sys/": {"type": "system"}},
|
||||
("auth", "list"): {"approle/": {"type": "approle"}, "kubernetes/": {"type": "kubernetes"},
|
||||
"token/": {"type": "token"}},
|
||||
("policy", "list"): ["default", "root", "ops-mason-build", "workload-kv-read-inbound-library-imap",
|
||||
("policy", "list"): ["default", "root", "ops-mason-survey", "workload-kv-read-inbound-library-imap",
|
||||
"workload-kv-read-other"],
|
||||
("list", "auth/approle/role"): ["inbound-library", "rein-openweights"],
|
||||
("list", "auth/kubernetes/role"): ["external-secrets-activity-core"],
|
||||
|
|
@ -149,20 +149,20 @@ def test_missing_capability_degrades_and_is_reported(catalog, capsys):
|
|||
("policy", "read", "workload-kv-read-inbound-library-imap")})
|
||||
rc, out = run(fake, catalog, capsys)
|
||||
assert rc == 0
|
||||
assert "not permitted by ops-mason-build" in out
|
||||
assert "not permitted by ops-mason-survey" in out
|
||||
assert "incomplete:" in out
|
||||
|
||||
|
||||
def test_no_grant_fails_loudly(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(survey.os.path, "expanduser", lambda p: str(tmp_path / "absent"))
|
||||
with pytest.raises(survey.SurveyError, match="no ops-mason-build grant"):
|
||||
with pytest.raises(survey.SurveyError, match="no ops-mason-survey grant"):
|
||||
survey.main(ARGS, runner=FakeBao(), environ={})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policies", [("root",), ("default", "ops-mason-build", "platform-admin"), ("default",)])
|
||||
@pytest.mark.parametrize("policies", [("root",), ("default", "ops-mason-survey", "platform-admin"), ("default",)])
|
||||
def test_non_grant_token_is_refused(catalog, policies):
|
||||
fake = FakeBao(table={("token", "lookup"): lookup(policies)})
|
||||
with pytest.raises(survey.SurveyError, match="not an ops-mason-build grant") as exc:
|
||||
with pytest.raises(survey.SurveyError, match="not an ops-mason-survey grant") as exc:
|
||||
survey.main([*ARGS, "--catalog", str(catalog)], runner=fake, environ={"BAO_TOKEN": TOKEN})
|
||||
assert TOKEN not in str(exc.value)
|
||||
assert len(fake.calls) == 1
|
||||
|
|
@ -182,7 +182,7 @@ def test_script_exits_nonzero_without_grant(tmp_path):
|
|||
p = subprocess.run([sys.executable, str(Path(__file__).parents[1] / "scripts/plan-survey.py"), *ARGS],
|
||||
capture_output=True, text=True, env=env)
|
||||
assert p.returncode == 2
|
||||
assert "ERROR: no ops-mason-build grant" in p.stderr
|
||||
assert "ERROR: no ops-mason-survey grant" in p.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("args", [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue