WP-0005-T05: lane verified from policy and role; owner-run check script; Forgejo case plan rendered

Live policy and role equal the approved spec, 8 agent denies present, custody
at coulomb v1. verify-fluid-telegram-lane.sh lets the founder prove member
capabilities and non-member refusal without printing a token. T06 entry
proposed to ops-warden. plans/case-forgejo-netkingdom-oidc.md: reviewed,
no collisions.

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:
tegwick 2026-10-09 19:47:45 +02:00
parent e10bc2b82b
commit cd251f7c2e
4 changed files with 214 additions and 2 deletions

View file

@ -0,0 +1,139 @@
---
id: case-forgejo-netkingdom-oidc
demand_source: SecurityCase forgejo-netkingdom-oidc (owner railiance-apps)
consumer_repo: railiance-apps
credential_type: security-case
derived_from_type: oidc-browser-relying-party@1.0
render_digest: sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23
status: reviewed
case_acceptance:
record: key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08,
KEY-WP-0038-T01); type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4,
hub decision e418263e
at: '2026-10-08'
by:
- owner
- founder
reference_case: true
approved_by: null
approved_at: null
---
# Construction plan (derived-from-type): forgejo-netkingdom-oidc
Rendered from `oidc-browser-relying-party@1.0` (Browser OIDC relying party for an internal service). Do not edit by hand:
re-run `ops-mason plan --case` instead. Render digest `sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23`.
**Reference case:** the type is `acknowledged`, not yet `implemented`. This case
is the one whose live materialization takes the type to `implemented`.
## 1. Demand
Let an internal web service sign its human users in through NetKingdom (KeyCape): an exact-redirect OIDC client, optional MFA pin, the LLDAP groups the service maps to its own roles, and, for confidential clients, a client secret minted straight into OpenBao custody. Replaces the per-client rollout scripts in sso-mfa/k8s/keycape. First case: Forgejo (KEY-WP-0038).
## 2. Existing-structure survey
Survey source: live (metadata only).
No collision with existing policies, roles, KV paths or catalog entries.
## 3. Objects (from the type's materialization)
| Item | Kind | Name |
|---|---|---|
| client-secret-path | kv_path | `platform/workloads/keycape/forgejo` |
| keycape-reader-policy | openbao_policy | `workload-kv-read-keycape-forgejo-eso` |
| consumer-reader-policy | openbao_policy | `workload-kv-read-keycape-forgejo-forgejo` |
| eso-role | openbao_role | `external-secrets-keycape-forgejo` |
| catalog-entry | catalog_entry | `forgejo-keycape-client` |
### client-secret-path
```
field CLIENT_SECRET; minted by the credential broker, never written by hand
```
### keycape-reader-policy
```
path "platform/data/workloads/keycape/forgejo" {
capabilities = ["read"]
}
```
### consumer-reader-policy
```
path "platform/data/workloads/keycape/forgejo" {
capabilities = ["read"]
}
```
### eso-role
```
auth: kubernetes
bound_service_account_names: [external-secrets]
bound_service_account_namespaces: [external-secrets]
token_policies: [workload-kv-read-keycape-forgejo-eso]
token_no_default_policy: true
token_ttl: 15m
token_max_ttl: 15m
```
### catalog-entry
```
id: forgejo-keycape-client
owner_repo: key-cape
warden_executes: false
risk: high
status: active once verification passes
workload_ref: applicable, name forgejo, declaration_ref of the consumer's published declaration or the explicit unknown form, never inferred
path_template: platform/workloads/keycape/forgejo
field: CLIENT_SECRET
delegation: native, intended_owner key-cape
rotation: mint-secret then verification; automatable false until a run has been proven
```
## 4. Review notes
Shape reviewed once, when the type was acknowledged; this review checks only
this case's names against existing structure (section 2).
Follow-through (ops-warden):
- ensure-groups — net-kingdom (attended): identity-provisioner ensure_service_groups, run inside the provisioner pod through the session's contained cluster access (groups forge-admins,forge-coulomb-owners,forge-coulomb-readers; members exactly forge-admins=tegwick;forge-coulomb-owners=tegwick as recorded in the accepted case, nothing else)
- mint-secret — railiance-platform: railiance-platform credential broker: generate a client secret inside custody at bao:platform/workloads/keycape/forgejo
- deliver-secret — railiance-platform: ExternalSecret sso/keycape-forgejo-client (key client-secret) from bao:platform/workloads/keycape/forgejo
- bind-env — net-kingdom (attended): patch Deployment sso/keycape: env KEYCAPE_<CLIENT_ID>_CLIENT_SECRET from sso/keycape-forgejo-client
- register-client — net-kingdom (attended): sso-mfa/k8s/keycape/keycape-client.py apply --client-file <client-from-case> --case-acceptance <record> (net-kingdom.keycape-client-registration)
- hand-over — railiance-apps: configure forgejo with issuer https://kc.coulomb.social, client forgejo and custody bao:platform/workloads/keycape/forgejo
Verification:
- authorize-accepts-redirects (present): GET https://kc.coulomb.social/authorize with client forgejo and each of https://forgejo.coulomb.social/user/oauth2/netkingdom/callback returns the login page
- authorize-refuses-other-redirect (deny): GET https://kc.coulomb.social/authorize with client forgejo and an unregistered redirect returns an OAuth error
- custody-denied-to-default (deny): a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/forgejo
- custody-allowed-to-consumer (allow): policy workload-kv-read-keycape-forgejo-forgejo grants read on bao:platform/workloads/keycape/forgejo (capabilities check only)
- groups-present (present): LLDAP groups forge-admins,forge-coulomb-owners,forge-coulomb-readers exist, with exactly the memberships of the accepted case added
- secret-delivered (present): the delivery for sso/keycape-forgejo-client reports Ready=True (status only)
- mfa-pinned (present): keycape-client.py inspect for forgejo shows the MFA pin on the registered client (non-secret client view)
Rollback, in order:
- remove the OIDC source for forgejo from forgejo (owner railiance-apps)
- keycape-client.py apply --operation remove for forgejo
- remove env KEYCAPE_<CLIENT_ID>_CLIENT_SECRET from Deployment sso/keycape, after register-client is reversed
- delete ExternalSecret sso/keycape-forgejo-client and its target
- covered by client-secret-path: one metadata delete, after deliver-secret is reversed
- identity-provisioner revoke_service_groups inside the provisioner pod (attended, as ensure-groups) with the case's memberships; delete_if_empty only if no other case uses forge-admins,forge-coulomb-owners,forge-coulomb-readers
- delete metadata and all versions at platform/workloads/keycape/forgejo, after the ExternalSecret is gone (this also reverses mint-secret)
- delete Kubernetes-auth role external-secrets-keycape-forgejo
- delete policy workload-kv-read-keycape-forgejo-eso, after eso-role
- delete policy workload-kv-read-keycape-forgejo-forgejo
- remove catalog entry forgejo-keycape-client
## 5. Case acceptance (replaces the executive summary)
Accepted by owner, founder at 2026-10-08; record `key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e`.

View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
# MASON-WP-0005-T05 — operator-side proof of the fluid-telegram operator lane.
#
# ./scripts/verify-fluid-telegram-lane.sh member # sign in as a fluid-telegram-operators member (aal2)
# ./scripts/verify-fluid-telegram-lane.sh non-member # sign in as someone outside the group
#
# Logs in on the lane's OIDC role, asks OpenBao what the token may do on each
# path (sys/capabilities-self: names only, never a value), revokes the token,
# and writes a 0600 receipt. The token is held in a shell variable only: never
# printed, never stored in the token helper, so your own session is untouched.
# The login URL appears on stderr as usual; only the token goes to stdout.
set -euo pipefail
export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}"
ROLE=fluid-telegram-hall-of-helix-workload-kv
BASE=platform/data/workloads/coulomb/fluid-telegram/hall-of-helix
RECEIPTS="$(getent passwd "$(id -u)" | cut -d: -f6)/.local/state/railiance/receipts"
mode="${1:-}"
case "$mode" in member|non-member) ;; *) echo "usage: $0 member|non-member" >&2; exit 2 ;; esac
umask 077
mkdir -p "$RECEIPTS"
receipt="$RECEIPTS/fluid-telegram-lane-$mode.txt"
echo "Sign in as the $mode identity in your browser (URL below)." >&2
if ! token="$(bao login -token-only -method=oidc -path=netkingdom role="$ROLE")" || [ -z "$token" ]; then
{ echo "mode=$mode at=$(date -u +%FT%TZ)"; echo "login=refused"; } > "$receipt"
echo "login refused (expected for non-member); receipt: $receipt" >&2
[ "$mode" = non-member ] && exit 0 || exit 1
fi
{
echo "mode=$mode at=$(date -u +%FT%TZ)"
echo "login=ok"
for p in \
"$BASE/operator-app" "$BASE/operator-session" "$BASE/bot-token" "$BASE/redaction-salt" \
"platform/data/workloads/coulomb/fluid-telegram/hall-of-helix" \
"platform/data/workloads/coulomb/fluid-telegram/other-campaign/bot-token" \
"platform/data/workloads/coulomb/whynot-design/npm-publish" \
"platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session" \
"platform/data/workloads/keycape/secrets-engine-openbao" \
"sys/policies/acl/agent-high-risk-boundary"; do
echo "$p: $(BAO_TOKEN="$token" bao token capabilities "$p" 2>/dev/null || echo error)"
done
echo "policies=$(BAO_TOKEN="$token" bao token lookup -format=json 2>/dev/null | python3 -c 'import json,sys; print(",".join(json.load(sys.stdin)["data"]["policies"]))' || echo error)"
} > "$receipt"
BAO_TOKEN="$token" bao token revoke -self >/dev/null 2>&1 && echo "revoked=yes" >> "$receipt" || echo "revoked=FAILED" >> "$receipt"
unset token
echo "receipt: $receipt" >&2
[ "$mode" = member ]

View file

@ -215,10 +215,10 @@ metadata deleted. fluid-telegram must set
```task
id: MASON-WP-0005-T05
status: wait
status: progress
depends_on: [MASON-WP-0005-T04]
blocked_on: human
blocking_reason: "Lane built 2026-10-09; verification needs a survey grant (positive matrix, sibling/parent/agent denial via policy text and capabilities). Non-member refusal and the salt CAS probe are owner-run (FT-WP-0002)."
blocking_reason: "ops-mason checks pass (2026-10-09). Founder runs scripts/verify-fluid-telegram-lane.sh member, then non-member, and shares the two receipts; fluid-telegram runs the salt create-once probe (FT-WP-0002)."
priority: high
state_hub_task_id: "4c205be7-4f1f-5dd1-aafa-fc112fdd640e"
```
@ -239,6 +239,17 @@ capabilities, audit request ids/timestamps, versions/timestamps, and pass/fail.
Any value-side create/update test must be performed by the sanctioned
operator/platform flow and return metadata-only evidence to ops-mason.
2026-10-09 (grant `claude-MASON-WP-0005-T05`, revoked after): live policy
`workload-kv-fluid-telegram-hall-of-helix-operator` equals the approved matrix
exactly (so parent, sibling campaign and sibling workload are not granted);
the OIDC role equals the approved role document (groups + aal2, 15m/30m, no
default policy, three callbacks); `agent-high-risk-boundary` holds the 8
`deny` stanzas (data + metadata for the four entries); all four entries at the
`coulomb` path v1, the `infotech` ones gone. Owner-run remainder: a member
login and a non-member login with `scripts/verify-fluid-telegram-lane.sh`
(capabilities only, receipt under `~/.local/state/railiance/receipts/`), and
the salt create-once probe by fluid-telegram (message `f966db1c`).
## Hand off routing and keep the adapter lane separate
```task
@ -262,6 +273,10 @@ The adapter lane is tracked separately as `MASON-IN-0003`, tied to
must use the adapter's own runtime identity rather than this attended OIDC
role.
2026-10-09: entry proposed to ops-warden as pointer-only, `resolvable: false`,
`applied-pending-verify` (message `856bd6ba`); activation stays with
ops-warden after T05.
## Loose-end review — 2026-09-28
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.

View file

@ -131,6 +131,14 @@ clean, 1 collisions, 2 refused. `tests/test_case_plan.py` (8, on a copy of the
type marked `implemented`); suite 167 passed. The live Forgejo case is refused
today, correctly: the type is `acknowledged`, not yet `implemented`.
2026-10-09: net-kingdom accepted the Forgejo case
(`net-kingdom/security-cases/forgejo-netkingdom-oidc.yaml`, message `d1bbcb44`).
As the reference case it renders with `--allow-unimplemented`, mirroring the
validator's flag: allowed only for an `acknowledged` type, marked
`reference_case: true`; and `reviewed` now requires a live survey. Rendered
live into `plans/case-forgejo-netkingdom-oidc.md`: status `reviewed`, no
collision, digest `sha256:fb8b114d…`.
```task
id: MASON-WP-0010-T03
status: wait