Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0291a-1e87-7151-9934-fcbfe3f65eb1
201 lines
6.8 KiB
Python
Executable file
201 lines
6.8 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Readiness gate for the zone-aware flex-auth caller identity.
|
|
|
|
flex-auth deployed ``flex-auth-ops-warden`` (FLEX-WP-0016) in ``callerAuth.mode:
|
|
warn``: it authenticates the caller with a Kubernetes TokenReview and binds
|
|
``resource.system: ops-warden`` to ``system:serviceaccount:ops-warden:ops-warden``,
|
|
but a caller that sends no ``Authorization`` header only produces a
|
|
``caller authentication warning`` and is still served. That pin cannot move to
|
|
``enforce`` until ops-warden's calling side actually presents a token. The
|
|
former repo-wide ``policy.enabled`` switch is retired by WARDEN-WP-0032.
|
|
|
|
This script asserts the calling side *without* flipping anything:
|
|
|
|
* warden.yaml loads and ``policy.caller_auth.mode`` is not ``none``,
|
|
* a caller token can actually be obtained (file / env / command),
|
|
* (optional, ``--url``) a live ``/v1/check`` against the warn pin returns a
|
|
decision **and** the response is reached with the header attached.
|
|
|
|
Exit 0 = ready to ask flex-auth to enforce, 1 = not ready, 2 = bad input.
|
|
The token is never printed, logged, or written anywhere — only its length and a
|
|
truncated SHA-256 fingerprint, which are safe to paste into a handoff message.
|
|
|
|
Usage:
|
|
python scripts/check_policy_caller_identity.py [--config ~/.config/warden/warden.yaml]
|
|
python scripts/check_policy_caller_identity.py --url http://127.0.0.1:19090
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Optional, Tuple
|
|
|
|
_SRC = Path(__file__).resolve().parent.parent / "src"
|
|
if _SRC.is_dir() and str(_SRC) not in sys.path:
|
|
sys.path.insert(0, str(_SRC))
|
|
|
|
from warden.caller_identity import ( # noqa: E402
|
|
CallerIdentityError,
|
|
resolve_caller_token,
|
|
)
|
|
from warden.config import ConfigError, load_config # noqa: E402
|
|
|
|
Check = Tuple[str, str, str]
|
|
|
|
|
|
def _fingerprint(token: str) -> str:
|
|
return "sha256:" + hashlib.sha256(token.encode()).hexdigest()[:12]
|
|
|
|
|
|
def run_checks(config_path: Optional[Path], url: Optional[str]) -> List[Check]:
|
|
checks: List[Check] = []
|
|
try:
|
|
cfg = load_config(config_path)
|
|
except ConfigError as e:
|
|
return [("fail", "warden.yaml", str(e))]
|
|
|
|
policy = cfg.policy
|
|
checks.append(("ok", "warden.yaml", "loaded; security-zones_v0.1 profile"))
|
|
|
|
mode = policy.caller_auth.mode
|
|
if mode == "none":
|
|
checks.append(
|
|
(
|
|
"fail",
|
|
"caller_auth.mode",
|
|
"none — no Authorization header is sent; the flex-auth pin stays in warn",
|
|
)
|
|
)
|
|
return checks
|
|
checks.append(("ok", "caller_auth.mode", mode))
|
|
|
|
try:
|
|
token = resolve_caller_token(policy.caller_auth)
|
|
except CallerIdentityError as e:
|
|
checks.append(("fail", "caller token", str(e)))
|
|
return checks
|
|
assert token is not None
|
|
checks.append(
|
|
("ok", "caller token", f"obtained, {len(token)} chars, {_fingerprint(token)}")
|
|
)
|
|
|
|
target = url or policy.flex_auth_url
|
|
if target is None:
|
|
checks.append(
|
|
(
|
|
"skip",
|
|
"live /v1/check",
|
|
"policy.flex_auth_url is absent; pass --url to run the live smoke",
|
|
)
|
|
)
|
|
return checks
|
|
|
|
import httpx # local import: the offline checks above must not need it
|
|
|
|
probe = {
|
|
"subject": {
|
|
"id": "agt-state-hub-bridge",
|
|
"type": "agt",
|
|
"tenant": policy.tenant,
|
|
},
|
|
"action": "sign",
|
|
"resource": {
|
|
"id": "ssh-cert:actor/agt-state-hub-bridge",
|
|
"type": "ssh-certificate",
|
|
"system": policy.system,
|
|
"tenant": policy.tenant,
|
|
},
|
|
"context": {
|
|
# A structurally complete context, so a deny means the policy said
|
|
# no — not that the probe was malformed. What is under test here is
|
|
# the caller identity, and that is answered by the HTTP status.
|
|
"actor_name": "agt-state-hub-bridge",
|
|
"actor_type": "agt",
|
|
"principals": ["agt-task-bridge"],
|
|
"ttl_hours": 24,
|
|
"pubkey_fingerprint": "sha256:" + "0" * 64,
|
|
"readiness_probe": True,
|
|
},
|
|
}
|
|
try:
|
|
response = httpx.post(
|
|
target.rstrip("/") + "/v1/check",
|
|
json=probe,
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
timeout=10.0,
|
|
)
|
|
except httpx.RequestError as e:
|
|
checks.append(("fail", "live /v1/check", f"unreachable at {target}: {e}"))
|
|
return checks
|
|
|
|
if response.status_code == 401:
|
|
checks.append(
|
|
(
|
|
"fail",
|
|
"live /v1/check",
|
|
"401 — the token was sent but flex-auth did not accept it "
|
|
"(check the TokenReview audience and the ServiceAccount binding)",
|
|
)
|
|
)
|
|
elif response.status_code == 403:
|
|
checks.append(
|
|
(
|
|
"fail",
|
|
"live /v1/check",
|
|
f"403 — authenticated, but the principal may not represent "
|
|
f"system {policy.system!r}",
|
|
)
|
|
)
|
|
elif response.status_code >= 400:
|
|
checks.append(
|
|
("fail", "live /v1/check", f"HTTP {response.status_code} from {target}")
|
|
)
|
|
else:
|
|
try:
|
|
decision = response.json()
|
|
except ValueError:
|
|
checks.append(("fail", "live /v1/check", "non-JSON decision"))
|
|
return checks
|
|
effect = str(decision.get("effect", "?"))
|
|
decision_id = decision.get("id") or decision.get("request_id") or "?"
|
|
checks.append(
|
|
("ok", "live /v1/check", f"HTTP 200, effect={effect}, decision={decision_id}")
|
|
)
|
|
return checks
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--config", type=Path, default=None, help="path to warden.yaml")
|
|
parser.add_argument(
|
|
"--url",
|
|
default=None,
|
|
help="flex-auth base URL to smoke (e.g. a port-forward of the warn pin)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
checks = run_checks(args.config, args.url)
|
|
glyph = {"ok": "✓", "fail": "✗", "skip": "·"}
|
|
print("flex-auth caller-identity readiness\n")
|
|
for status, label, detail in checks:
|
|
print(f" {glyph[status]} {label}: {detail}")
|
|
|
|
failed = [c for c in checks if c[0] == "fail"]
|
|
if failed:
|
|
print(
|
|
f"\nNOT READY — {len(failed)} check(s) failed. "
|
|
"Do not ask flex-auth to enforce caller authentication."
|
|
)
|
|
return 1
|
|
print(
|
|
"\nREADY — the calling side presents an identity. Verify "
|
|
"callerAuth.mode remains enforce on flex-auth-ops-warden after rollout. "
|
|
"Zone-specific PEP failure modes replace the retired global switches."
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|