secrets-engine/docs/task-scoped-openbao-execution-contract.md
tegwick 4dd004a225
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Verify normalized task approval facts and preserve signed source types
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 17:28:03 +02:00

25 KiB
Raw Blame History

Task-scoped protected OpenBao execution — owner design

Design ID: SECRETS-EXEC-20261011-v1. Status: proposed, source preparation only. Owning draft: SECRETS-WP-0013; adoption remains SECRETS-WP-0008-T06. No native entitlement, owner assent or founder go is established here.

The concrete gap and immediate correction

cli._require_lane_approval consumes the approved action before _open_backend. engine_auth.select_engine_auth selects service-jwt for auto whenever its login contract is configured. CCR-2026-0038 grants a 300-second, eight-use token with exactly secrets-engine-login-self. It explicitly denies custody data/metadata, policy/auth writes and child-token creation. Thus a valid approval can be consumed before an ordinary apply/provision/exec fails for lack of backend power.

The staged correction checks local execution readiness before claim/PDP/CAS for every caller about to access protected OpenBao work. Until a real reviewed execution backend ships, service-jwt is rejected for those callers. Explicit bootstrap/env providers retain their existing approval/stance gates; no fallback or default changes. Service-auth login/self probes remain available. Local-only compromise/reactivate records retain their existing path. A file/boolean claiming entitlement cannot make the guard pass. Tests exercise the actual apply handler, asserting zero claim/PDP/consume/login/apply calls for configured auto and missing explicit service-jwt entitlement. This correction fixes early consumption; it does not implement task-scoped protected work or complete T06.

One initial execution surface

Implement secrets-engine execute-ccr --catalog <id> --stage prod --request <file> as an explicit, metadata-only action. It admits one owner-reviewed Platform workload-kv-read CCR with already-existing KV and auth mounts, exact generated policy and exact Kubernetes or existing netkingdom OIDC role. It does not call generic apply_plan: that function can manage AppRoles/mounts, which differs from Platform's typed CCR applier. Provision, exec delivery, rotation, KV reads, SecretIDs, token creation by the caller, mounting and arbitrary CLI commands remain outside this first entitlement. Their backend obligations require their own later exact action contracts, not reuse of this metadata allowance.

CCR38 and its role/policy stay byte-for-byte unchanged. The engine identifies itself through that login-only service session; authentication does not create authority for the metadata action. A new separately reviewed Platform task-execution-grant CCR admits the founder-owned execution mediator and its temporary exact two-path task token. Existing applied CCRs, including CCR38, cannot be replayed to manufacture new native work. A real unexecuted, separately approved metadata CCR and native success/negative/cleanup receipts are needed for the first proof; none is selected or approved by this design.

Exact request binding

Use authorization.build_action_request(entry, 'execute-ccr', ...) with the existing canonical digest rules. apply is a distinct legacy action: its v2 policy ignores this metadata contract and must never stand in for execute-ccr. Add a reviewed context.execution_contract object before observing the claim or calling Check. Its fields are:

Field Binding
contract_version SECRETS-EXEC-20261011-v1
operation_id Owner-issued UUID, part of the request digest
operation metadata-apply-ccr, closed enum
execution_actor Verified service:secrets-engine, separate from initiating subject
ccr_id / ccr_immutable_sha256 Exact unapplied owner CCR ID and immutable content digest
execution_grant_ccr_id / grant_immutable_sha256 Exact reviewed task-token materialization and cleanup contract
catalog_sha256 Exact non-secret approved catalog target specification
source_bundle Absolute owner-resolved files and immutable SHA256 hashes for engine dispatcher, Platform mediator, typed CCR module and policy/auth source
backend_origin / cluster_uid Exact reviewed estate OpenBao origin and railiance01 identity
operations Ordered exact HTTP method, normalized path and canonical JSON/text payload hash for each read/write/readback; no user supplied body
operation_deadline Upper bound no later than approval, PDP, caller and original/fresh source validity; task-token expiry may be earlier
backend_budget TTL at most 120s, at most twelve uses, no renew/child/wrap/unlimited uses
expected_before / expected_after Owner-authenticated revision/state digests, never secret contents

The action subject/type/purpose/tenant are the actual initiating principal's verified identity and existing catalog purpose, not inferred from engine service identity or arbitrary environment fields. Founder-attended invocation names that principal and the representation explicitly. The mediator refuses absent owner identity correspondence; a caller-authored subject string is not evidence.

The signed/served approval must bind the same approval-free execution context; the PDP must return the exact canonical request and matching approval digest. Publish and pin the updated access-engine policy package/version that evaluates this context; an older package accepting only a catalog ID cannot authorize it. No package default or implicit semantic acceptance is assumed. Approval identity, decision ID and canonical request digest travel together in a non-secret request descriptor. They are provenance until freshly validated and CAS-consumed.

CCR immutable digests exclude only explicitly owner-defined append-only review/ evidence fields; policy bytes, role bytes, typed mutation body, target coordinates, grant/deadline and source revisions are included. Approval IDs added after issuance do not change their own binding. The mediator independently reconstructs all operation bodies from owner sources and compares the resulting digest list. It accepts no caller ACL template, path override, shell argv or raw JSON body.

Execution mediation and consume ordering

Use the existing attended Platform front door, invoked by the founder, to run a new fixed Platform owner mediator. No daemon, broad new service or new bearer delivery to agents is required. The caller engine prepares a non-secret request and receives a non-secret receipt. The founder-held admin credential exists only inside the reviewed Platform child, through the existing attended route. It is never exported to the caller. Neither local receipt files nor user-authored approved labels authorize the child.

The Platform child runs the reviewed Secrets Engine authorization/consume code at the last responsible execution boundary; the CLI preparation path does not consume early and then hand an untrusted receipt to a more privileged process. The backend mediation contract must preserve engine ownership of lifecycle validation while Platform owns its OpenBao credential and typed native primitive.

  1. Load pinned sources and exact non-secret request; check typed CCR/grant owner approval, catalog/subject/operation correspondence, source/state expectations, owner-only execution state directory, supported method/body allowlist and local clock/deadline. No protected OpenBao call occurs yet.
  2. Atomically reserve the operation in a dedicated engine execution ledger, uniquely keyed by approval_id + request_digest; the operation UUID is inside the digest. Bind the authenticated caller, sources and deadline. This is execution state, not the audit outbox or a local evidence-presence gate. Multiworker locking uses SQLite BEGIN IMMEDIATE and uniqueness constraints; only the reservation creator may proceed.
  3. Use authorize_action / approval-claim validation / check_decision / exact envelope validation, with pinned policy package and trusted application time. Recheck freshness after network calls. Require served human control for the production grant; missing claims, unreachable PDP or mismatched binding refuse.
  4. Call require_production_consume once for that exact binding. Require confirmed matching consumption. Persist consume-confirmed before any protected OpenBao login, custody read, capability check or mutation. A consume timeout is unknown; never treat it as absence or automatically retry. A consumed/idempotent response without the same live local admission is not permission to execute again. Retrying a completed operation returns only its existing receipt; an incomplete/unknown operation requires owner reconciliation and a fresh exact approval for any additional backend action.
  5. After consume, perform the existing attended in-memory client-secret source and CCR38 service login without provider fallback. Verify its exact login-self policy/TTL/uses and service identity; keep this session self-only. Failure consumes the trial without metadata effects; no implicit re-login or retry.
  6. In the Platform child, use the separately authorized founder-held identity only to materialize the reviewed exact ephemeral task policy/token. OpenBao's token parent remains platform-owned; the CCR38 token cannot mint it. The grant binds every policy/token create/revoke/delete step to this operation and its cleanup. Do not give a standing sys/policies/acl/*, auth/*/role/* or auth/token/create grant to the engine service.
  7. Recheck decision, approval and operation deadlines before each protected request. The task token is nonrenewable, no default/identity policies and grants only read/create/update on the one approved ACL policy path and the one approved workload auth-role path, plus revoke-self. No child token, mount administration, KV data/metadata, SecretID or sibling role is admitted. OpenBao ACL narrows endpoint access; it cannot restrict the HCL/role payload. Only the mediator's independently reconstructed body equality enforces that.
  8. Use an injected restricted backend adapter for the exact ordered native calls. Compare current expected state; write only the approved generated body, read it back, and compare it. No OpenBaoClient._run_ok raw failure reaches receipts: capture/discard backend output and use closed reason codes. A generic 403/timeout or “already exists” is not successful verification.
  9. Finally self-revoke the task token and login-self token, then retire only the exact per-operation grant policy under founder-owned cleanup authority. Record confirmed revocation/absence separately from native apply status. Policy retirement alone does not prove issued-token revocation.

Task-token use budget is declared before issuance: two before-state reads, two writes, two readbacks and one self-revoke (seven uses), plus at most four explicit metadata capability checks and one reserved cleanup use (twelve total). Failed backend calls count; never retry to evade limits. The immutable backend_budget.ttl_seconds declares the requested task-token TTL (30–120s). The native adapter does not dynamically recompute or shorten that declaration. Before issuance, conservatively require at least the declared TTL plus the 5s cleanup reserve within the remaining operation/approval/PDP authority, including clock uncertainty. The requested and explicit maximum native token TTL equal the declared budget; returned native TTL must be positive and at most that budget. The token can expire before the operation deadline; failure or expiry stops work without remint. The signed operation deadline remains no later than approval, PDP, authenticated caller and both original/fresh source validity. Access's trusted source path independently requires the same TTL-plus-5s fit after source I/O and an operation interval no longer than 120s. Therefore a structural 120s backend budget cannot fit inside a 120s authority interval with the reserve and refuses. No renewal or remint extends the operation. CCR38 keeps its own 300s/eight-use limit; login-self checks and revoke fit its separate budget.

Failure, replay and recovery

Ledger states: reserved → consume-confirmed → authenticating → grant-issued → applying → verified, followed by a separate cleanup-confirmed or cleanup-unconfirmed result. Every external call is durably recorded as about to execute before the call, then recorded complete afterward. Crash between those steps is unknown rather than presumed failure. Concurrent duplicate requests never both reach consume or OpenBao.

An operation can end refused before consumption, consumed-without-backend-work, partially-applied, backend-outcome-unknown, verified-cleanup-unconfirmed or verified-cleanup-confirmed. No incomplete state permits blind reapplication. Cleanup continues when an action deadline expires, under the original explicit cleanup obligation, without new business writes. Read/repair of changed target state requires a fresh exact owner-reviewed recovery approval; no old snapshot overwrites concurrent owner edits. Hard interruption uses the Platform owner's bounded revocation/cleanup procedure with protected accessor custody. Ledger and evidence carry only fingerprints/metadata; the raw accessor/token never leaves owner memory or the separately admitted owner-private recovery channel.

Revocation failure blocks owner acceptance and T06 closure, even if TTL will eventually expire. Emit non-secret atomic local lifecycle events/outbox records at the ledger boundaries; audit-core outage does not suppress cleanup. Do not claim an empty archive proves no effects, or that local ledger state is founder approval. Owner acceptance requires native before/after equality, denied sibling actions and verified cleanup under the same exact trial.

Implementation hooks and closure tests

  • engine_auth.py: staged readiness guard now. Later separate identity-session and execution-adapter objects; generic service-jwt client remains self-only.
  • cli.py: staged guard passed through backend-action approval callers now. New explicit execute-ccr preparation/mediated dispatch later; ordinary apply does not acquire new rights merely because the login contract is configured.
  • authorization.py, approval_consume.py: execution context participates in both approval-free binding and full PDP digest; owner child returns the actual ConsumedApproval, not only an AuthorizedAction ID.
  • New execution_contract.py, execution_ledger.py, mediated_backend.py: strict closed schema, deterministic compiler, separate admission state, pinned owner-native adapter and non-secret outcome types. Ordinary subprocess command execution is not an implementation of this adapter.
  • Platform owns task-execution-grant CCR/typed validator/token materializer/ fixed execution child/recovery hooks. Approval Engine/PDP owners acknowledge the request+claim/package correspondence; secrets-engine cannot assent for them.

Counterexamples required before shipping: login-only auto provider consumes nothing; stale/forged grant file changes nothing; same name with changed HCL or role body refuses; wrong method/sibling policy/role/KV/SecretID/child-token/mount write refuses; changed source/caller/tenant/catalog/deadline alters the bound request and refuses; consume conflict/unavailability/timeout produces zero backend calls; idempotent approval consumption never causes a second execution; concurrent calls admit one worker; postconsume login failure never falls back; expiry during a multi-call operation stops new mutations but permits scoped cleanup; readback mismatch/transport failure is incomplete; revoke failure never passes; partial apply and crash after write require explicit recovery.

Current staged readiness regression suite: eight new checks plus 28 existing auth, consume and privileged-evidence checks pass (36 total). No native action was performed. Full entitlement implementation, policy-package publication, independent Platform/Secrets Engine/identity/approval/PDP acknowledgement and exact founder-scoped native proof remain required. A successful metadata trial alone would not cover value provision or exec delivery.

Existing commands and the next contract they actually need

Existing operation Current login-only outcome Contract required after the metadata pilot
apply / apply_plan Readiness refusal before consume; no backend A separate exact policy/AppRole/mount structure compiler and grant for each admitted PlanAction. The workload-kv-read execute-ccr pilot does not implement these generic AppRole/mount calls.
provision / rotate Readiness refusal before consume; no KV writes Exact KV v2 data path, declared field set, expected CAS version, no sibling changes, owner-held opaque input handle, confirmed new version and separately admitted revoke/cleanup. Never expose secret plaintext or guessable secret-value hashes in request/evidence.
verify positive/negative Readiness refusal before consume; no custody reads Metadata capability probe plus, only when required, exact declared field-presence read through a restricted owner adapter. Return booleans; malformed/denied/transport error is inconclusive, not absence. Independent negative identity and exact deny paths are separate bindings.
exec / wrap Readiness refusal before consume; no read/injection/wrap Exact custody path/field, signed executable/cwd/destination/exec-owner binding and duration, no user arbitrary shell, owner-mediated read and approved process-local delivery, or exact one-use wrap creation/unwrap/expiry; self revoke and injected material cleanup.
handoff Readiness refusal before consume; no role-id/SecretID mint Exact existing AppRole and single-use SecretID creation, bounded TTL/uses and owner-admitted delivery; native role-id lookup/SecretID mint/login/revoke budget and no caller standing credential.
revoke / native suspend/deactivate Readiness refusal before consume; no backend revocation/deletion Exact policy/role/lease/accessor target and expected state, admitted lifecycle effect, rollback/custody preservation and denial readback. Token/lease handles remain owner-private; receipt fingerprints cannot authorize another handle.
session revoke Readiness refusal before its backend; legacy stance remains Exact already-held protected handle, typed token/lease revocation and independent subject/authority binding; no arbitrary accessor or lease-prefix wildcard.
destroy Existing live destroy refusal remains Exact destructive dual-control contract and custody/backup/version proof; metadata pilot supplies neither.
compromise / reactivate local state Existing local state gate remains No new OpenBao entitlement; these local records do not establish actual native revocation/reactivation.
service-auth probe-openbao Existing explicit self-only identity trial CCR38 positive/negative/lifetime/revoke proof, with no implied entitlement to any catalog lane.

Each subsequent contract needs its own owner-reviewed compiler, request/PDP package binding, method/body enforcement, consume admission, TTL/use budget, failure/recovery rules and native counterexamples. No success in the initial metadata pilot unblocks these rows by default. Full T06 adoption requires the actual selected engine production workflow to run through the corresponding reviewed entitlement, with native effect, denial/no-fallback and cleanup proof.

Source preparation: typed request propagation (2026-10-11)

The staged ExecutionContract descriptor is deliberately a closed, immutable non-secret value. parse validates shape and captures JSON; every as_dict revalidates even directly constructed instances and returns a detached copy. It is not an owner approval, source authenticity attestation, principal representation or proof that the requested origin is admitted. Compiler and mediator must independently reconstruct and authenticate sources and bodies.

The exact proposed descriptor serialization refines the table above:

  • Digests use sha256: plus 64 lowercase hexadecimal characters. UUIDs use canonical lowercase hyphenated form. CCR and grant IDs must be distinct.
  • source_bundle has exactly engine_dispatcher, platform_mediator, typed_ccr and policy_auth; each has exactly an absolute normalized path and sha256. This parses paths without reading files; pin verification remains unimplemented.
  • operations is an ordered list of exactly method/path/payload_sha256 objects. It accepts only GET/PUT metadata policy and auth-role endpoint shapes, with normalized relative paths. This schema does not reconstruct bodies or prove that all before/write/readback operations occur or target exactly two paths. A later owner compiler must enforce those semantics before any execution.
  • backend_origin is an explicit-port HTTP(S) origin without credentials, path, query or fragment. Syntactic acceptance creates no approved route or network call. Estate pinning and server authentication remain mediator obligations.
  • backend_budget has exactly ttl_seconds (integer 30–120) and uses (integer 1–12); booleans fail. Listed calls must leave at least one cleanup use. Renewal, remint and live per-call accounting remain outside this descriptor.
  • expected_before/expected_after are non-secret state digests; operation_deadline is timezone-aware. Before claim, refuse expired deadlines using the existing trusted-time window. After claim and Check, refuse a deadline later than either served approval or decision expiry. A future mediator must recheck before every effect and provide separately authorized cleanup after business expiry.
  • catalog_sha256 is a descriptor-local sorted compact JSON fingerprint of the complete existing context.catalog_target, distinct from canonical PDP digests. Resource catalog ID/stage, subject, tenant and purpose remain bound separately through the unchanged canonical request hashing implementation.

The same typed descriptor passes through build_action_request, _expected_request, resolve_approval_observation and authorize_action. Any contract on apply or another legacy action refuses. execute-ccr without a typed valid contract, prod stage or platform tenant refuses. execute-ccr always binds human_control=true, even on a legacy bootstrap catalog, and cannot use an unserved/demo fallback. Invalid or catalog-mismatched descriptors refuse before claim/PDP/CAS. Returned requests are independent copies; later caller mutation cannot change the stored descriptor.

99 focused source tests pass across context propagation, the existing approval join, decision validation, readiness, consume and engine auth. New tests exercise all four actual builders, actual consume request formatting and claim-free/full joins; mutation of operation, CCR/grant/source/body/method/path/budget/deadline/ estate/state/principal/purpose/catalog/tenant/stage refuses the original approval. Transport/PDP stand-ins deliberately supply hypothetical human control and an ALLOW: they prove propagation, not a shipped policy or real principal admission.

No execute-ccr CLI, identity-owner verified initiating-principal interface, new access policy package, typed owner compiler, task-grant materializer, independent authenticated consume-confirmation integration, durable admission journal, restricted backend, per-call deadline checks or native entitlement is supplied by this stage. Existing cfg subject strings remain legacy provenance; this change does not make them verified human representation. Existing generic consume receipt validation limitations remain explicitly separate. service-jwt readiness guard/default and ordinary actions remain unchanged. T01 stays in progress, T03 stays wait, WP13 stays proposed, and WP8-T06 stays open.

D3 wrapped-delivery primitive and its separate entitlement

D3 P3=b chooses response-wrapped issuer delivery with one-time unwrap. The existing wrapped-handoff primitive now verifies native TTL/accessor/creation-path metadata and reserves a private immutable recipient file before minting. Native KV-v2 creation paths include the data/ endpoint; caller-requested CLI paths cannot substitute for returned metadata. Failed mint/write or an altered reservation retains an uncertain attempt and never silently overwrites/remints.

A disposable local OpenBao integration proves the exact source path,60-second TTL, successful first unwrap and specific HTTP400 consumed-token replay denial. This proves backend semantics, not the estate's first delivered lane, audited initiating principal, consumer identity, independent owner acceptance or cleanup under crash/revocation. Production CLI wrapping remains fail-closed. Existing metadata execute-ccr contracts expressly exclude wrap/KV value delivery and cannot be borrowed for this purpose. A separate exact owner-admitted delivery operation must bind actor,path,audience,deadline,authorization/consume state,destination, audit,replay/crash recovery and rollback before native adoption. The prepared D2 repository-issuer lane remains attended until its own reviewed execution and independent destination/adoption proofs pass.