Advance dependency roots with guarded source implementations
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a10391-6355-7b41-b0fa-3e14e874f091
This commit is contained in:
parent
20eef043ed
commit
9d10fba4cb
5 changed files with 263 additions and 8 deletions
|
|
@ -98,7 +98,7 @@ one, per `MASON-WP-0001`'s own scoping.
|
|||
|
||||
## Binding OpenBao approval to execution inputs
|
||||
|
||||
The AppRole and Kubernetes-auth builders additionally require two frontmatter
|
||||
The AppRole, Kubernetes-auth and attended OIDC matrix builders require two frontmatter
|
||||
fields: `build_spec` and `approved_spec_sha256`. `build_spec` is the complete
|
||||
value-free output of `ops_mason.executor.build_spec_document(spec)`. It includes
|
||||
the engine, exact policy/role/path and identity bindings, token bounds, policy
|
||||
|
|
@ -148,8 +148,8 @@ by this format migration.
|
|||
|
||||
The existing AppRole engine creates only exact KV-v2 read lanes: one literal
|
||||
entry path, `read` on data and metadata, no wildcard, empty/parent path segment,
|
||||
HCL injection, or write capability. OIDC matrix writers remain a separate,
|
||||
blocked contract under MASON-WP-0005. Role and policy names must be single
|
||||
HCL injection, or write capability. The separate attended OIDC matrix engine is implemented under MASON-WP-0005;
|
||||
its live construction remains blocked on identity, platform review and plan approval. Role and policy names must be single
|
||||
literal names; Kubernetes service-account names/namespaces must be explicit,
|
||||
nonempty, unique DNS labels. Token use counts must be explicit nonnegative
|
||||
integers (zero is deliberately unlimited when approved); TTLs are positive
|
||||
|
|
@ -185,3 +185,37 @@ Do not blindly retry or delete them: inspect structure and audit metadata,
|
|||
resolve/revoke any partial issuance through the scoped custody procedure, and
|
||||
review a fresh delivery destination before retrying. The executor does not
|
||||
claim transactional rollback or automatically widen its revocation authority.
|
||||
|
||||
|
||||
### Attended OIDC operator matrix
|
||||
|
||||
`OIDCKVSpec` and `build_oidc_kv_lane` implement the ops-mason side of the
|
||||
fluid-telegram four-entry matrix. Draft and review `build_spec_document(spec)`
|
||||
and its digest through the same exact-spec approval process. The spec requires
|
||||
campaign coordinates, one owner-confirmed group, identity-confirmation and
|
||||
platform-matrix-review references, explicit loopback CLI callbacks, policy/role
|
||||
names and token bounds. These references are review inputs, not authorization
|
||||
tokens or proof that an owner accepted a draft.
|
||||
|
||||
The renderer emits four literal KV-v2 data stanzas: operator-app read;
|
||||
operator-session and bot-token create/read/update; redaction-salt create/read.
|
||||
It adds only the exact token revoke-self update endpoint. There are no metadata,
|
||||
parent, sibling, wildcard, deletion or salt-update grants. The engine writes
|
||||
policy/auth-role structure only; it never reads/writes KV values or issues a
|
||||
login token. The approved role has one bound group, no default policy, explicit
|
||||
15-minute initial / 30-minute maximum lifetime caps and non-periodic tokens.
|
||||
|
||||
Initial callback support is deliberately limited to owner-reviewed
|
||||
`http://localhost:8250/oidc/callback` and
|
||||
`http://127.0.0.1:8250/oidc/callback`. A private UI callback or additional MFA
|
||||
claim shape requires matching owner-confirmed contract work before extending
|
||||
this engine. A group parameter or a local test does not establish installed
|
||||
issuer/MFA assurance. MASON-WP-0005-T01/T03/T05 keep those live obligations.
|
||||
|
||||
On failure the engine suppresses Bao output and does not claim a successful
|
||||
audit receipt. It can leave a written policy if the auth-role write fails;
|
||||
inspect object/audit metadata and use the approved rollback procedure before a
|
||||
retry. Metadata-only local tests do not prove installed KV create/update/CAS
|
||||
behavior or agent-policy effective denial. Those remain native owner checks.
|
||||
Platform CCR request-type support and review remain a separate T02 dependency;
|
||||
the generic read-lane CCR renderer still must refuse this write matrix.
|
||||
|
|
|
|||
19
docs/evidence/2026-10-04-operator-matrix-source.json
Normal file
19
docs/evidence/2026-10-04-operator-matrix-source.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"workplan": "MASON-WP-0005",
|
||||
"task": "T02",
|
||||
"status": "progress",
|
||||
"actual_source_tests": 92,
|
||||
"elapsed_seconds": 1.33,
|
||||
"engine": "openbao-oidc-kv-read-write",
|
||||
"execution": "mock subprocess only; no OpenBao calls",
|
||||
"platform_ccr_extension": "not implemented; pending owner support/review",
|
||||
"identity": "unconfirmed live group/MFA/callbacks remain T01",
|
||||
"construction_plan": "draft unchanged",
|
||||
"remaining": [
|
||||
"platform contract support/review",
|
||||
"confirmed IAM claim and assurance",
|
||||
"live survey",
|
||||
"exact construction approval",
|
||||
"native effective-policy and salt/CAS proof"
|
||||
]
|
||||
}
|
||||
|
|
@ -76,6 +76,107 @@ class KubernetesKVSpec:
|
|||
bao_bin: str = "bao"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OIDCKVSpec:
|
||||
"""Attended four-entry operator matrix; all identity evidence is reviewed input."""
|
||||
policy_name: str
|
||||
role_name: str
|
||||
campaign_path: str
|
||||
bound_group: str
|
||||
allowed_redirect_uris: tuple[str, ...]
|
||||
identity_confirmation_ref: str
|
||||
platform_matrix_review_ref: str
|
||||
auth_mount: str = "netkingdom"
|
||||
token_ttl: str = "15m"
|
||||
token_max_ttl: str = "30m"
|
||||
bao_bin: str = "bao"
|
||||
audit_log_path: Path | None = None
|
||||
|
||||
|
||||
_OPERATOR_MATRIX = (
|
||||
("operator-app", ("read",)),
|
||||
("operator-session", ("create", "read", "update")),
|
||||
("bot-token", ("create", "read", "update")),
|
||||
("redaction-salt", ("create", "read")),
|
||||
)
|
||||
|
||||
|
||||
def oidc_policy_hcl(spec: OIDCKVSpec) -> str:
|
||||
"""Render exact KV data paths plus self-revocation; no metadata grants."""
|
||||
_validate_oidc_spec(spec)
|
||||
mount, rest = spec.campaign_path.split("/", 1)
|
||||
stanzas = []
|
||||
for entry, caps in _OPERATOR_MATRIX:
|
||||
encoded = ", ".join(json.dumps(c) for c in caps)
|
||||
stanzas.append(f'path "{mount}/data/{rest}/{entry}" {{\n capabilities = [{encoded}]\n}}\n')
|
||||
stanzas.append('path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n')
|
||||
return "\n".join(stanzas)
|
||||
|
||||
|
||||
def _validate_oidc_spec(spec: OIDCKVSpec) -> None:
|
||||
from urllib.parse import urlsplit
|
||||
for value, label in ((spec.role_name, "OIDC role"), (spec.auth_mount, "auth mount"),
|
||||
(spec.bound_group, "bound group")):
|
||||
_name(value, label)
|
||||
if not isinstance(spec.campaign_path, str):
|
||||
raise BuildRefused("campaign path must be literal")
|
||||
parts = spec.campaign_path.split("/")
|
||||
if len(parts) != 5 or parts[0:2] != ["platform", "workloads"] or parts[3] != "fluid-telegram":
|
||||
raise BuildRefused("operator matrix requires platform/workloads/<tenant>/fluid-telegram/<campaign>")
|
||||
for part in parts:
|
||||
_name(part, "campaign path segment")
|
||||
for ref in (spec.identity_confirmation_ref, spec.platform_matrix_review_ref):
|
||||
if not isinstance(ref, str) or not ref.strip() or ref.strip().lower() in {"pending", "unconfirmed", "unknown", "draft"}:
|
||||
raise BuildRefused("confirmed identity and platform matrix review references are required")
|
||||
ttl, maximum = _duration(spec.token_ttl, "token_ttl"), _duration(spec.token_max_ttl, "token_max_ttl")
|
||||
if not 0 < ttl <= 900 or not ttl <= maximum <= 1800:
|
||||
raise BuildRefused("attended OIDC token bounds must be at most 15m/30m")
|
||||
if not isinstance(spec.allowed_redirect_uris, tuple) or not spec.allowed_redirect_uris:
|
||||
raise BuildRefused("explicit OIDC callback tuple required")
|
||||
if len(set(spec.allowed_redirect_uris)) != len(spec.allowed_redirect_uris):
|
||||
raise BuildRefused("duplicate OIDC callback")
|
||||
for uri in spec.allowed_redirect_uris:
|
||||
if not isinstance(uri, str):
|
||||
raise BuildRefused("OIDC callback must be a URI")
|
||||
try:
|
||||
u = urlsplit(uri)
|
||||
valid = (u.scheme == "http" and u.hostname in {"localhost", "127.0.0.1"}
|
||||
and u.port == 8250 and u.path == "/oidc/callback" and not u.username
|
||||
and not u.password and not u.query and not u.fragment)
|
||||
except ValueError:
|
||||
valid = False
|
||||
if not valid:
|
||||
raise BuildRefused("initial matrix engine accepts only reviewed loopback CLI callbacks on port 8250")
|
||||
|
||||
|
||||
def oidc_role_document(spec: OIDCKVSpec) -> dict:
|
||||
_validate_oidc_spec(spec)
|
||||
return {
|
||||
"role_type": "oidc", "user_claim": "sub", "groups_claim": "groups",
|
||||
"oidc_scopes": ["openid", "profile", "email", "groups"],
|
||||
"bound_claims": {"groups": spec.bound_group},
|
||||
"allowed_redirect_uris": list(spec.allowed_redirect_uris),
|
||||
"token_policies": [spec.policy_name], "token_no_default_policy": True,
|
||||
"token_ttl": spec.token_ttl, "token_max_ttl": spec.token_max_ttl,
|
||||
"token_explicit_max_ttl": spec.token_max_ttl, "token_num_uses": 0,
|
||||
}
|
||||
|
||||
|
||||
def build_oidc_kv_lane(plan: ConstructionPlan, spec: OIDCKVSpec) -> dict[str, str]:
|
||||
"""Write only policy/auth structure after fresh exact-spec approval."""
|
||||
spec = replace(spec)
|
||||
plan = _approved_build(plan, spec)
|
||||
_run(spec.bao_bin, ["policy", "write", spec.policy_name, "-"], input_text=oidc_policy_hcl(spec))
|
||||
_run(spec.bao_bin, ["write", f"auth/{spec.auth_mount}/role/{spec.role_name}", "-"],
|
||||
input_text=json.dumps(oidc_role_document(spec)))
|
||||
objects = {"approved_spec_sha256": plan.approved_spec_sha256,
|
||||
"policy_name": spec.policy_name, "oidc_role_name": spec.role_name,
|
||||
"campaign_path": spec.campaign_path}
|
||||
record_build(plan_id=plan.id, plan_path=str(plan.path), approved_by=plan.approved_by or "",
|
||||
approved_at=plan.approved_at or "", objects=objects, log_path=spec.audit_log_path)
|
||||
return objects
|
||||
|
||||
|
||||
def _run(bao_bin: str, args: list[str], input_text: str | None = None) -> str:
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
|
|
@ -131,7 +232,7 @@ def _hash(value: str) -> None:
|
|||
raise BuildRefused("reused policy requires an exact SHA-256 content pin")
|
||||
|
||||
|
||||
def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec) -> None:
|
||||
def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec | OIDCKVSpec) -> None:
|
||||
_name(spec.policy_name, "policy name")
|
||||
_duration(spec.token_ttl, "token_ttl")
|
||||
if not isinstance(spec.bao_bin, str) or not spec.bao_bin.strip():
|
||||
|
|
@ -160,6 +261,8 @@ def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec) -> None:
|
|||
spec.delivery_dir, spec.delivery_dir / "role_id", spec.delivery_dir / "secret_id"
|
||||
}:
|
||||
raise BuildRefused("audit path must not overlap credential delivery")
|
||||
elif isinstance(spec, OIDCKVSpec):
|
||||
_validate_oidc_spec(spec)
|
||||
else:
|
||||
_name(spec.role_name, "Kubernetes role name")
|
||||
_hash(spec.policy_sha256)
|
||||
|
|
@ -173,7 +276,7 @@ def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec) -> None:
|
|||
raise BuildRefused("duplicate service account binding")
|
||||
|
||||
|
||||
def build_spec_document(spec: AppRoleKVSpec | KubernetesKVSpec) -> dict:
|
||||
def build_spec_document(spec: AppRoleKVSpec | KubernetesKVSpec | OIDCKVSpec) -> dict:
|
||||
"""Render the complete, value-free execution input for phase-3 review."""
|
||||
_validate_spec(spec)
|
||||
fields = asdict(spec)
|
||||
|
|
@ -183,7 +286,7 @@ def build_spec_document(spec: AppRoleKVSpec | KubernetesKVSpec) -> dict:
|
|||
elif isinstance(value, tuple):
|
||||
fields[key] = list(value)
|
||||
return {
|
||||
"engine": "openbao-approle-kv" if isinstance(spec, AppRoleKVSpec) else "openbao-kubernetes-kv",
|
||||
"engine": "openbao-approle-kv" if isinstance(spec, AppRoleKVSpec) else "openbao-oidc-kv-read-write" if isinstance(spec, OIDCKVSpec) else "openbao-kubernetes-kv",
|
||||
**fields,
|
||||
}
|
||||
|
||||
|
|
|
|||
77
tests/test_oidc_matrix.py
Normal file
77
tests/test_oidc_matrix.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
from dataclasses import replace
|
||||
from unittest.mock import MagicMock, patch
|
||||
import json
|
||||
import pytest
|
||||
from ops_mason.executor import OIDCKVSpec, BuildRefused, BuildError, oidc_policy_hcl, oidc_role_document, build_oidc_kv_lane, build_spec_document
|
||||
from test_executor import _plan
|
||||
|
||||
|
||||
def spec(tmp_path):
|
||||
return OIDCKVSpec(policy_name='workload-kv-fluid-telegram-hall-of-helix-operator',
|
||||
role_name='fluid-telegram-hall-of-helix-workload-kv',
|
||||
campaign_path='platform/workloads/coulomb/fluid-telegram/hall-of-helix',
|
||||
bound_group='reviewed-test-operators', allowed_redirect_uris=('http://localhost:8250/oidc/callback',),
|
||||
identity_confirmation_ref='test-only:iam-owner-receipt', platform_matrix_review_ref='test-only:platform-owner-receipt',
|
||||
audit_log_path=tmp_path/'audit.jsonl')
|
||||
|
||||
|
||||
def test_matrix_has_exact_data_scope_and_write_once_salt(tmp_path):
|
||||
s=spec(tmp_path);h=oidc_policy_hcl(s)
|
||||
assert h.count('platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/')==4
|
||||
assert 'metadata/' not in h and '*' not in h and '/destroy/' not in h
|
||||
salt=h.split('/redaction-salt"')[1].split('}')[0]
|
||||
assert '"create", "read"' in salt and '"update"' not in salt
|
||||
app=h.split('/operator-app"')[1].split('}')[0]
|
||||
assert '"read"' in app and '"create"' not in app
|
||||
assert 'auth/token/revoke-self' in h
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value',[
|
||||
('campaign_path','platform/workloads/coulomb/fluid-telegram/*'),
|
||||
('campaign_path','platform/workloads/coulomb/fluid-telegram'),
|
||||
('campaign_path','platform/workloads/coulomb/fluid-telegram/../sibling'),
|
||||
('campaign_path','platform/workloads/coulomb/another-workload/campaign'),
|
||||
('bound_group','*'),('bound_group','group,other'),
|
||||
('identity_confirmation_ref','unconfirmed'),('platform_matrix_review_ref','pending'),
|
||||
('allowed_redirect_uris',('https://public.example/ui/callback',)),
|
||||
('allowed_redirect_uris',('http://user@localhost:8250/oidc/callback',)),
|
||||
('allowed_redirect_uris',('http://localhost:8250/oidc/callback?x=1',)),
|
||||
('allowed_redirect_uris',()),('allowed_redirect_uris',('http://localhost:bad/oidc/callback',)),
|
||||
('token_ttl','16m'),('token_max_ttl','31m'),('token_max_ttl','1m')])
|
||||
def test_refuses_unsafe_matrix_before_bao(tmp_path,field,value):
|
||||
s=replace(spec(tmp_path),**{field:value})
|
||||
with patch('ops_mason.executor.subprocess.run') as run:
|
||||
with pytest.raises(BuildRefused):build_oidc_kv_lane(_plan(tmp_path,spec=spec(tmp_path)),s)
|
||||
run.assert_not_called()
|
||||
|
||||
|
||||
def test_draft_and_changed_approval_never_execute(tmp_path):
|
||||
s=spec(tmp_path)
|
||||
with patch('ops_mason.executor.subprocess.run') as run:
|
||||
with pytest.raises(BuildRefused):build_oidc_kv_lane(_plan(tmp_path,status='draft',spec=s),s)
|
||||
p=_plan(tmp_path,spec=s)
|
||||
with pytest.raises(BuildRefused):build_oidc_kv_lane(p,replace(s,bound_group='other-reviewed-group'))
|
||||
run.assert_not_called()
|
||||
|
||||
|
||||
def test_exact_role_binding_and_value_free_build(tmp_path):
|
||||
s=spec(tmp_path);p=_plan(tmp_path,spec=s)
|
||||
with patch('ops_mason.executor.subprocess.run',return_value=MagicMock(returncode=0,stdout='suppressed-response')) as run:
|
||||
result=build_oidc_kv_lane(p,s)
|
||||
assert len(run.call_args_list)==2
|
||||
assert run.call_args_list[0].args[0]==['bao','policy','write',s.policy_name,'-']
|
||||
assert run.call_args_list[1].args[0]==['bao','write','auth/netkingdom/role/'+s.role_name,'-']
|
||||
role=json.loads(run.call_args_list[1].kwargs['input'])
|
||||
assert role['bound_claims']=={'groups':s.bound_group}
|
||||
assert role['token_no_default_policy'] is True and role['token_explicit_max_ttl']=='30m'
|
||||
assert role['token_policies']==[s.policy_name]
|
||||
assert build_spec_document(s)['engine']=='openbao-oidc-kv-read-write'
|
||||
assert 'suppressed-response' not in s.audit_log_path.read_text()
|
||||
assert result['approved_spec_sha256']==p.approved_spec_sha256
|
||||
|
||||
|
||||
def test_failure_suppresses_output_and_never_claims_success(tmp_path):
|
||||
s=spec(tmp_path)
|
||||
with patch('ops_mason.executor.subprocess.run',return_value=MagicMock(returncode=1,stdout='sensitive-marker',stderr='sensitive-marker')):
|
||||
with pytest.raises(BuildError) as e:build_oidc_kv_lane(_plan(tmp_path,spec=s),s)
|
||||
assert 'sensitive-marker' not in str(e.value) and not s.audit_log_path.exists()
|
||||
|
|
@ -9,7 +9,7 @@ flavor: planning
|
|||
owner: codex
|
||||
topic_slug: helix-forge
|
||||
created: "2026-09-04"
|
||||
updated: "2026-10-03"
|
||||
updated: "2026-10-04"
|
||||
state_hub_workstream_id: "483e56d6-6601-5377-9066-66225214c046"
|
||||
---
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ side. Re-qualified from `message-from:railiance-platform` to
|
|||
|
||||
```task
|
||||
id: MASON-WP-0005-T02
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "69c558d9-664f-5ce0-80b2-d2e7544353a3"
|
||||
```
|
||||
|
|
@ -202,3 +202,25 @@ role.
|
|||
T01–T06 remain wait: accepted tenant/matrix, confirmed identity/MFA/callbacks, approved matching writer contracts, live survey, explicit construction approval and verification are outstanding. The platform design remains proposed. No lane was built or activated.
|
||||
|
||||
Evidence and precise resumption conditions: [review](../docs/evidence/2026-09-28-loose-end-review.md). Existing tasks retain all remaining obligations; no new task or workplan was opened.
|
||||
|
||||
|
||||
## Operator-matrix source delivery — 2026-10-04
|
||||
|
||||
T02 advances to progress. The ops-mason engine now renders the exact four-entry
|
||||
OIDC matrix, writes only policy/auth structure, checks confirmed identity and
|
||||
platform-review reference parameters, enforces narrow loopback callbacks and
|
||||
bounded tokens, and reuses the existing fresh exact-spec construction approval
|
||||
check before any Bao command. Value output is suppressed; audit records hold
|
||||
only coordinates and approval digest. Actual source tests cover no-approval,
|
||||
changed scope, unsafe paths/claims/callbacks, salt create-only capabilities,
|
||||
role payload, no KV-value calls, and failure redaction.
|
||||
|
||||
Platform's design is still a proposal: no matching operator-matrix request type
|
||||
exists in its current CCR schema/validator. The old September note saying the
|
||||
schema/validator are already drafted does not establish implementation or
|
||||
acceptance. Platform contract support/review remains in T02; confirmed claim,
|
||||
issuer/MFA assurance and callbacks remain T01, then live survey and explicit
|
||||
exact-plan approval T03. No production role, policy, login or credential was
|
||||
created. The construction plan stays draft; local synthetic identity references
|
||||
in tests do not count as owner returns. This source wave also preserves all
|
||||
unrelated dirty owner files and the existing untracked construction plan.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue