MASON-WP-0005-T01: bind key-cape's confirmed claims for the operator lane
key-cape confirmed groups_claim=groups, bound group fluid-telegram-operators, assurance aal2 and the three openbao-admin callbacks. The OIDC executor now pins /assurance/level=aal2 (bound_claims_type=string) and accepts the exact operator-tunnel UI callback; the retired public UI callback stays refused. T01 now waits on the founder go for the LLDAP group and a scoped survey grant. 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
This commit is contained in:
parent
24d05c6cde
commit
894761477f
4 changed files with 49 additions and 11 deletions
|
|
@ -113,9 +113,18 @@ until recovery is verified and must never be exposed through this OIDC role.
|
|||
- token TTL: `15m`, maximum TTL `30m`
|
||||
- user claim: `sub`; groups claim: `groups`
|
||||
- scopes: `openid,profile,email,groups`
|
||||
- proposed bound claim: `groups=fluid-telegram-operators` — **unconfirmed**
|
||||
- callbacks: Bao UI plus localhost and loopback CLI callbacks already accepted
|
||||
by the platform contract
|
||||
- bound claims (`bound_claims_type=string`): `groups=[fluid-telegram-operators]`
|
||||
and `/assurance/level=aal2` — confirmed by key-cape 2026-10-08 (message
|
||||
`b2ed890f-a05a-4b96-95eb-2cd9d8b32caa`, key-cape `e0b5fae`). Human ID tokens
|
||||
always carry `groups` (LLDAP group names); `aal2` is set only after a
|
||||
verified privacyIDEA factor, the same pin as CCR-2026-0034.
|
||||
- precondition: LLDAP group `fluid-telegram-operators` does not exist yet;
|
||||
creating it with its members is a NetKingdom directory act needing the
|
||||
founder go. KeyCape does not create groups.
|
||||
- callbacks (subset of the `openbao-admin` client registration):
|
||||
`http://localhost:8250/oidc/callback`, `http://127.0.0.1:8250/oidc/callback`,
|
||||
`http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback`. Never the
|
||||
retired `https://bao.coulomb.social/...` (2026-09-15).
|
||||
|
||||
The role is campaign-specific because its policy is campaign-specific. A
|
||||
second campaign receives a second policy and role after its own review; it is
|
||||
|
|
@ -143,7 +152,8 @@ This section is incomplete while T01 and T02 are open; the plan must remain
|
|||
access to KV metadata because the provisioner uses data GETs to detect
|
||||
absence; metadata evidence comes from the separately scoped builder/operator
|
||||
verification path.
|
||||
- **Identity binding:** an unconfirmed group is a hard blocker, not a warning.
|
||||
- **Identity binding:** the group and assurance claims are confirmed; the
|
||||
group's existence in LLDAP is a hard blocker, not a warning.
|
||||
The role must not be readable by every OIDC user.
|
||||
- **Write-once semantics:** KV-v2 create/update distinction is documented, but
|
||||
activation also requires an owner-run non-secret probe proving the installed
|
||||
|
|
@ -177,7 +187,7 @@ This section is incomplete while T01 and T02 are open; the plan must remain
|
|||
|
||||
## 5. Executive summary (phase 3)
|
||||
|
||||
Not yet renderable. The owner-approved tenant segment, OIDC bound claim, live
|
||||
structure survey, CCR/executor extension, and installed-engine create/update
|
||||
Not yet renderable. The owner-approved tenant segment, the LLDAP group's
|
||||
creation, the live structure survey, CCR/executor extension, and installed-engine create/update
|
||||
probe are open inputs. When those close, this section must ask for one explicit
|
||||
approval covering the exact objects and capability matrix above.
|
||||
|
|
|
|||
|
|
@ -93,6 +93,11 @@ class OIDCKVSpec:
|
|||
audit_log_path: Path | None = None
|
||||
|
||||
|
||||
# KeyCape sets assurance.level=aal2 only after a verified privacyIDEA factor
|
||||
# (key-cape reply 2026-10-08, as in CCR-2026-0034); every attended role pins it.
|
||||
_REQUIRED_ASSURANCE = {"/assurance/level": "aal2"}
|
||||
_UI_CALLBACK = "http://127.0.0.1:18200/ui/vault/auth/{mount}/oidc/callback"
|
||||
|
||||
_OPERATOR_MATRIX = (
|
||||
("operator-app", ("read",)),
|
||||
("operator-session", ("create", "read", "update")),
|
||||
|
|
@ -138,6 +143,8 @@ def _validate_oidc_spec(spec: OIDCKVSpec) -> None:
|
|||
for uri in spec.allowed_redirect_uris:
|
||||
if not isinstance(uri, str):
|
||||
raise BuildRefused("OIDC callback must be a URI")
|
||||
if uri == _UI_CALLBACK.format(mount=spec.auth_mount):
|
||||
continue
|
||||
try:
|
||||
u = urlsplit(uri)
|
||||
valid = (u.scheme == "http" and u.hostname in {"localhost", "127.0.0.1"}
|
||||
|
|
@ -146,7 +153,7 @@ def _validate_oidc_spec(spec: OIDCKVSpec) -> None:
|
|||
except ValueError:
|
||||
valid = False
|
||||
if not valid:
|
||||
raise BuildRefused("initial matrix engine accepts only reviewed loopback CLI callbacks on port 8250")
|
||||
raise BuildRefused("matrix engine accepts only the reviewed loopback CLI callbacks on port 8250 and the operator-tunnel UI callback")
|
||||
|
||||
|
||||
def oidc_role_document(spec: OIDCKVSpec) -> dict:
|
||||
|
|
@ -154,7 +161,8 @@ def oidc_role_document(spec: OIDCKVSpec) -> dict:
|
|||
return {
|
||||
"role_type": "oidc", "user_claim": "sub", "groups_claim": "groups",
|
||||
"oidc_scopes": ["openid", "profile", "email", "groups"],
|
||||
"bound_claims": {"groups": spec.bound_group},
|
||||
"bound_claims_type": "string",
|
||||
"bound_claims": {"groups": [spec.bound_group], **_REQUIRED_ASSURANCE},
|
||||
"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,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ def test_exact_role_binding_and_value_free_build(tmp_path):
|
|||
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['bound_claims']=={'groups':[s.bound_group],'/assurance/level':'aal2'}
|
||||
assert role['bound_claims_type']=='string'
|
||||
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'
|
||||
|
|
@ -80,3 +81,12 @@ def test_failure_suppresses_output_and_never_claims_success(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()
|
||||
|
||||
|
||||
def test_callbacks_accept_operator_ui_and_refuse_retired_public_ui(tmp_path):
|
||||
s=spec(tmp_path)
|
||||
ui='http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback'
|
||||
assert ui in oidc_role_document(replace(s,allowed_redirect_uris=(ui,)))['allowed_redirect_uris']
|
||||
for bad in ('https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback',
|
||||
'http://127.0.0.1:18200/ui/vault/auth/other/oidc/callback'):
|
||||
with pytest.raises(BuildRefused):oidc_role_document(replace(s,allowed_redirect_uris=(bad,)))
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ consumer workplan `FT-WP-0002` (`f2373858-c932-5a4d-81b6-db9a3595135a`).
|
|||
```task
|
||||
id: MASON-WP-0005-T01
|
||||
status: wait
|
||||
blocked_on: message-from:key-cape
|
||||
blocking_reason: "KeyCape/NetKingdom must confirm the OIDC group claim, assurance level and callbacks for the attended provisioner role; railiance-platform (2026-09-09) declines to decide these. Tenant/path acceptance moves to the plan approval (T03)."
|
||||
blocked_on: human
|
||||
blocking_reason: "Claim, assurance and callbacks confirmed by key-cape (2026-10-08). Remaining: founder go for net-kingdom to create LLDAP group fluid-telegram-operators with its members, and a scoped OpenBao grant (bao-session.sh grant MASON-WP-0005-T01) for the live survey."
|
||||
priority: high
|
||||
state_hub_task_id: "18c9099f-882f-575c-93b9-cc68d7fbc2af"
|
||||
```
|
||||
|
|
@ -72,6 +72,16 @@ plan approval); the construction contract and writer authority are ops-mason's
|
|||
side. Re-qualified from `message-from:railiance-platform` to
|
||||
`message-from:key-cape`; asked key-cape for the claim the same day.
|
||||
|
||||
2026-10-08: key-cape answered (message `b2ed890f`, source key-cape `e0b5fae`):
|
||||
`groups_claim=groups`, bound `groups=[fluid-telegram-operators]`; bound
|
||||
`/assurance/level=aal2` with `bound_claims_type=string` (as CCR-2026-0034);
|
||||
callbacks are the two loopback CLI URIs on 8250 plus the operator-tunnel UI
|
||||
callback on 18200. Recorded in the plan's OIDC role shape. The executor now
|
||||
always pins `aal2` and accepts that exact UI callback, refusing the retired
|
||||
public one (`tests/test_oidc_matrix.py`, 153 passed). Two inputs remain, both
|
||||
human: the founder go for net-kingdom to create the LLDAP group with its
|
||||
members, and a scoped grant for the live survey (no operator session today).
|
||||
|
||||
## Extend the construction and credential-change contracts
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue