diff --git a/credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml b/credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml new file mode 100644 index 0000000..dc27095 --- /dev/null +++ b/credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml @@ -0,0 +1,95 @@ +id: CCR-2026-0051 +kind: credential-change-request +schema_version: 1 +request_type: workload-kv-read +title: Temporary MAS Scaleway TEM relay SMTP ESO read lane +status: proposed +created: '2026-10-18' +updated: '2026-10-18' +requester: + agent: ops-mason + reason: ops-mason plans/mas-tem-relay-smtp.md (MASON-WP-0014, reviewed 2026-10-18T10:13:55Z) + for consumer mail-address-semantics MAS-WP-0003. Hub messages daa119e6, a77e5879 + (kv_path correction) and 049afa1f. No approval is implied by this filing. +review: + required: true + required_approvers: + - founder + - railiance-platform-owner + - mail-address-semantics-owner + comments: [] +target: + domain: infotech + tenant: platform + workload: mas-tem-relay + environment: production + purpose: Deliver Scaleway TEM SMTP relay username/password to namespace mas only. + Temporary lane; torn down with namespace mas. +openbao: + mount: platform + kv_path: platform/suppliers/scaleway/mas-tem-relay + fields: + - username + - password + metadata_read: false + token_self_lifecycle: true + policy_name: workload-kv-read-mas-tem-relay + policy_file: openbao/policies/workload-kv-read-mas-tem-relay.hcl + auth: + method: kubernetes + mount: kubernetes + role: external-secrets-mas-tem-relay + bound_claims: + service_account_names: + - mas-tem-relay-eso + service_account_namespaces: + - mas + bound_claims_confirmed: false + policies: + - workload-kv-read-mas-tem-relay + ttl: 15m + audience: openbao + token_max_ttl: 15m + token_explicit_max_ttl: 15m + token_no_default_policy: true +access_frontdoor: + type: external-secrets + catalog_id: mas-tem-relay-smtp + selector: Namespace mas, SA mas-tem-relay-eso only + readiness: pending-review + resolvable: false +delivery: + surface: external-secrets + target: Namespace mas; dedicated SA mas-tem-relay-eso; ExternalSecret projecting + username and password only. + bootstrap_command: Not applicable. KV value already written by the founder (v2); + this CCR creates no value and authorizes no value read. +risk: + classification: medium + notes: + - Supplier SMTP credential can send mail as the TEM sending domain; exposure is + limited to namespace mas. + - New read scope on a supplier path; founder approval required. No other lane reads + this path (ops-mason live survey 2026-10-18T10:13:55Z). + - Apply may be run by ops-mason under ops-mason-construct (MASON-IN-0005) once + installed (MASON-WP-0015-T02) and this CCR is approved; that grant covers + workload-kv-read-* policies and external-secrets-* roles only. +verification: + positive: + - Exact SA/namespace/audience login succeeds; ESO Ready/SecretSynced; metadata + version matches admitted KV version 2 without printing values. + negative: + - Wrong SA, namespace or audience login refused; sibling paths under platform/suppliers/scaleway, + list, metadata, write and delete denied. + activation_conditions: + - Founder, platform owner and consumer owner approve the exact CCR. + - Native bound claims confirmed (confirm-binding) and native policy/role readback + matches source. + evidence: [] +lifecycle: + deactivate: Delete ExternalSecret and role external-secrets-mas-tem-relay, then + policy workload-kv-read-mas-tem-relay, when namespace mas is torn down. + rotate: Founder writes a new KV version via UI; ESO refresh picks it up; verify + metadata version only. + compromised: Revoke the SMTP credential at Scaleway, disable the role, rotate the + KV value, then re-enable after review. diff --git a/openbao/policies/workload-kv-read-mas-tem-relay.hcl b/openbao/policies/workload-kv-read-mas-tem-relay.hcl new file mode 100644 index 0000000..bc3fcbe --- /dev/null +++ b/openbao/policies/workload-kv-read-mas-tem-relay.hcl @@ -0,0 +1,11 @@ +path "platform/data/suppliers/scaleway/mas-tem-relay" { + capabilities = ["read"] +} + +path "auth/token/lookup-self" { + capabilities = ["read"] +} + +path "auth/token/revoke-self" { + capabilities = ["update"] +} diff --git a/workplans/RPF-WP-0061-self-service-ccr-intake.md b/workplans/RPF-WP-0061-self-service-ccr-intake.md new file mode 100644 index 0000000..969acc5 --- /dev/null +++ b/workplans/RPF-WP-0061-self-service-ccr-intake.md @@ -0,0 +1,124 @@ +--- +id: RPF-WP-0061 +type: workplan +title: "Self-service CCR intake: clients file and progress CCRs without a platform worker" +domain: financials +repo: railiance-platform +status: proposed +owner: codex +created: "2026-10-18" +updated: "2026-10-18" +flavor: implementation +origin: cross-repo +origin_ref: ops-mason daa119e6 / 049afa1f; MASON-IN-0005 +--- + +Demand: every credential change request today waits on a railiance-platform +worker session to allocate the CCR number, copy the client's draft and policy +file into this repo, run `credential-change.py validate`, write the platform +owner review and relay the founder command. For routine lanes the worker adds +no judgement: ops-mason already produces drafts that pass `validate` +(MASON-WP-0010-T03, MASON-WP-0014), and from MASON-IN-0005 ops-mason can apply +`workload-kv-read-*` policies and `external-secrets-*` roles itself. The worker +has become a relay, and clients stall whenever no platform session runs. + +Goal: a client can take a standard CCR from draft to approved and applied +using only repo tooling, CI and the State Hub decision flow. Platform judgement +is moved into a versioned, reviewed *standard lane profile*; only requests that +fall outside it are routed to a platform worker. + +Invariants (unchanged by this plan): + +- CCRs remain non-secret repo files in `credential-change-requests/`; the repo + stays authoritative and the hub stays a read model. +- Founder approval is never automated. Bound claims still need + `confirm-binding` against native state before apply. +- Nothing here widens ops-mason-construct or any applier grant; secret-id, + token creation and value reads stay with the founder and applier. + +## Design + +1. **Submit.** The client runs, in a railiance-platform checkout, + `scripts/credential-change.py submit --bundle ` (or `make ccr-submit`). + It copies the CCR and policy file, allocates the next free `CCR-YYYY-NNNN` + from the files on the current `main`, sets `status: proposed`, validates, + commits on branch `ccr/-` and pushes. A push or merge race simply + re-allocates; git is the allocator, so no central counter or agent is needed. +2. **Platform review as policy-as-code.** `credential-change.py + review-standard ` checks the standard lane profile + (`registry/ccr-standard-profile.yaml`): `workload-kv-read`; kubernetes auth; + exactly one KV path under `platform/workloads//` or + `platform/suppliers//`; policy file byte-equal to the + generated HCL (read on the data path plus lookup/revoke-self only); ttl and + max ttl ≤ 15m; `token_no_default_policy: true`; one SA in one namespace; + no other CCR in `proposed|approved|active` reading the same path. A pass + appends the `railiance-platform-owner` review comment with the profile + version and digest. A fail writes the reasons and marks the CCR + `needs_platform_review`, which sends one hub message to `railiance-platform`. +3. **CI gate.** A Forgejo Action on `ccr/*` branches runs `validate`, + `review-standard` and the secret scan; only a green branch fast-forwards to + `main`. The client merges its own green branch. +4. **Founder decision.** `submit` records a State Hub decision for the CCR + (templates exist in `decision-templates`). The founder resolves it; + anyone runs `sync-decision` to move the file to `approved`/`needs_changes`. +5. **Apply and evidence.** In-profile CCRs are applied by ops-mason under + ops-mason-construct, or by the applier, after `confirm-binding`; + `record-evidence` closes the CCR to `active`. +6. **Platform oversight.** Platform owns the profile and reviews changes to it + like code. A weekly `credential-change.py audit-standard` lists every + auto-reviewed CCR so a platform session can spot-check after the fact. + +## T01 — Define the standard lane profile + +```task +id: RPF-WP-0061-T01 +status: todo +priority: high +``` + +Write `registry/ccr-standard-profile.yaml` from the existing approved ESO +lanes (CCR-2026-0033, -0035, -0041, -0050, -0051 shapes). Record which past +CCRs would and would not have passed it. + +## T02 — `submit` and `review-standard` subcommands + +```task +id: RPF-WP-0061-T02 +status: todo +priority: high +``` + +Add both subcommands to `scripts/credential-change.py` with tests covering +id allocation, duplicate-path refusal, policy-file mismatch and every profile +rule. `review-standard` never touches OpenBao. + +## T03 — CI gate on `ccr/*` branches + +```task +id: RPF-WP-0061-T03 +status: todo +priority: medium +``` + +Forgejo Action running validate, review-standard and the secret scan; confirm +which identities may push `ccr/*` (ops-mason first). + +## T04 — Hub decision on submit and client-run `sync-decision` + +```task +id: RPF-WP-0061-T04 +status: todo +priority: medium +``` + +## T05 — Client documentation and first self-service CCR + +```task +id: RPF-WP-0061-T05 +status: todo +priority: medium +``` + +Document the flow in `docs/credential-change-approval.md` and notify +ops-mason and ops-warden. Acceptance: one CCR filed, reviewed, approved and +applied by a client with no railiance-platform worker session involved.