Files
railiance-platform/docs/credential-broker.md

12 KiB

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:

credential-grants/catalog.yaml

Validate it with:

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:

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:

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:

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:

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:

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:

make credential-helper-dry-run

Pass helper global options before the subcommand. For example, if the OpenBao pod has an approved token helper session:

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:

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:

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:

scripts/credential.py revoke <lease-accessor>

When using local-token-file, remove stale local lease material after revoke or expiry:

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.