Require declared human control in factory credential delivery
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
d1c13b5dd6
commit
2b0d04e8e1
11 changed files with 451 additions and 27 deletions
|
|
@ -20,6 +20,7 @@ from urllib.request import Request, urlopen
|
|||
|
||||
from secrets_engine.approval_auth import approval_auth_configured, approval_token, credential_urlopen
|
||||
from secrets_engine.approval_claim import observe_pdp_approval_claim, validate_approval_claim
|
||||
from secrets_engine.catalog import human_control_required
|
||||
from secrets_engine.decision_check import check_decision
|
||||
from secrets_engine.authorization import (
|
||||
build_action_request,
|
||||
|
|
@ -40,6 +41,7 @@ class ConsumeBinding:
|
|||
approval_id: str
|
||||
request_digest: str
|
||||
decision_id: str = ""
|
||||
human_control: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -68,6 +70,7 @@ class AuthorizedAction:
|
|||
"authorization_decision_id": self.decision_id,
|
||||
"authorization_expires_at": self.expires_at,
|
||||
"request_digest": self.binding.request_digest,
|
||||
"approval_human_control": self.binding.human_control,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -222,13 +225,17 @@ def resolve_approval_observation(
|
|||
) -> ApprovalObservation | None:
|
||||
"""Observe a fresh fact and prepare the claim-bearing request (step 1).
|
||||
|
||||
Missing serving coordinates return None, preserving the production refusal.
|
||||
Missing serving coordinates return None for undeclared lanes. A declared
|
||||
human control raises instead, so no stage/demo fallback can discharge it.
|
||||
Correspondence and a consume binding require authorize_action's PDP step.
|
||||
"""
|
||||
base_url = str(getattr(cfg, "approval_url", "") or "")
|
||||
auth_configured = approval_auth_configured(cfg)
|
||||
authorization_id = _authorization_id(entry, decision)
|
||||
required_human_control = human_control_required(entry)
|
||||
if not base_url or not auth_configured or not authorization_id:
|
||||
if required_human_control:
|
||||
raise DecisionError("human_control requires a served approval claim and exact action binding")
|
||||
return None
|
||||
|
||||
expected_request = _expected_request(
|
||||
|
|
@ -242,7 +249,8 @@ def resolve_approval_observation(
|
|||
authorization_id=authorization_id,
|
||||
opener=opener or credential_urlopen,
|
||||
)
|
||||
observe_pdp_approval_claim(claim, approval_id=authorization_id)
|
||||
observe_pdp_approval_claim(claim, approval_id=authorization_id,
|
||||
require_human_control=required_human_control)
|
||||
expected_request["context"]["approval"] = claim
|
||||
return ApprovalObservation(authorization_id, expected_request, claim)
|
||||
|
||||
|
|
@ -310,6 +318,7 @@ def authorize_action(
|
|||
validate_approval_claim(
|
||||
observation.claim, approval_id=observation.approval_id,
|
||||
expected_pdp_digest=envelope["binding"]["approval_binding_digest"],
|
||||
require_human_control=expected_request["context"].get("human_control") is True,
|
||||
)
|
||||
if validated.action != action:
|
||||
raise DecisionError("access-engine decision does not bind this action")
|
||||
|
|
@ -318,6 +327,7 @@ def authorize_action(
|
|||
approval_id=observation.approval_id,
|
||||
request_digest=validated.request_digest,
|
||||
decision_id=validated.decision_id,
|
||||
human_control=observation.claim["binding"].get("human_control") is True,
|
||||
),
|
||||
decision_id=validated.decision_id,
|
||||
expires_at=validated.expires_at,
|
||||
|
|
@ -413,16 +423,21 @@ def require_production_consume(
|
|||
evidence: Any = None,
|
||||
opener: Callable[..., Any] | None = None,
|
||||
) -> ConsumedApproval | None:
|
||||
"""CAS-consume before a production OpenBao call. No-op off the prod path.
|
||||
"""CAS-consume before production or an explicitly human-controlled action.
|
||||
|
||||
Build/test remain fail-open relative to approval-engine. The three-factor
|
||||
unsafe-demo exception is not a consume path. Missing binding, URL, or
|
||||
credential fail closed so a stance bypass cannot reach OpenBao.
|
||||
Undeclared build/test and the unsafe-demo exception retain their existing
|
||||
behavior. Declared human controls require a verified binding and consume
|
||||
regardless of stage/demo; missing inputs cannot fall back to a lane review.
|
||||
"""
|
||||
if getattr(entry, "stage", "") != "prod":
|
||||
return None
|
||||
if demo_exception_enabled(cfg):
|
||||
return None
|
||||
required_human_control = human_control_required(entry)
|
||||
if required_human_control:
|
||||
if binding is None or binding.human_control is not True:
|
||||
raise DecisionError("human_control requires an observed declared-human consume binding")
|
||||
else:
|
||||
if getattr(entry, "stage", "") != "prod":
|
||||
return None
|
||||
if demo_exception_enabled(cfg):
|
||||
return None
|
||||
if binding is None:
|
||||
raise DecisionError(
|
||||
"production OpenBao call requires CAS consume of an approval "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue