Prepare tenant-zero OpenBao roles and review platform essentials
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e324-abce-7e51-bb2b-496f097afdb0
This commit is contained in:
codex 2026-09-27 16:43:32 +02:00
parent 4f20e755c4
commit 5781d34b3b
10 changed files with 179 additions and 14 deletions

View file

@ -1,6 +1,6 @@
# Secrets-engine service JWT login # 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`. Demand: State Hub message `38b47122-07eb-4a7f-a5df-13a38f50e110`.
## Contract and ownership ## Contract and ownership
@ -20,11 +20,11 @@ or the coding-agent mount to introduce this service.
| Setting | Proposed value / evidence required | | 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` | | Role type / user claim | `jwt` / `sub` |
| Audience | `secrets-engine-openbao` | | Audience | `secrets-engine-openbao` |
| Subject | `service:secrets-engine` | | 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 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 | | 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 | | 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/) 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 defines token TTL/use limits and the explicit maximum. These are mechanism
references, not evidence of this cluster's installed configuration. 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 -- <reviewed-command>`.
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.

View file

@ -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.

View file

@ -256,3 +256,12 @@ renumbered.
Six months, or on any of: a service moving placement level, a backup target Six months, or on any of: a service moving placement level, a backup target
becoming available, or the framework reaching `accepted`. Next review due becoming available, or the framework reaching `accepted`. Next review due
**2027-02-17**. **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).

View file

@ -8,7 +8,7 @@
"bound_claims": { "bound_claims": {
"sub": "service:codex:railiance-platform", "sub": "service:codex:railiance-platform",
"principal_type": "service", "principal_type": "service",
"tenant": "tenant:coulomb", "tenant": "tenant:platform",
"roles": "coding-agent" "roles": "coding-agent"
}, },
"token_policies": [ "token_policies": [

View file

@ -0,0 +1,7 @@
{
"jwks_url": "https://kc.coulomb.social/jwks",
"bound_issuer": "https://kc.coulomb.social",
"jwt_supported_algs": [
"RS256"
]
}

View file

@ -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
}

View file

@ -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"]
}

View file

@ -247,7 +247,7 @@ class CredentialChangeTests(unittest.TestCase):
{ {
"sub": "service:codex:railiance-platform", "sub": "service:codex:railiance-platform",
"principal_type": "service", "principal_type": "service",
"tenant": "tenant:coulomb", "tenant": "tenant:platform",
"roles": "coding-agent", "roles": "coding-agent",
}, },
) )

View file

@ -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"]))

View file

@ -8,7 +8,7 @@ status: blocked
flavor: implementation flavor: implementation
owner: codex owner: codex
created: "2026-09-05" created: "2026-09-05"
updated: "2026-09-15" updated: "2026-09-27"
related: related:
- RPF-WP-0032 - RPF-WP-0032
- RPF-WP-0033 - 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 registration (KEY-WP-0009); secrets-engine owns service authentication and
authority consumption (SECRETS-WP-0008-T06, SECRETS-WP-0007-T04). authority consumption (SECRETS-WP-0008-T06, SECRETS-WP-0007-T04).
**Unblock:** confirmed HTTPS issuer/JWKS, exact claims and audience, consumer **Unblock:** live registration and protected client custody, reviewed exact source
readiness, approved source and attended apply authority. A service login does 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 not grant lane mutation authority. Do not build another identity provider or
lifecycle engine here. 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 in a CCR. Value remains unread. Platform will ask secrets-engine which path
publish actually reads before any attended destroy. publish actually reads before any attended destroy.
**Unblock:** secrets-engine confirms which location their publish actually reads **Unblock:** admit the governed exact-path AppRole policy and coordinated catalog
and whether the two hold the same value; the owner of the legacy path is path/field migration under SECRETS-WP-0006-T06; prove native delivery from the
identified; and a metadata-or-field-name read of the legacy path is admitted so governed lane before the attended legacy destroy. secrets-engine confirmed the
the duplicate can be characterised without reading its value. 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 **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 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 reviewed lane change, the duplicate is destroyed under the September 15 operator decision and the
owner, and the disposition is recorded. If the value proves to be live and 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: ungoverned, treat it as an exposure with the same custody rules as RPF-WP-0027:
never record the value, fingerprint, length or shape. 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 revocation, workload health and key-check evidence. No OpenRouter credential has
been read and no inference or spend was performed. T03 remains waiting; this been read and no inference or spend was performed. T03 remains waiting; this
entry supersedes earlier statements that requester or group admission is missing. 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.