fix: recheck policy decision expiry after audit custody
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
72f3513954
commit
f11b948e8c
7 changed files with 126 additions and 8 deletions
|
|
@ -73,12 +73,15 @@ class Decision:
|
|||
allowed: bool
|
||||
decision_id: str
|
||||
policy_version: str
|
||||
valid_until: float
|
||||
caller: str = ""
|
||||
signed_envelope: str | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
if type(self.allowed) is not bool or not self.decision_id or not self.policy_version:
|
||||
raise ValueError("explicit boolean decision and provenance required")
|
||||
if type(self.valid_until) not in {int, float} or not math.isfinite(self.valid_until):
|
||||
raise ValueError("finite decision deadline required")
|
||||
|
||||
|
||||
class Identity(Protocol):
|
||||
|
|
@ -182,6 +185,7 @@ class AccessController:
|
|||
raise AccessFailure(403, "root_entitlement_required")
|
||||
context = Authorization(actor, action, resource, facts, correlation_id, request_digest)
|
||||
decision = await self._dependency("policy", lambda: self.policy.evaluate(context))
|
||||
self._check_decision_deadline(decision)
|
||||
await self.append_audit({
|
||||
"profile": PROFILE, "correlation_id": correlation_id,
|
||||
"issuer": actor.issuer, "subject": actor.subject,
|
||||
|
|
@ -196,6 +200,7 @@ class AccessController:
|
|||
})
|
||||
if not decision.allowed:
|
||||
raise AccessFailure(403, "policy_denied")
|
||||
self._check_decision_deadline(decision)
|
||||
if actor.expires_at <= time.time():
|
||||
raise AccessFailure(401, "expired_access_token")
|
||||
if time.time() - facts.checked_at > 5:
|
||||
|
|
@ -204,6 +209,11 @@ class AccessController:
|
|||
return replace(context, decision_id=decision.decision_id,
|
||||
policy_version=decision.policy_version, policy_caller=decision.caller)
|
||||
|
||||
def _check_decision_deadline(self, decision: Decision) -> None:
|
||||
if time.time() >= decision.valid_until:
|
||||
self._observations["policy"] = ("unavailable", time.monotonic())
|
||||
raise AccessFailure(503, "policy_decision_expired")
|
||||
|
||||
async def _resolve_facts(self, actor, resource):
|
||||
facts = await self.facts.resolve(actor, resource)
|
||||
if (facts.issuer, facts.subject, facts.actor_tenant) != (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
|
@ -133,7 +133,7 @@ def verify_decision(envelope: dict, *, request: dict, keys: dict,
|
|||
if not provenance["policy_version"] or not provenance["policy_package_digest"]:
|
||||
raise ValueError("missing policy provenance")
|
||||
age = (now - _time(provenance["decision_time"])).total_seconds()
|
||||
if not 0 <= age <= 30:
|
||||
if not 0 <= age < 30:
|
||||
raise ValueError("stale decision")
|
||||
if envelope.get("obligations"):
|
||||
# No obligation is silently treated as satisfied. Owner-specific
|
||||
|
|
@ -141,13 +141,16 @@ def verify_decision(envelope: dict, *, request: dict, keys: dict,
|
|||
raise ValueError("unsupported decision obligations")
|
||||
if envelope["effect"] not in {"allow", "deny"}:
|
||||
raise ValueError("unsupported effect")
|
||||
deadline = min(_time(caller_record["not_after"]),
|
||||
_time(provenance["decision_time"]) + timedelta(seconds=30))
|
||||
if envelope["effect"] == "allow":
|
||||
lifetime = envelope["lifetime"]
|
||||
if (lifetime["kind"] != "ttl" or not
|
||||
_time(lifetime["not_before"]) <= now < _time(lifetime["expires_at"])):
|
||||
raise ValueError("invalid decision lifetime")
|
||||
deadline = min(deadline, _time(lifetime["expires_at"]))
|
||||
return Decision(envelope["effect"] == "allow", envelope["id"], provenance["policy_version"],
|
||||
caller, go_json(envelope).decode())
|
||||
deadline.timestamp(), caller, go_json(envelope).decode())
|
||||
|
||||
|
||||
class FlexPolicy:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue