secrets-engine/docs/task-execution-handoff.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

113 lines
6.9 KiB
Markdown

# Strict Secrets service task-execution handoff
This is a narrow source seam for existing SECRETS-WP-0013, not a native grant or
an execute-ccr CLI. Both Access runtime-deny policy variants remain unchanged.
The proposed service subject/transport mapping and the complete native executor
still need independent owner admission. Positive source fixtures do not admit
that mapping, a policy, a caller, a production key or an OpenBao entitlement.
Platform's trusted owner process calls:
```python
from secrets_engine.task_execution_handoff import (
authorize_task_execution, consume_task_execution,
)
authorization = authorize_task_execution(cfg, entry, execution_contract=contract)
record = authorization.as_owner_handoff()
# Independently bind exact owner Plan/descriptor; reserve durable one-shot journal.
# Persist pending strict-CAS intent before calling:
consumption = consume_task_execution(cfg, authorization)
# Independently GET-confirm exact original consumption with Approval Engine's
# ExecutionConfirmationClient before journaling consume-confirmed/native work.
```
Preparation uses the existing empty fields/policy_targets/auth_targets defaults
and existing build_action_request/claim/Check validators. No caller-selected
transport, authorizer callback, policy result or raw handoff mapping is accepted.
The normal authorizer retains detached serialized request, claim and authenticated
Go-typed envelope snapshots; its existing evidence output does not expose them.
The narrow adapter reuses the validators, rather than evaluating a policy.
The adapter checks the exact descriptor in the signed context, full evaluated and
submitted request digests, approval-free digest, exact five-field native act and
native binding digest, service subject/resource/tenant correspondence and original
decision/approval/deadline bounds. Initiating/represented subject is strictly
`secrets-engine` of type `service`; the execution actor is `service:secrets-engine`.
Signed caller provenance must report `enforce`, the proposed exact Secrets
ServiceAccount principal, `flex-auth` audience and live expiry bounding the
operation deadline. This consumes the signed PDP's caller attestation; it does
not independently perform TokenReview or promote the proposed server mapping.
Human/delegation/projected-identity flags cannot substitute for this service seam.
The later typed execution builder now propagates the fixed Access public
service_actor_binding before approval issue; it is not caller authentication.
No source-policy activation is added.
`as_owner_handoff()` returns a detached non-secret mapping with:
- execution_contract
- approval_id, request_digest, decision_id
- native_binding, approval_binding_digest, submitted_request_digest
- decision_not_before, decision_expires_at, approval_expires_at
The exact native binding is derived from the signed evaluated request using the
published Approval five-field mapping, and is compared to the served claim.
Descriptor/CCR/grant/source/catalog/target correspondence is retained end to end;
Platform still independently compiles its owner Plan and grant from owner bytes.
The mapping is evidence for that join, never a transferable bearer authorization.
Consumption accepts only the module-created opaque prepared object in that same
trusted owner process. It fixes source/config coordinates, reauthenticates using
currently admitted responder keys, and rechecks claim/decision/descriptor/caller
freshness before reading the CAS credential. Each object is reserved once before
CAS/revalidation and cannot retry after any failure or unknown outcome. Its
process-local guard complements Platform's durable cross-process journal; it is
not a replacement for that journal or process isolation.
The strict response path uses the existing CAS transport but rejects duplicate,
nonfinite or excessively deep JSON, missing/mismatched approval and decision IDs,
wrong digest/status, missing or nonboolean idempotence, historical idempotent
success, verdict fields and absent/naive/invalid consumption timestamps. The
consumption instant and post-CAS freshness must remain inside the original
bounds. No field is coerced or filled from the submitted binding. A late/uncertain
CAS may have occurred; it never licenses retry or protected native work.
The returned immutable TaskExecutionConsumption contains exact approval_id,
request_digest, decision_id, consumed_at and explicit false idempotent. It proves
only the strict CAS response observed by Secrets. Platform must independently
confirm original durable consumption using Approval's real provider client,
journal it, then recheck its exact Plan, owner sources, grant and deadlines before
any native operation. Neither a dataclass nor a caller-supplied boolean admits
execution. Existing generic CLI CAS behavior is otherwise unchanged.
2026-10-16 retained-registry preapproval source preparation adds
`prepare_task_approval(cfg, entry, execution_contract=contract)`. It calls the
existing signed Check client with the original claim-free typed proposal and
accepts only a signed DENY normalization record. This neither authorizes nor
consumes an operation. No registry algorithm is copied and no registry is emptied.
The detached `as_owner_handoff()` returns `original_request`,
`submitted_request_digest`, authoritative `native_binding`,
`approval_binding_digest`, normalization `decision_id`, `execution_contract`, and
signed `registry_snapshot_digest`. Exact original context (including JSON kinds),
submitted digest, recomputed evaluated digest, explicit approval-free digest,
policy/key admission, service/caller identity, decision time and deadline are
checked. Registry digest is evidence from the admitted signer; this method does
not independently grant registry/config admission. The caller uses the returned
native act and approval-free digest for normal Approval issuance and human entry.
It preserves the original proposal; final authorize_task_execution builds that
same proposal and attaches only the original issued claim. Access normalizes
again and independently joins current source facts. Changed evaluated facts or
native target refuse before CAS. A normalization DENY object/mapping is never
accepted by consume_task_execution.
Normalization decision_time must be valid and nonfuture; there is deliberately
no invented standalone normalization TTL. The DENY receipt is not permission and
cannot discharge final signed ALLOW or source validation. Expired original task/
caller bounds refuse at preparation; a stale normalization's changed evaluated
facts refuse when Access independently checks the original issued claim against
the current retained registry. Unchanged historical normalization can only supply
public issue metadata; it cannot itself produce a CAS/native admission. The actual
source-server drift proof restarts Access with changed authoritative subject
roles after preapproval and observes final refusal with no new ALLOW record, no
CAS and unconsumed Approval state.