From cd251f7c2eef202f874060198c5a00012397d43d Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 9 Oct 2026 19:47:45 +0200 Subject: [PATCH] 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 Assistant: claude-code Assistant-Model: opus Assistant-Process: 327203@bnt-lap001 Assistant-Session: 55028ae3-4914-4bd1-8f5c-815f3527aef0 --- plans/case-forgejo-netkingdom-oidc.md | 139 ++++++++++++++++++ scripts/verify-fluid-telegram-lane.sh | 50 +++++++ ...fluid-telegram-operator-credential-lane.md | 19 ++- ...N-WP-0010-security-case-materialization.md | 8 + 4 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 plans/case-forgejo-netkingdom-oidc.md create mode 100755 scripts/verify-fluid-telegram-lane.sh diff --git a/plans/case-forgejo-netkingdom-oidc.md b/plans/case-forgejo-netkingdom-oidc.md new file mode 100644 index 0000000..0ab7150 --- /dev/null +++ b/plans/case-forgejo-netkingdom-oidc.md @@ -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_SECRET from sso/keycape-forgejo-client +- register-client — net-kingdom (attended): sso-mfa/k8s/keycape/keycape-client.py apply --client-file --case-acceptance (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_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`. diff --git a/scripts/verify-fluid-telegram-lane.sh b/scripts/verify-fluid-telegram-lane.sh new file mode 100755 index 0000000..3069a6b --- /dev/null +++ b/scripts/verify-fluid-telegram-lane.sh @@ -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 ] diff --git a/workplans/MASON-WP-0005-fluid-telegram-operator-credential-lane.md b/workplans/MASON-WP-0005-fluid-telegram-operator-credential-lane.md index 83ed797..fdfe430 100644 --- a/workplans/MASON-WP-0005-fluid-telegram-operator-credential-lane.md +++ b/workplans/MASON-WP-0005-fluid-telegram-operator-credential-lane.md @@ -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. diff --git a/workplans/MASON-WP-0010-security-case-materialization.md b/workplans/MASON-WP-0010-security-case-materialization.md index 85a85ae..d5e922a 100644 --- a/workplans/MASON-WP-0010-security-case-materialization.md +++ b/workplans/MASON-WP-0010-security-case-materialization.md @@ -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