Document the one recipe a new rapp uses to acquire runtime secrets: standing KV secrets bind through a CCR target.rapp, leases through grant rapp_id. Stamp the existing postgres grants and the qonto workload CCR. Gate, delivery, and revocation are unchanged.
8.1 KiB
Rapp credential-lane binding
Work record: RAILIANCE-WP-0015-T06
Owner: railiance-platform (S3)
This is the single recipe a new rapp uses to acquire runtime secrets. It binds the existing S3 lanes. It does not invent a package-local broker, change grant TTLs, delivery modes, approval, or revocation.
The rule
A rapp declares what it needs. S3 vends it through the lanes that already exist. The package never commits a credential, never holds a grant catalog, and never files a CCR in the rapp repo.
rapp.yaml S3 lane object live delivery
───────── ────────────── ─────────────
runtime_dependencies[] ──► capability (who satisfies it
is not the rapp's problem)
secret_references[] ──► CCR (standing KV secret)
openbao.kv_path == the reference
workload lane: delivery.surface
is external-secrets / kubernetes
consumer_contract ──► grant catalog (short-lived lease)
+ consumers[] id: <rapp_id>/<consumer>-<role>
rapp_id: <rapp_id>
Two existing lane objects. Not a third.
| Need | Lane | Home | How the rapp names it |
|---|---|---|---|
| Standing secret (API key, provider token, webhook secret) | Credential change request | credential-change-requests/CCR-*.yaml |
secret_references lists the OpenBao path |
| Short-lived lease (dynamic DB password, bounded OpenBao token) | Grant catalog | credential-grants/catalog.yaml |
grant id is <rapp_id>/<consumer>-<role> and grant rapp_id equals the package |
Operator/human fetch of the same KV path is a separate CCR
(delivery.surface: operator-workstation). It may share the path. It is
not the rapp's runtime bind.
What each rapp field means
runtime_dependencies names capabilities. The schema is explicit:
the list does not say who provisions the thing that satisfies a name.
openbao-database-secrets-engine and s3-backup-target are capabilities.
The database engine is configured by S3 from statements the package
owns; the backup bucket is a resource-control resource and the
provider credential is S3 custody (Secret platform-pg-backup-s3).
Fail-closed until that handoff lands is correct, not a hole in the
package.
secret_references names OpenBao paths, never values. Each path on
a placed rapp must have exactly one workload CCR whose
openbao.kv_path matches. "Workload" means the CCR's delivery surface
is the in-cluster consumer (External Secrets or Kubernetes auth), not
an operator workstation. A rapp with no standing KV secret omits the
field.
consumer_contract.credential_lane names the broker, not a grant.
Today that value is railiance-platform-credential-broker. Individual
consumer leases are catalog entries bound by rapp_id.
Choose the lane
- Is the secret a long-lived value that a pod must hold across
restarts? File a CCR. Delivery is External Secrets into a
namespace-scoped Secret. The CCR is the approval, policy, auth role,
and ops-warden front door. Follow
docs/credential-change-approval.mdanddocs/credential-lane-lifecycle-runbook.md. - Is the secret something that should exist only for the length of a
connection or an attended command? Add a grant. Delivery is
scripts/credential.py exec(or in-cluster database/creds). Followdocs/credential-broker.md. - Never both for the same need. A CCR is not a grant with a longer TTL. A grant is not a KV write.
Do not open a package-local OpenBao policy, a rapp-owned Secret manifest with a value, or a second catalog.
Binding fields
These fields are pointers. They do not change who may approve, how a lease is minted, or how it is revoked.
Grant catalog — required when the grant id starts with rapp-:
id: rapp-postgres/audit-core-runtime
rapp_id: rapp-postgres
rapp_id must equal the id prefix before /. The validator enforces
that. Grants that are not package-owned (ops-warden/warden-sign) omit
the field.
CCR — set when the lane is the runtime bind for a rapp:
target:
workload: qonto-assistant # existing; the running unit
rapp: rapp-qonto # optional; the package that owns rollout
target.rapp is optional because most live CCRs belong to workloads
that are not yet packages. Adding it is part of extracting a rapp, not
a reason to invent a CCR. The validator accepts the field and rejects a
value that is not a rapp-* slug.
target.workload stays the workload identity. It must not become the
repo slug.
Recipe for a new rapp
Do these in order. Stop if a step would change a gate, a TTL, a delivery mode, or a revocation rule — that work is a CCR or a grant change, not a rapp-local patch.
-
Write
runtime_dependenciesfor every capability the package assumes, including secret-bearing ones. -
Decide standing-secret vs lease for each credential need.
-
Standing secret: put the OpenBao path in
secret_references. File the CCR in this repo (scripts/credential-change.py), settarget.rapp, and keep policy HCL here. The rapp repo may hold only the ExternalSecret that consumes the store S3 applies. -
Lease: add a grant whose id is
<rapp_id>/<consumer>-<role>and whoserapp_idmatches. For a provisioning rapp, declare the consumer inconsumers:and keepconsumer_contract.credential_lane: railiance-platform-credential-broker. -
Validate without minting anything:
make credential-grants-validate make credential-change-validate -
Apply and verify through the existing attended helpers. Do not add a rapp-local apply path.
A workload that is not yet a rapp keeps using a CCR or grant with no
rapp / rapp_id. Extraction adds the pointer; it does not restamp
the secret.
Worked examples
rapp-openbao — the store, not a consumer
No secret_references, no grant, no CCR. OpenBao is the custody
engine. Package-owned smoke stays non-secret. Authenticated policy and
audit checks stay in S3. A later platform rapp must not copy this
shape unless it is a secrets engine.
rapp-postgres — provisioning rapp, lease lane
runtime_dependenciesincludesopenbao-database-secrets-engineands3-backup-target.consumer_contract.credential_laneis the broker.- Catalog grants
rapp-postgres/audit-core-runtimeandrapp-postgres/audit-core-migrationcarryrapp_id: rapp-postgres. - No
secret_references: the package does not read a KV path. Consumers receive a short-liveddatabase/creds/<role>lease. s3-backup-targetis unsatisfied untilresource-controlhands an endpoint.make postgres-backup-deploystays fail-closed. That is not a missing CCR on this package.
rapp-qonto — consuming rapp, standing-secret lane
secret_references: [tenants/binky/qonto-api].- Workload CCR:
CCR-2026-0009(delivery.surface: external-secrets,target.rapp: rapp-qonto). That is the runtime bind. - Operator CCR:
CCR-2026-0008(same path, workstation delivery). Not the rapp bind. Rotating the value rotates both lanes because they share the secret; deactivating the operator front door must not be mistaken for taking the workload offline.
Current bind table
| Rapp | Declaration | S3 lane | Status |
|---|---|---|---|
rapp-openbao |
none | none — package is the store | correct |
rapp-postgres |
consumer_contract + openbao-database-secrets-engine |
rapp-postgres/audit-core-runtime, rapp-postgres/audit-core-migration |
bound |
rapp-postgres |
s3-backup-target |
no lane yet; Secret platform-pg-backup-s3 is S3 custody after RESOURCE-WP-0002 |
fail-closed on purpose |
rapp-qonto |
secret_references: tenants/binky/qonto-api |
CCR-2026-0009 |
pointer set; CCR itself is still proposed |
Live CCRs without target.rapp are un-rapped workloads. They stay on
the CCR lane. They do not get a grant or a package invented to make
this table look complete.