secrets-engine delivered the action vocabulary T01 asked for: twelve actions read out of cli.py, not the example vocabulary. The gate earned its keep -- four would have been inferred wrongly, and revoke, the obvious thirteenth, does not exist as an action at all. It gates as deactivate, which is also reached from lifecycle deactivate. docs/secrets-engine-action-vocabulary.md records the list and the four traps. examples/secrets-engine/ carries the package, both manifests, a loadable registry snapshot, 26 fixtures, five check requests, and a README. validate -kind policy is valid with 22/22 Rego tests and 26/26 fixtures. allow_ttl is 15m, stated in the package rather than inherited from the engine default: these decisions authorize live secret operations, so the reliance window belongs where a reviewer sees it. Per FLEX-DEC-2026-004 it is authority to issue, not authority to keep using what the operation produced. destroy is the dual-control case, gated on a context.approval claim marked approved with two distinct approvers, repeated entries counting once. flex-auth checks what the claim says and deliberately does not re-derive its temporal validity, signature, or supersession -- those are approval-engine's to assert and the PEP's to verify against the live claim, per the split accepted in FLEX-DEC-2026-006. It stays in the package though its handler raises before the gate, so the rule is reviewed and fixtured before SECRETS-WP-0007-T04 opens the path. Following the FLEX-WP-0010-T02 precedent the denial ladder has no action_not_granted branch: one subject holding all twelve actions could never reach it, and a rule that cannot fail reads as control that is not there. Registering a second calling identity is the revisit trigger. Not deployed. No flex-auth-secrets-engine pin exists yet (T04), so their policy pin stays unset and fail-closed until T05. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB Assistant: claude-code Assistant-Model: opus Assistant-Process: 412054@bnt-lap001 Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
91 lines
4.1 KiB
Markdown
91 lines
4.1 KiB
Markdown
# secrets-engine action vocabulary
|
|
|
|
Status: published
|
|
Date: 2026-09-06
|
|
Workplan: `FLEX-WP-0021-T01`
|
|
Source: secrets-engine `docs/gated-actions.md`, read out of their `cli.py`
|
|
Package: `secrets-engine.catalog-lane.lifecycle` v1
|
|
(`examples/secrets-engine/policy_package.md`)
|
|
|
|
`FLEX-WP-0021-T01` required secrets-engine to **name** this list rather than
|
|
letting flex-auth derive it, and made an inferred action a blocker rather than
|
|
a default. That was not ceremony: four of the twelve would have been inferred
|
|
wrongly, and one action that looks obvious does not exist at all.
|
|
|
|
The example vocabulary `secrets-engine.lifecycle/v1` this replaces is **not**
|
|
this list and was never published (`FLEX-DEC-2026-005`).
|
|
|
|
## Request shape
|
|
|
|
| Field | Value |
|
|
| --- | --- |
|
|
| `resource.type` | `secret-catalog-lane` |
|
|
| `resource.system` | `secrets-engine` |
|
|
| `resource.id` | the catalog id |
|
|
| `resource.attributes.stage` | deployment stage |
|
|
| `resource.attributes.fields` | sorted; populated for `provision`, `rotate`, `verify`, `exec`; empty list otherwise |
|
|
| `resource.attributes.policy_targets` / `auth_targets` | sorted |
|
|
| `context.approval` | approval claim; required for `destroy` only |
|
|
|
|
Empty lists are sent rather than omitted or guessed.
|
|
|
|
## The twelve actions
|
|
|
|
| Action | Reaches OpenBao | Notes |
|
|
| --- | --- | --- |
|
|
| `apply` | yes | `apply --dry-run` does not reach the gate |
|
|
| `provision` | yes | carries `fields` |
|
|
| `rotate` | yes | carries `fields` |
|
|
| `verify` | yes | carries `fields` |
|
|
| `handoff` | yes | |
|
|
| `wrap` | yes | |
|
|
| `exec` | yes | carries `fields` |
|
|
| `deactivate` | yes | also the gate for the CLI verb `revoke` |
|
|
| `suspend` | yes | |
|
|
| `destroy` | yes | dual control; not reachable live yet |
|
|
| `compromise` | no | local delivery overlay only |
|
|
| `reactivate` | no | local delivery overlay only |
|
|
|
|
## Four things an inferred list gets wrong
|
|
|
|
**1. `revoke` is not an action.** The CLI verb `revoke` gates as `deactivate`,
|
|
which is also reached from `lifecycle deactivate`. There is no `revoke` value
|
|
and one must not be added. The package asserts this
|
|
(`test_revoke_is_not_an_action` → `unknown_action`).
|
|
|
|
**2. `destroy` is defined but not reachable live.** Its handler raises before
|
|
the gate, so only `--dry-run` renders. It stays in the vocabulary as the
|
|
dual-control case; no live `destroy` Check arrives until
|
|
`SECRETS-WP-0007-T04` lands. Keeping it in the package now means the rule is
|
|
reviewed and fixtured *before* the path opens rather than in the same change
|
|
that opens it.
|
|
|
|
**3. `compromise` and `reactivate` touch no OpenBao object.** They mutate local
|
|
delivery-overlay state only. They are gated because they change delivery
|
|
posture, not because they write to the backend — a distinction worth recording,
|
|
because a reviewer looking for backend writes would otherwise read their
|
|
presence as a mistake.
|
|
|
|
**4. Seven CLI surfaces never reach the gate** and must not appear in the
|
|
package: `plan`, `apply --dry-run`, `route`, `audit`, `catalog`,
|
|
`decision inspect`, and `evidence`.
|
|
|
|
`apply --dry-run` is the one flex-auth cannot enforce. `apply` is an action and
|
|
`apply --dry-run` is not, but both would arrive as `apply` — the distinction is
|
|
invisible to the PDP. What keeps them apart is that the PEP does not call the
|
|
gate for a dry run. flex-auth records the limit rather than implying a control
|
|
it does not have.
|
|
|
|
## Dual control on `destroy`
|
|
|
|
`destroy` requires `context.approval` marked `approved` with at least two
|
|
distinct approver subject ids. Repeated entries from one subject count once.
|
|
|
|
flex-auth consumes that claim as an input claim and never mutates it
|
|
(`security-layer-model_v0.7` §9.4). Per `FLEX-DEC-2026-006` the approval fact is
|
|
`approval-engine`'s step-1 artifact and the decision is step 2; the PEP
|
|
validates across both. This package checks that the claim *says* approved with
|
|
distinct approvers. It does **not** re-derive the claim's temporal validity,
|
|
signature, or supersession state — those belong to `approval-engine` to assert
|
|
and to the PEP to verify against the live claim. A PDP re-deriving them from a
|
|
caller-supplied blob would be inventing an authority it does not have.
|