fix(auth): authenticate producer-signed decisions before authorization
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
parent
6bd7129eac
commit
c80a944105
30 changed files with 2059 additions and 32 deletions
|
|
@ -19,6 +19,7 @@ from urllib.error import HTTPError, URLError
|
|||
from urllib.request import HTTPRedirectHandler, ProxyHandler, Request, build_opener
|
||||
|
||||
from secrets_engine.errors import DecisionError
|
||||
from secrets_engine.decision_signature import strict_json, load_trust, authenticate_response
|
||||
from secrets_engine.openbao import read_strict_token_file
|
||||
|
||||
_MAX_BODY = 512 * 1024
|
||||
|
|
@ -95,6 +96,9 @@ def check_decision(
|
|||
base_url: str,
|
||||
token_file: Path,
|
||||
request: dict[str, Any],
|
||||
trust_file: Path | None = None,
|
||||
verifier: Path | None = None,
|
||||
allow_unsigned_test_fixture: bool = False,
|
||||
timeout_seconds: float = 3,
|
||||
opener: Callable[..., Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
|
|
@ -103,9 +107,13 @@ def check_decision(
|
|||
Silence is never permission: an unreachable PDP raises rather than
|
||||
returning a permissive default.
|
||||
"""
|
||||
require_signature = not allow_unsigned_test_fixture
|
||||
if not base_url or not base_url.startswith(("http://", "https://")):
|
||||
raise DecisionError("access-engine check URL is missing or invalid")
|
||||
require_supported_pdp_address(base_url)
|
||||
trust = load_trust(trust_file) if require_signature else None
|
||||
if require_signature and (verifier is None or not Path(verifier).is_absolute() or not Path(verifier).is_file()):
|
||||
raise DecisionError("access-engine signature verifier is not installed")
|
||||
token = read_strict_token_file(Path(token_file), purpose="access-engine credential")
|
||||
encoded = json.dumps(request).encode("utf-8")
|
||||
http_request = Request(
|
||||
|
|
@ -123,13 +131,15 @@ def check_decision(
|
|||
body = response.read(_MAX_BODY + 1)
|
||||
if len(body) > _MAX_BODY:
|
||||
raise DecisionError("access-engine check body is too large")
|
||||
payload = json.loads(body.decode("utf-8"))
|
||||
payload = strict_json(body.decode("utf-8"))
|
||||
except HTTPError as e:
|
||||
raise DecisionError(f"access-engine check refused: {_status_message(e.code)}") from e
|
||||
except URLError as e:
|
||||
raise DecisionError("access-engine is unreachable for check") from e
|
||||
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
except (ValueError, UnicodeDecodeError, RecursionError) as e:
|
||||
raise DecisionError("access-engine check returned a non-JSON body") from e
|
||||
if not isinstance(payload, dict):
|
||||
raise DecisionError("access-engine check returned a non-object body")
|
||||
if require_signature:
|
||||
return authenticate_response(body, trust=trust, verifier=verifier)
|
||||
return payload
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue