diff --git a/docs/credential-lane-designs/secrets-engine-service-jwt.md b/docs/credential-lane-designs/secrets-engine-service-jwt.md index 7ecad81..ce7e05f 100644 --- a/docs/credential-lane-designs/secrets-engine-service-jwt.md +++ b/docs/credential-lane-designs/secrets-engine-service-jwt.md @@ -1,6 +1,6 @@ # Secrets-engine service JWT login -Status: proposed, not provisioned. Owner: railiance-platform, RPF-WP-0032. +Status: proposed, not provisioned. Owner: railiance-platform, RPF-WP-0035-T02 (design origin RPF-WP-0032). Demand: State Hub message `38b47122-07eb-4a7f-a5df-13a38f50e110`. ## Contract and ownership @@ -20,11 +20,11 @@ or the coding-agent mount to introduce this service. | Setting | Proposed value / evidence required | | --- | --- | -| Verification | RS256 only; exact HTTPS issuer and approved discovery/JWKS URL, both pending KeyCape owner confirmation | +| Verification | RS256 only; issuer `https://kc.coulomb.social`, JWKS `https://kc.coulomb.social/jwks`; public discovery rechecked 2026-09-27; live registration remains unverified | | Role type / user claim | `jwt` / `sub` | | Audience | `secrets-engine-openbao` | | Subject | `service:secrets-engine` | -| Bound claims, string matching | `principal_type=service`, `tenant=tenant:coulomb`, required `roles=secrets-engine`, `scope=openbao:login` | +| Bound claims, string matching | `principal_type=service`, `tenant=tenant:platform`, required `roles=secrets-engine`, `scope=openbao:login` | | Token policy | `secrets-engine-login-self` only; no default policy | | Token bounds | service token, TTL/max/explicit max `5m`, 8 uses, no periodic token; budget must include cleanup | | Logging | verbose OIDC logging disabled; no JWT claim dump or token/accessor in receipts | @@ -96,3 +96,27 @@ OpenBao's [JWT documentation](https://openbao.org/docs/auth/jwt/) describes signature verification and claim binding; its [role API](https://openbao.org/docs/2.4.x/api/auth/jwt/) defines token TTL/use limits and the explicit maximum. These are mechanism references, not evidence of this cluster's installed configuration. + +## Prepared source return — 2026-09-27 + +The exact proposed bundle is `openbao/auth/keycape-services-config.json`, +`openbao/auth/secrets-engine-jwt-role.json` and +`openbao/policies/secrets-engine-login-self.hcl`. These files are non-secret +inputs for the existing attended platform procedure, not an applied receipt. +The discovery document advertises `/token` and `client_credentials`. + +The operator clarified that platform infrastructure belongs to tenant zero, +`tenant:platform`. Coulomb is a workload tenant; its DNS domain is not ownership. +The former `tenant:coulomb` service registration and consumer preflight were +consistent but incorrectly scoped. The corrected source uses `tenant:platform` +for both OpenBao infrastructure clients and the existing approval clients. + +Before writing: survey mounts/roles, confirm exact live client registration and +protected client-side custody, review this bundle and obtain the scoped attended +window via `warden access openbao-platform-admin-login --exec -- `. +Do not execute a placeholder command. The reviewed child must install only this +isolated mount/config, self policy and role, compare effective policy metadata, +then perform the positive/negative/expiry/use-limit/revocation checks above. +Do not print login responses. Publish the consumer contract only after those +checks pass. A conflicting existing mount/role requires review, not overwrite. +No service-client secret is created, moved or requested by this source return. diff --git a/docs/platform-tenant-essentials-review.md b/docs/platform-tenant-essentials-review.md new file mode 100644 index 0000000..58c11ce --- /dev/null +++ b/docs/platform-tenant-essentials-review.md @@ -0,0 +1,43 @@ +# Platform essentials tenant review — 2026-09-27 + +Operator requirement: `tenant:platform` is tenant zero for platform infrastructure. +`tenant:coulomb` is a workload/product tenant. The shared `coulomb.social` DNS +suffix does not establish tenant ownership. Provider ownership, caller identity, +resource tenant and enforcement capability are separate facts. + +| Essential / boundary | Source reviewed | Result and action | +| --- | --- | --- | +| OpenBao custody/provider | `tenancy.yaml`, `docs/tenancy-posture.md`, `openbao/auth/` | Platform-owned. Correct both infrastructure JWT roles to platform; retain exact audience/subject/scope and bounded policies. | +| Platform coding agent | KeyCape service registration; `coding-agent-jwt-role.json` | Incorrect Coulomb claim corrected in both source owners. Existing workload read policy remains explicit; changing identity tenant grants no additional data access. | +| Secrets Engine service login | KeyCape registration; consumer `service_auth.py`; proposed service role | Incorrect Coulomb claim corrected together to platform. Login-only self policy; no lane mutation privilege. | +| Approval / informed decision / policy checks | KeyCape registrations; Secrets Engine approval and authorization profiles | Already platform-bound. Keep exact tenant comparison, distinct audiences/scopes and human controls. | +| Audit senders | `docs/credential-lane-designs/factory-audit-senders-review.md`; Audit Core tenancy declaration | Factory senders already restricted to platform. Provider ownership does not relabel historical events or consumer tenants. | +| KeyCape human directory fallback | KeyCape `token.go`, tenant claim contract | Still legacy `tenant:coulomb` when directory tenant is missing. Do not change the default to platform: that would implicitly elevate unclassified users. Explicit platform registration/directory binding remains necessary. | +| Shared PostgreSQL / Forgejo database | `tenancy.yaml`, `docs/tenancy-posture.md`, rapp-postgres declarations | Platform provider ownership; consumers retain their databases and workload isolation. No tenant claim is implemented at the substrate by these declarations. | +| OpenBao package | rapp-openbao YAML/JSON source review | No runtime tenant claim found in package declarations; package ownership and the auth role bindings above must not be inferred from ingress DNS. Absence of a claim is not live verification. | +| Warden / Forgejo | Warden tenancy declaration; Forgejo YAML/JSON source review | Infrastructure belongs to platform. SSH, package and workload grants retain their explicit identities; do not replace Coulomb organization or KV path components with platform. | + +This is a bounded source review of the existing credential-chain dependencies, +not a claim that every platform deployment has been audited. No live tenant +migration, token issuance, value read or workload relocation was performed. +The existing RPF-WP-0035-T02 owns the coordinated service-login return; existing +KEY-WP-0009 contract and SECRETS-WP-0008-T06 carry provider/consumer changes. +No new work item is opened. + +## Deployment and acceptance + +1. Inventory exact live KeyCape registrations and OpenBao mount/roles using + metadata-only, attended authority. Source files are not live-state receipts. +2. Compare issuer, audience, subject, tenant, policies and aliases. Preserve + workload grants; do not introduce a tenant alias or accept both tenants. +3. Coordinate issuance and verifier/consumer deployment. Old Coulomb-bound + platform JWTs must fail; new platform-bound JWTs must succeed. Rollback is + an explicit coordinated restoration, never a fallback to another identity. +4. Already-issued OpenBao tokens do not change tenant/policies when a JWT role + changes. Revoke affected tokens under owner custody and verify denial. Bound + the remaining JWT and token lifetimes; retain only non-secret results. +5. Prove wrong-tenant/audience/subject/scope refusal, exact effective policies, + expiry/use limits and self-revocation before publishing the consumer contract. + +Tenant ownership does not raise the historical I/A/E/R/V posture scores. The +posture records describe demonstrated isolation/recovery, not the owner tenant. diff --git a/docs/tenancy-posture.md b/docs/tenancy-posture.md index 3012e0a..379927a 100644 --- a/docs/tenancy-posture.md +++ b/docs/tenancy-posture.md @@ -256,3 +256,12 @@ renumbered. Six months, or on any of: a service moving placement level, a backup target becoming available, or the framework reaching `accepted`. Next review due **2027-02-17**. + +## Platform ownership clarification — 2026-09-27 + +All infrastructure providers in this declaration belong to `tenant:platform` +(tenant zero), including OpenBao, shared PostgreSQL and Forgejo's database. +Coulomb is a workload tenant; DNS under `coulomb.social` does not assign these +providers to it. Provider ownership is separate from the measured tenancy +posture axes and from each credential or database consumer's resource tenant. +See [the source review and migration requirements](platform-tenant-essentials-review.md). diff --git a/openbao/auth/coding-agent-jwt-role.json b/openbao/auth/coding-agent-jwt-role.json index 2bd5e86..8fa6147 100644 --- a/openbao/auth/coding-agent-jwt-role.json +++ b/openbao/auth/coding-agent-jwt-role.json @@ -8,7 +8,7 @@ "bound_claims": { "sub": "service:codex:railiance-platform", "principal_type": "service", - "tenant": "tenant:coulomb", + "tenant": "tenant:platform", "roles": "coding-agent" }, "token_policies": [ diff --git a/openbao/auth/keycape-services-config.json b/openbao/auth/keycape-services-config.json new file mode 100644 index 0000000..1e85dc7 --- /dev/null +++ b/openbao/auth/keycape-services-config.json @@ -0,0 +1,7 @@ +{ + "jwks_url": "https://kc.coulomb.social/jwks", + "bound_issuer": "https://kc.coulomb.social", + "jwt_supported_algs": [ + "RS256" + ] +} diff --git a/openbao/auth/secrets-engine-jwt-role.json b/openbao/auth/secrets-engine-jwt-role.json new file mode 100644 index 0000000..8efa143 --- /dev/null +++ b/openbao/auth/secrets-engine-jwt-role.json @@ -0,0 +1,25 @@ +{ + "role_type": "jwt", + "user_claim": "sub", + "bound_audiences": [ + "secrets-engine-openbao" + ], + "bound_subject": "service:secrets-engine", + "bound_claims_type": "string", + "bound_claims": { + "principal_type": "service", + "tenant": "tenant:platform", + "roles": "secrets-engine", + "scope": "openbao:login" + }, + "token_policies": [ + "secrets-engine-login-self" + ], + "token_no_default_policy": true, + "token_type": "service", + "token_ttl": "5m", + "token_max_ttl": "5m", + "token_explicit_max_ttl": "5m", + "token_num_uses": 8, + "verbose_oidc_logging": false +} diff --git a/openbao/policies/secrets-engine-login-self.hcl b/openbao/policies/secrets-engine-login-self.hcl new file mode 100644 index 0000000..c9df710 --- /dev/null +++ b/openbao/policies/secrets-engine-login-self.hcl @@ -0,0 +1,10 @@ +# RPF-WP-0035-T02: proposed login-only service policy; not a lane applier. +path "auth/token/lookup-self" { + capabilities = ["read"] +} +path "sys/capabilities-self" { + capabilities = ["update"] +} +path "auth/token/revoke-self" { + capabilities = ["update"] +} diff --git a/tests/test_credential_change.py b/tests/test_credential_change.py index 0729282..24d4adb 100644 --- a/tests/test_credential_change.py +++ b/tests/test_credential_change.py @@ -247,7 +247,7 @@ class CredentialChangeTests(unittest.TestCase): { "sub": "service:codex:railiance-platform", "principal_type": "service", - "tenant": "tenant:coulomb", + "tenant": "tenant:platform", "roles": "coding-agent", }, ) diff --git a/tests/test_secrets_engine_jwt_contract.py b/tests/test_secrets_engine_jwt_contract.py new file mode 100644 index 0000000..9840ace --- /dev/null +++ b/tests/test_secrets_engine_jwt_contract.py @@ -0,0 +1,30 @@ +"""Source safety bounds; live JWT verification remains attended acceptance.""" +import json +import re +import unittest +from pathlib import Path +ROOT = Path(__file__).resolve().parents[1] + +class ServiceJWTContract(unittest.TestCase): + def test_platform_identity_is_exact_and_cannot_be_an_applier(self): + role = json.loads((ROOT / "openbao/auth/secrets-engine-jwt-role.json").read_text()) + self.assertEqual(role["bound_audiences"], ["secrets-engine-openbao"]) + self.assertEqual(role["bound_subject"], "service:secrets-engine") + self.assertEqual(role["bound_claims"], dict(principal_type="service", tenant="tenant:platform", roles="secrets-engine", scope="openbao:login")) + self.assertEqual(role["bound_claims_type"], "string") + self.assertEqual(role["token_policies"], ["secrets-engine-login-self"]) + self.assertTrue(role["token_no_default_policy"]) + self.assertFalse(role["verbose_oidc_logging"]) + self.assertEqual(role["token_type"], "service") + self.assertEqual(role["token_num_uses"], 8) + for key in ("token_ttl", "token_max_ttl", "token_explicit_max_ttl"): + self.assertEqual(role[key], "5m") + self.assertNotIn("token_period", role) + + def test_only_self_endpoints_and_pinned_signature_verifier(self): + policy = (ROOT / "openbao/policies/secrets-engine-login-self.hcl").read_text() + grants = re.findall(r'path "([^"]+)"\s*{\s*capabilities = \["([^"]+)"\]', policy) + self.assertEqual(grants, [("auth/token/lookup-self", "read"), ("sys/capabilities-self", "update"), ("auth/token/revoke-self", "update")]) + self.assertNotIn("*", policy) + config = json.loads((ROOT / "openbao/auth/keycape-services-config.json").read_text()) + self.assertEqual(config, dict(jwks_url="https://kc.coulomb.social/jwks", bound_issuer="https://kc.coulomb.social", jwt_supported_algs=["RS256"])) diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md index 1f3a819..ee8332c 100644 --- a/workplans/RPF-WP-0035-credential-lane-implementation.md +++ b/workplans/RPF-WP-0035-credential-lane-implementation.md @@ -8,7 +8,7 @@ status: blocked flavor: implementation owner: codex created: "2026-09-05" -updated: "2026-09-15" +updated: "2026-09-27" related: - RPF-WP-0032 - RPF-WP-0033 @@ -56,8 +56,9 @@ and a metadata-only custody receipt. KeyCape owns issuer/JWKS and service registration (KEY-WP-0009); secrets-engine owns service authentication and authority consumption (SECRETS-WP-0008-T06, SECRETS-WP-0007-T04). -**Unblock:** confirmed HTTPS issuer/JWKS, exact claims and audience, consumer -readiness, approved source and attended apply authority. A service login does +**Unblock:** live registration and protected client custody, reviewed exact source +and attended apply authority. Issuer/JWKS and consumer source claims now agree; +see the 2026-09-27 return below. A service login does not grant lane mutation authority. Do not build another identity provider or lifecycle engine here. @@ -276,15 +277,16 @@ governed lane is the live consumer. Do not wrap `secret/coulomb/whynot-design/np in a CCR. Value remains unread. Platform will ask secrets-engine which path publish actually reads before any attended destroy. -**Unblock:** secrets-engine confirms which location their publish actually reads -and whether the two hold the same value; the owner of the legacy path is -identified; and a metadata-or-field-name read of the legacy path is admitted so -the duplicate can be characterised without reading its value. +**Unblock:** admit the governed exact-path AppRole policy and coordinated catalog +path/field migration under SECRETS-WP-0006-T06; prove native delivery from the +governed lane before the attended legacy destroy. secrets-engine confirmed the +legacy source path on 2026-09-21 (message `355424d4-17ab-435e-9e1d-6921775cb4a2`). +No comparison of values, fingerprints, lengths or shapes is needed or authorized. **Done when:** the legacy path's provenance and consumer are established, the governed lane is confirmed as the one in use or the consumer is moved to it as a -reviewed lane change, the duplicate is destroyed or brought under a CCR with an -owner, and the disposition is recorded. If the value proves to be live and +reviewed lane change, the duplicate is destroyed under the September 15 operator decision and the +disposition is recorded. Do not bring the legacy duplicate under a CCR. If the value proves to be live and ungoverned, treat it as an exposure with the same custody rules as RPF-WP-0027: never record the value, fingerprint, length or shape. @@ -679,3 +681,18 @@ verify and exec using scoped attended authority, and capture native denial, revocation, workload health and key-check evidence. No OpenRouter credential has been read and no inference or spend was performed. T03 remains waiting; this entry supersedes earlier statements that requester or group admission is missing. + +### 2026-09-27 T02/T07 source follow-up + +T02 now has the exact proposed RS256 configuration, bounded JWT role and +self-only policy in `openbao/`. Operator correction: platform infrastructure +belongs to `tenant:platform`; KeyCape registration and consumer preflight are +corrected together. Existing live registrations must be migrated and verified. +Public discovery confirms issuer/JWKS/token endpoint. Registration/custody, +attended provisioning and live rejection/cleanup evidence remain required; +T02 stays wait. No service credential or backend entitlement was issued. + +T07 consumed the confirmed legacy consumer return. The stale value-comparison +ask is removed. WARDEN-WP-0037-T03's no-rotation hold stays in force until the +governed native migration is evidenced. Legacy destruction follows migration; +neither source reconciliation nor the historical pilot is that evidence.