ops-warden/scripts/check_policy_caller_identity.py
tegwick 654c05dece
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
WARDEN-WP-0031 T04: prove ops-warden's caller identity against the live pin
flex-auth's binding names system:serviceaccount:ops-warden:ops-warden, and that
ServiceAccount did not exist. deploy/kubernetes/caller-identity.yaml creates it
plus its namespace — no RBAC, automount off; it is never used to call the
Kubernetes API, only to be TokenReviewed. Applied to the railiance01 cluster.

Operator warden.yaml now uses caller_auth mode: command (kubectl create token,
audience flex-auth, 10m). Gate exits 0 live against a port-forward of the pin:
HTTP 200, effect=allow, decision:f3f7c88f9585582a.

The evidence is not that allow — warn allows anonymous callers too. It is that
the pin's "caller authentication warning" count held at 4 across two
authenticated runs. That is the ADHOC-2026-08-17-T01 condition.

Also gives the readiness probe a structurally complete context, so a deny means
the policy said no rather than the probe being malformed.

policy.enabled stays false. T05 waits on flex-auth setting callerAuth.mode:
enforce (their FLEX-WP-0016 T03).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 19:06:04 +02:00

203 lines
6.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""Readiness gate for the flex-auth ``policy.enabled`` flip (WARDEN-WP-0031).
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`` — and therefore ``policy.enabled: true`` cannot be set — until
ops-warden's calling side actually presents a token.
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", f"loaded; policy.enabled={str(policy.enabled).lower()}")
)
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 url is None and not policy.enabled:
checks.append(
(
"skip",
"live /v1/check",
f"policy.enabled=false; pass --url to smoke {target} anyway",
)
)
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, and do not set policy.enabled: true."
)
return 1
print(
"\nREADY — the calling side presents an identity. Next: tell flex-auth to set "
"callerAuth.mode: enforce on flex-auth-ops-warden, re-run this check, then set "
"policy.enabled: true with fail_closed: true."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())