# OpenBao Approved Automation Delegation

This document specifies the narrow OpenBao metadata surface that approved
credential-change automation may mutate. It exists to avoid routine use of broad
`platform-admin` while keeping secret values under operator custody.

## Scope

The delegated applier is for reviewed metadata only:

- ACL policies generated from approved CCRs;
- auth roles bound to reviewed OIDC claims or Kubernetes service accounts;
- credential-broker issuer policies and token roles generated from reviewed
  grant catalog entries;
- readback and capability checks needed to prove the mutation landed.

It must not read, write, print, wrap, unwrap, or proxy managed secret values.
Production secret provisioning remains an attended OpenBao/operator custody
step unless a later workplan approves a stronger dual-control flow.

## Environment Boundaries

Build and development may use sandbox metadata once a non-production OpenBao
mount or namespace is declared. Generated test secrets must stay in the sandbox
and must never be copied into State Hub, prompts, Git, or chat.

The non-production applier policy candidate is
`openbao/policies/credential-change-nonprod-applier.hcl`. It currently grants
only metadata writes, matching the no-secret-value rule used in production.
Any future generated test-secret path needs a separate CCR-backed approval so
it cannot silently expand this delegation.

Test and staging may apply reviewed metadata after owner review. Verification
must include positive and negative access checks, and evidence must be
non-secret.

Production may apply only reviewed non-secret metadata. The production applier
policy is `openbao/policies/credential-change-prod-applier.hcl`, and every live
run must be preceded by `scripts/credential-change.py applier-dry-run <CCR>`.
Unapproved CCRs fail closed before any OpenBao mutation is rendered. Live
metadata mutation uses `scripts/credential-change.py applier-apply <CCR>` with
an exact `DELEGATED APPLY <CCR-ID>` confirmation phrase and the local `bao` CLI
under ambient delegated applier authority; the command does not accept OpenBao
tokens in argv.

## Production Mutation Surface

| Change class | Allowed OpenBao path | Notes |
| --- | --- | --- |
| Workload KV read policies | `sys/policies/acl/workload-kv-read-*` | Generated from CCR mount/path/field metadata. |
| Credential broker issuer policies | `sys/policies/acl/credential-broker-*-issuer` | Generated from grant catalog metadata. |
| OIDC workload roles | `auth/netkingdom/role/*` | Bound claims and workload role names must be confirmed by the local dry-run before apply. |
| Kubernetes workload roles | `auth/kubernetes/role/*` | Bound service accounts/namespaces must be confirmed before apply. |
| Credential broker token roles | `auth/token/roles/credential-broker-*` | Child-token roles only; no root or platform-admin policies. |
| Self checks | `auth/token/lookup-self`, `sys/capabilities-self` | Read/update only as required by OpenBao. |

Denied by omission:

- `platform/data/*`, `platform/metadata/*`, or any other secret value path;
- `sys/*` outside the approved ACL policy prefixes;
- `auth/*` outside the approved role prefixes;
- `identity/*`, unseal/recovery material, audit devices, mounts, and root/admin
  policy assignment;
- wildcard, parent-directory, or mismatched policy and role names.

## Local Dry-Run Guardrails

The CCR dry-run is deliberately stricter than the OpenBao ACL policy. It must:

1. validate the CCR schema and secret-marker scan;
2. require CCR status `approved`, `applied`, `verified`, or `active`;
3. require `openbao.auth.bound_claims_confirmed=true`;
4. require mount `platform` and path `platform/workloads/...` for workload KV
   requests;
5. require policy names to start with `workload-kv-read-` and remain under
   `openbao/policies/<policy-name>.hcl`;
6. require OIDC roles to stay under `auth/netkingdom/role/*-workload-kv-read`;
7. require Kubernetes roles to stay under `auth/kubernetes/role/*-workload-kv-read`
   or `auth/kubernetes/role/*-secrets-read`;
8. render only exact policy and auth-role metadata mutations;
9. leave secret value writes and front-door activation out of scope.

`applier-apply` reuses the same guardrails, renders the dry-run payload before
mutation, requires exact confirmation, writes only policy/auth-role metadata,
and appends non-secret `delegated_metadata_apply` evidence. For approved CCRs it
can advance file-backed status to `applied`; for already applied/verified/active
CCRs it records idempotent evidence without moving the lifecycle backward.

## Required Evidence

Record only non-secret evidence:

- CCR id and approval/decision reference;
- applier identity and timestamp;
- policy name and auth role path;
- OpenBao request id or audit timestamp;
- positive and negative verification references;
- front-door activation confirmation after verification.

## Applier Identity Setup

`openbao-apply-credential-change-appliers.py` configures the source-owned
metadata applier policies and matching OpenBao token roles:

- `credential-change-nonprod-applier` uses
  `openbao/policies/credential-change-nonprod-applier.hcl`;
- `credential-change-prod-applier` uses
  `openbao/policies/credential-change-prod-applier.hcl`.

The token roles allow only their matching applier policy, explicitly disallow
`root` and `platform-admin`, disable the default policy, use service tokens,
and do not issue tokens by themselves. Token issuance remains an approved
custody path outside this setup script. Use
`make openbao-credential-change-appliers-dry-run` before any live apply.

## Current Production Policy Candidate

`openbao/policies/credential-change-prod-applier.hcl` is the source candidate
for a future production applier identity. It is not a substitute for CCR review;
it is the OpenBao-side capability envelope used after local dry-run validation.

## Current Non-Production Policy Candidate

`openbao/policies/credential-change-nonprod-applier.hcl` is the source
candidate for a build/test/staging applier identity. It is intentionally
metadata-only until this repo declares a non-production OpenBao mount or
namespace and records live positive/negative evidence for that lane.
