secrets-engine/docs/task-execution-handoff.md
tegwick ff1a13dabd
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 4s
feat: require strict signed task authorization before approval consumption
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 09:20:57 +02:00

4.6 KiB

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:

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. No service_actor_binding request propagation or 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.