From 717a97408a09203c876940ec38e8a5b471cfd9c2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 27 Sep 2026 16:43:31 +0200 Subject: [PATCH] Correct platform infrastructure client tenant bindings Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e324-abce-7e51-bb2b-496f097afdb0 --- config/service-clients.example.yaml | 4 ++-- docs/openbao-service-auth-contract.md | 19 +++++++++++++++++++ docs/tenant-claim-contract.md | 8 +++++--- src/cmd/keycape/clients_test.go | 4 ++-- src/internal/config/config_test.go | 2 +- src/internal/server/oidc/tenant_test.go | 14 +++++++------- ...der-capabilities-and-service-identities.md | 12 +++++++++++- 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/config/service-clients.example.yaml b/config/service-clients.example.yaml index 9d74948..a4b13e5 100644 --- a/config/service-clients.example.yaml +++ b/config/service-clients.example.yaml @@ -9,7 +9,7 @@ clients: clientType: "confidential" secretRef: "env:KEYCAPE_CODEX_RAILIANCE_PLATFORM_CLIENT_SECRET" serviceSubject: "service:codex:railiance-platform" - tenant: "tenant:coulomb" + tenant: "tenant:platform" roles: ["coding-agent"] tokenLifetime: "15m" @@ -20,7 +20,7 @@ clients: clientType: "confidential" secretRef: "env:KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET" serviceSubject: "service:secrets-engine" - tenant: "tenant:coulomb" + tenant: "tenant:platform" roles: ["secrets-engine"] tokenLifetime: "15m" diff --git a/docs/openbao-service-auth-contract.md b/docs/openbao-service-auth-contract.md index 6ae4b63..3416a59 100644 --- a/docs/openbao-service-auth-contract.md +++ b/docs/openbao-service-auth-contract.md @@ -84,3 +84,22 @@ break-glass providers and require their own authorization and evidence. Verification must inspect metadata and capabilities without reading a secret value. A coding-agent check must prove the `agent-high-risk-boundary` deny wins against any otherwise readable workload policy. + +## Consumer/platform reconciliation — 2026-09-27 + +The operator clarified that platform infrastructure belongs to tenant zero, +**`tenant:platform`**, including OpenBao and its platform service identities. +Coulomb is a workload on the platform; `coulomb.social` is currently shared DNS, +not a tenant assignment. The former matching `tenant:coulomb` declarations were +wrong. `codex-railiance-platform` and `secrets-engine-openbao` now declare +`tenant:platform`, coordinated with the platform role and consumer preflight. +This source correction does not migrate live registrations or existing tokens. + +Public discovery at `https://kc.coulomb.social/.well-known/openid-configuration` +was rechecked: issuer `https://kc.coulomb.social`, JWKS `/jwks`, token endpoint +`/token`, with `client_credentials` advertised. This establishes public endpoint +metadata only, not that this confidential client is registered or provisioned. +Platform's RPF-WP-0035-T02 now carries the exact proposed login-only role/config/ +policy. The remaining joint acceptance is protected client custody and actual +registration, attended role provisioning, native signature/claim refusals and +OpenBao self-revocation. KEY-WP-0009's provider-contract completion is unchanged. diff --git a/docs/tenant-claim-contract.md b/docs/tenant-claim-contract.md index 1c013e0..9ab277b 100644 --- a/docs/tenant-claim-contract.md +++ b/docs/tenant-claim-contract.md @@ -18,8 +18,8 @@ never influenced by request parameters. | Principal | Source of `tenant` | Value in the reviewed registrations | | --- | --- | --- | -| Service (`client_credentials`) | The client's `tenant` field. Required by config validation — a `client_credentials` client without `serviceSubject` and `tenant` fails startup validation. | In `config/service-clients.example.yaml`: `tenant:platform` for `secrets-engine-approval` and `approval-engine-operator`; `tenant:coulomb` for `codex-railiance-platform` and `secrets-engine-openbao`. | -| Human (`authorization_code`) | The directory user's tenant, falling back to the platform default when unset. | Directory value, else `tenant:coulomb`. | +| Service (`client_credentials`) | The client's `tenant` field. Required by config validation — a `client_credentials` client without `serviceSubject` and `tenant` fails startup validation. | In `config/service-clients.example.yaml`: `tenant:platform` for the platform approval clients, `codex-railiance-platform` and `secrets-engine-openbao` (operator correction, 2026-09-27). | +| Human (`authorization_code`) | The directory user's tenant, falling back to the legacy workload default when unset. | Directory value, else `tenant:coulomb`. | `tenant`, `tenant_hint`, `audience` and `resource` request parameters cannot change the claim. `tenant_hint` on `/authorize` reaches registration/enrollment @@ -47,7 +47,9 @@ The decision changed the `tenant` field on exactly two registrations, entries in `docs/approval-engine-provisioning-request.yaml`. Deliberately unchanged: -- `codex-railiance-platform` and `secrets-engine-openbao` keep `tenant:coulomb`. +- Historical exception superseded 2026-09-27: `codex-railiance-platform` and + `secrets-engine-openbao` belong to `tenant:platform`; see the OpenBao contract. + Apply the issuer/role/consumer changes in a coordinated deployment window. - Human directory resolution keeps its `tenant:coulomb` default. - No cross-tenant grant is implied: a `tenant:platform` token conveys no reach into `tenant:coulomb` resources, and audiences, scopes, subjects, roles, diff --git a/src/cmd/keycape/clients_test.go b/src/cmd/keycape/clients_test.go index 4c7f143..5b8e179 100644 --- a/src/cmd/keycape/clients_test.go +++ b/src/cmd/keycape/clients_test.go @@ -57,8 +57,8 @@ func TestServiceRegistrationTenantsAreExactPerDecision(t *testing.T) { want := map[string]string{ "secrets-engine-approval": "tenant:platform", "approval-engine-operator": "tenant:platform", - "codex-railiance-platform": "tenant:coulomb", - "secrets-engine-openbao": "tenant:coulomb", + "codex-railiance-platform": "tenant:platform", + "secrets-engine-openbao": "tenant:platform", // The only browser client here, and the only registration-supplied // human tenant. Reviewed and added deliberately: this guard exists so a // new client carrying a tenant cannot arrive unnoticed, and it did its diff --git a/src/internal/config/config_test.go b/src/internal/config/config_test.go index 0dc8bff..f1ab59b 100644 --- a/src/internal/config/config_test.go +++ b/src/internal/config/config_test.go @@ -393,7 +393,7 @@ func TestServiceClientExampleContracts(t *testing.T) { codingAgent := cfg.Clients[0] if codingAgent.ClientID != "codex-railiance-platform" || codingAgent.ServiceSubject != "service:codex:railiance-platform" || - codingAgent.Tenant != "tenant:coulomb" || + codingAgent.Tenant != "tenant:platform" || codingAgent.TokenLifetime != "15m" { t.Fatalf("coding-agent contract drifted: %+v", codingAgent) } diff --git a/src/internal/server/oidc/tenant_test.go b/src/internal/server/oidc/tenant_test.go index 4418381..1517610 100644 --- a/src/internal/server/oidc/tenant_test.go +++ b/src/internal/server/oidc/tenant_test.go @@ -118,7 +118,7 @@ func TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases(t *testing.T) GrantTypes: []string{"client_credentials"}, ClientType: "confidential", ClientSecret: "approval-test-secret", - ServiceSubject: "service:secrets-engine", + ServiceSubject: "service:coulomb-workload-test", Tenant: "tenant:platform", Roles: []string{"secrets-engine"}, } @@ -147,22 +147,22 @@ func TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases(t *testing.T) } } -// The aligned decision grants no cross-tenant reach: the unrelated OpenBao -// login client keeps tenant:coulomb. +// Platform ownership does not elevate workload clients. Request parameters +// cannot change a workload registration's tenant. func TestUnrelatedServiceClientKeepsCoulombTenant(t *testing.T) { h := serviceTokenHandler(t) - h.ClientConfig["secrets-engine-openbao"] = &domain.Client{ - ClientID: "secrets-engine-openbao", + h.ClientConfig["coulomb-workload-test"] = &domain.Client{ + ClientID: "coulomb-workload-test", AllowedScopes: []string{"openbao:login"}, GrantTypes: []string{"client_credentials"}, ClientType: "confidential", ClientSecret: "openbao-test-secret", - ServiceSubject: "service:secrets-engine", + ServiceSubject: "service:coulomb-workload-test", Tenant: "tenant:coulomb", Roles: []string{"secrets-engine"}, } req := tokenRequest(url.Values{"grant_type": {"client_credentials"}, "scope": {"openbao:login"}, "tenant": {"tenant:platform"}}) - req.SetBasicAuth("secrets-engine-openbao", "openbao-test-secret") + req.SetBasicAuth("coulomb-workload-test", "openbao-test-secret") w := httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusOK { diff --git a/workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md b/workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md index 27fb467..c9e585d 100644 --- a/workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md +++ b/workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md @@ -8,7 +8,7 @@ status: finished owner: codex topic_slug: netkingdom created: "2026-08-23" -updated: "2026-09-08" +updated: "2026-09-27" depends_on: - NK-WP-0030 - KEY-WP-0006 @@ -126,3 +126,13 @@ disablement and the claim set; secret custody, OpenBao roles/policies and token enforcement, privacyIDEA factor and token lifecycle, resource authorization and deployment remain with their named owners. C2a is not claimed. This closes the notification gap; it is still not evidence of live deployment or provider cutover. + +### 2026-09-27 operator tenant-zero correction + +OpenBao infrastructure identities belong to `tenant:platform`, not Coulomb. +Corrected both static infrastructure registrations, matching source tests and +the tenant/OpenBao contracts. Consumer preflight and platform role sources are +updated together; no live registration or token migration is claimed. The +provider contract remains complete; RPF-WP-0035-T02 and SECRETS-WP-0008-T06 +retain coordinated custody/deployment and native rejection/revocation proof. +Human fallback and workload tenant grants are not elevated to platform.