307 lines
12 KiB
Markdown
307 lines
12 KiB
Markdown
# Credential Request And Lease Broker
|
|
|
|
**Workplan:** `RAILIANCE-WP-0005`
|
|
**Owner:** `railiance-platform`
|
|
**Status:** source implementation complete; live verification pending approved token path
|
|
|
|
This document records the Railiance credential broker ownership decision and
|
|
the first implementation contract for short-lived OpenBao credential leases.
|
|
|
|
## Decision
|
|
|
|
`railiance-platform` owns OpenBao credential request, generation, delivery,
|
|
audit, and revocation because this repo owns the platform secrets service and
|
|
the OpenBao policy surface. The broker may later split into a dedicated
|
|
service repo if the implementation grows, but the grant catalog and OpenBao
|
|
policy contracts remain platform-owned.
|
|
|
|
The broker is not a new secret store. It is a controlled request path for
|
|
bounded credentials that already belong to OpenBao or adjacent platform
|
|
authorities.
|
|
|
|
## Boundaries
|
|
|
|
| Concern | Owner | Boundary |
|
|
| --- | --- | --- |
|
|
| OpenBao mounts, policies, token roles, response wrapping, audit | `railiance-platform` | Generates and revokes bounded credentials. |
|
|
| Human login, OIDC, MFA, IAM profile claims | `key-cape` | Authenticates human and service identities. |
|
|
| Authorization decision | `flex-auth` | Decides whether an actor may request a grant for a purpose, TTL, audience, and delivery mode. |
|
|
| SSH certificate signing | `ops-warden` | Issues SSH certificates only. It does not vend OpenBao tokens, API keys, or provider secrets. |
|
|
| Request tracking | State Hub | Stores non-secret metadata only: request ids, actor, grant, purpose, TTL, decision id, lease accessor, status, timestamps, and audit pointers. |
|
|
| Agent/runtime consumption | `llm-connect` and callers | Never place secrets in prompts. Consume credentials through local exec injection, response wrapping, service-account auth, or approved local files. |
|
|
|
|
## Non-Secret Metadata Only
|
|
|
|
State Hub, workplans, docs, Git, chat, and prompts may contain:
|
|
|
|
- grant ids such as `ops-warden/warden-sign`;
|
|
- requested TTL and bounded max TTL;
|
|
- actor and subject ids;
|
|
- purpose strings;
|
|
- lease handles or accessors when they are not sufficient to use the secret;
|
|
- OpenBao audit request ids or timestamps;
|
|
- status values such as requested, issued, denied, revoked, or expired.
|
|
|
|
They must not contain:
|
|
|
|
- OpenBao root tokens, platform-admin tokens, or wrapped token values;
|
|
- unseal shares, recovery codes, private keys, OTP seeds, passwords, or API keys;
|
|
- raw bearer tokens in command lines, prompt text, State Hub bodies, or logs;
|
|
- screenshots or pasted command output containing secret values.
|
|
|
|
## Grant Catalog
|
|
|
|
The catalog lives at:
|
|
|
|
```text
|
|
credential-grants/catalog.yaml
|
|
```
|
|
|
|
Validate it with:
|
|
|
|
```bash
|
|
make credential-grants-validate
|
|
```
|
|
|
|
Every grant entry defines:
|
|
|
|
- a stable grant id;
|
|
- credential type and OpenBao policy set;
|
|
- grant class: `self-service`, `approval-required`, or `break-glass`;
|
|
- default and max TTL;
|
|
- allowed actor types and purpose examples;
|
|
- allowed and denied delivery modes;
|
|
- audit and revocation expectations.
|
|
|
|
The first pilot grant is `ops-warden/warden-sign`, which creates a short-lived
|
|
OpenBao token with only the `warden-sign` policy.
|
|
|
|
## OpenBao Token Roles
|
|
|
|
OpenBao-token grants are configured from source with:
|
|
|
|
- an issuer policy under `openbao/policies/`;
|
|
- an `auth/token/roles/<role>` token role with allowed policies, disallowed
|
|
admin policies, non-renewable TTL bounds, no default policy, and orphan token
|
|
issuance;
|
|
- verification that reads the issuer policy, token role, and target workload
|
|
policy before any smoke token is minted.
|
|
|
|
Dry-run the current grant configuration with:
|
|
|
|
```bash
|
|
make openbao-token-grants-dry-run
|
|
make openbao-verify-token-grants-dry-run
|
|
```
|
|
|
|
Live application uses an operator-approved OpenBao token from
|
|
`OPENBAO_TOKEN_FILE` or an interactive hidden prompt. The token is passed to the
|
|
OpenBao pod through stdin, never through argv:
|
|
|
|
```bash
|
|
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-configure-token-grants
|
|
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-verify-token-grants
|
|
```
|
|
|
|
The smoke verifier can mint a short-lived child token, confirm that it can list
|
|
`ssh/roles`, confirm that it cannot list unrelated secret engines, and revoke
|
|
the token by accessor:
|
|
|
|
```bash
|
|
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-verify-token-grants-smoke
|
|
```
|
|
|
|
## Delivery Modes
|
|
|
|
`exec-env` is the preferred local path. The helper obtains a lease, injects
|
|
the credential only into a child process environment, redacts output, and then
|
|
revokes or lets the credential expire.
|
|
|
|
`response-wrap` is for attended handoff. The broker returns a single-use
|
|
OpenBao wrapping token instead of the raw credential. The recipient unwraps it
|
|
once; a second unwrap must fail.
|
|
|
|
`local-token-file` is for tools that cannot consume environment variables
|
|
cleanly. Files must be mode `0600`, stored under `.local/credential-leases/`,
|
|
and removed when the lease is revoked or expires. That directory is ignored by
|
|
Git.
|
|
|
|
`kubernetes-auth` is for in-cluster workloads. Workloads should authenticate
|
|
with service-account-bound auth instead of receiving manually handed tokens.
|
|
For the pilot grant, `request --delivery kubernetes-auth` returns only
|
|
non-secret OpenBao auth metadata such as the auth mount, role, service account
|
|
names, and namespaces; it does not mint or print a bearer token.
|
|
|
|
The denied modes are absolute unless a later ADR updates the catalog:
|
|
|
|
- `chat`
|
|
- `state-hub-body`
|
|
- `git`
|
|
- `command-line-token-argument`
|
|
- `llm-prompt`
|
|
|
|
## Pilot Flow
|
|
|
|
The target ops-warden smoke path is:
|
|
|
|
```bash
|
|
credential exec --grant ops-warden/warden-sign --ttl 15m -- \
|
|
SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh
|
|
```
|
|
|
|
The source helper MVP lives at `scripts/credential.py` until this flow graduates
|
|
into a packaged command. It supports the same core shape:
|
|
|
|
```bash
|
|
scripts/credential.py request --grant ops-warden/warden-sign --purpose flex-auth-openbao-smoke
|
|
scripts/credential.py exec --grant ops-warden/warden-sign --purpose flex-auth-openbao-smoke -- \
|
|
SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh
|
|
scripts/credential.py status <lease-accessor>
|
|
scripts/credential.py revoke <lease-accessor>
|
|
```
|
|
|
|
`request` defaults to `local-token-file`: the raw child token is written only to
|
|
`.local/credential-leases/` with mode `0600`, and stdout contains the lease
|
|
handle/accessor plus metadata. `--delivery response-wrap` returns an OpenBao
|
|
wrapping token for attended handoff, not the raw child token.
|
|
|
|
`exec` mints a bounded child token, injects it as `VAULT_TOKEN` only into the
|
|
child process environment, redacts token-looking output, and revokes the token
|
|
by accessor when the child exits. The helper rejects caller-supplied
|
|
`VAULT_TOKEN`/`BAO_TOKEN` env assignments and unsafe OpenBao debug/trace log
|
|
settings.
|
|
|
|
Dry-run all helper paths with:
|
|
|
|
```bash
|
|
make credential-helper-dry-run
|
|
```
|
|
|
|
Pass helper global options before the subcommand. For example, if the OpenBao
|
|
pod has an approved token helper session:
|
|
|
|
```bash
|
|
make credential-exec-ops-warden-smoke CREDENTIAL_HELPER_GLOBAL_ARGS=--use-token-helper
|
|
```
|
|
|
|
The child process receives `VAULT_TOKEN` in its environment. The token is not
|
|
printed, written to shell history, sent to State Hub, or placed in an LLM
|
|
prompt.
|
|
|
|
## Identity And Authorization
|
|
|
|
The helper records the following non-secret request identity fields:
|
|
|
|
- `actor`: the requester identity, defaulting to `codex:<local-user>`;
|
|
- `actor_type`: one of the grant-approved actor classes such as
|
|
`human-operator`, `approved-agent`, or `ci-runner`;
|
|
- `subject`: the bound human, agent, CI, or Kubernetes service-account subject.
|
|
|
|
Human operators should use the KeyCape/OIDC path with MFA when the grant class
|
|
or purpose requires it. Agents and CI runners should use stable subject strings
|
|
that can be mapped to IAM profile claims, for example
|
|
`agent:codex/railiance-platform` or
|
|
`system:serviceaccount:<namespace>:<service-account>`. Headless automation must
|
|
use Kubernetes auth or an explicitly approved non-interactive identity; it must
|
|
not reuse a human OpenBao token.
|
|
|
|
The helper performs local catalog checks before any issuance:
|
|
|
|
- purpose is required;
|
|
- requested TTL must not exceed the grant max TTL;
|
|
- delivery mode must be allowed by the grant;
|
|
- actor type must be allowed by the grant.
|
|
|
|
Optional flex-auth preflight is enabled with `--flex-auth-url` or `FLEX_AUTH_URL`.
|
|
The helper posts non-secret request metadata to
|
|
`/credential-grants/authorize` by default and accepts allow/deny responses using
|
|
`allowed`, `decision`, or `status` fields plus optional `decision_id` and
|
|
`reason`. Use `--require-flex-auth` when local preauthorization is not
|
|
acceptable. Use `--decision-id` to carry an already-approved external decision
|
|
without calling flex-auth again.
|
|
|
|
## State Hub Metadata
|
|
|
|
State Hub recording is opt-in through `--record-state-hub` or
|
|
`CREDENTIAL_RECORD_STATE_HUB=1`. The helper writes request lifecycle notes to
|
|
`/progress/` with non-secret metadata only:
|
|
|
|
- grant id, actor, actor type, subject, purpose, requested TTL, delivery mode;
|
|
- authorization mode, decision id, and decision reason;
|
|
- lease accessor, wrapping accessor, or wrapped accessor when available;
|
|
- status values such as `requested`, `issued`, `wrapped`, `revoked`, or
|
|
`delegated`.
|
|
|
|
It never records raw child tokens, wrapping tokens, token files, passwords,
|
|
OpenBao root/platform-admin tokens, or command output.
|
|
|
|
## Verification And Revocation
|
|
|
|
Offline checks:
|
|
|
|
```bash
|
|
make credential-grants-validate
|
|
make credential-tests
|
|
make openbao-token-grants-dry-run
|
|
make openbao-verify-token-grants-dry-run
|
|
make credential-helper-dry-run
|
|
```
|
|
|
|
Live source-owned checks, once an approved OpenBao issuer token path exists:
|
|
|
|
```bash
|
|
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-configure-token-grants
|
|
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make openbao-verify-token-grants-smoke
|
|
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token make credential-exec-ops-warden-smoke
|
|
```
|
|
|
|
Emergency revocation by accessor:
|
|
|
|
```bash
|
|
scripts/credential.py revoke <lease-accessor>
|
|
```
|
|
|
|
When using `local-token-file`, remove stale local lease material after revoke or
|
|
expiry:
|
|
|
|
```bash
|
|
find .local/credential-leases -type f -maxdepth 1 -print
|
|
```
|
|
|
|
Response wrapping live verification is manual until a richer integration test
|
|
exists: unwrap the returned wrapping token once with OpenBao, confirm the second
|
|
unwrap attempt fails, then revoke the wrapped child token by accessor.
|
|
|
|
## Routing And Rollout
|
|
|
|
Credential routing remains split by responsibility:
|
|
|
|
- `ops-warden` signs SSH certificates only;
|
|
- OpenBao token or dynamic-lease needs route to `railiance-platform`;
|
|
- login/MFA routes to KeyCape;
|
|
- authorization decisions route to flex-auth.
|
|
|
|
The rollout sequence is:
|
|
|
|
1. `ops-warden/warden-sign` pilot for the flex-auth/ops-warden smoke.
|
|
2. Platform-readonly token helper for diagnostics.
|
|
3. Workload-specific grants for application repositories.
|
|
4. Optional split to a dedicated credential-broker repo if the helper grows
|
|
beyond platform ownership.
|
|
|
|
The workplan can close only after the live warden-sign pilot runs through the
|
|
helper and the credential routing catalog returns this railiance-platform flow
|
|
for VAULT_TOKEN/OpenBao-token requests.
|
|
|
|
## Implementation Sequence
|
|
|
|
1. Validate and maintain the non-secret grant catalog.
|
|
2. Add bounded OpenBao token role configuration for each OpenBao-token grant.
|
|
3. Build a small helper that supports `request`, `exec`, `status`, and `revoke`.
|
|
4. Add optional flex-auth preflight and State Hub request lifecycle metadata.
|
|
5. Update ops-warden routing so OpenBao token needs point here, while SSH certificate issuance remains in ops-warden.
|
|
|
|
Live token issuance requires an approved operator path to create or use the
|
|
non-root issuer capability. Source-only validation and dry-run helper behavior
|
|
must remain useful without a live token.
|