Refuse explicit policy authentication and binding denials before side effects
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-08 16:46:00 +02:00
parent 11e5e8be0f
commit 31d9b6671c
5 changed files with 151 additions and 0 deletions

View file

@ -131,6 +131,12 @@ def check_sign_policy(cfg: PolicyConfig, spec: CertSpec) -> str | None:
response = httpx.post(url, json=request, headers=headers, timeout=10.0)
response.raise_for_status()
except httpx.HTTPStatusError as e:
if e.response.status_code in {401, 403}:
spec.policy_outcome = "deny"
raise CAError(
f"flex-auth refused sign policy check (HTTP {e.response.status_code}); "
"caller authentication or system binding was rejected"
) from e
_evaluator_failure(
f"flex-auth rejected sign policy check (HTTP {e.response.status_code}) "
f"for security zone {zone!r}",
@ -213,6 +219,11 @@ def check_fetch_policy(
response = httpx.post(url, json=request, headers=headers, timeout=10.0)
response.raise_for_status()
except httpx.HTTPStatusError as e:
if e.response.status_code in {401, 403}:
raise CAError(
f"flex-auth refused fetch policy check (HTTP {e.response.status_code}); "
"caller authentication or system binding was rejected"
) from e
_evaluator_failure(
f"flex-auth rejected fetch policy check (HTTP {e.response.status_code})",
fail_closed=fail_closed,

View file

@ -0,0 +1,48 @@
"""An explicit caller refusal cannot authorize a CA or credential side effect."""
from unittest.mock import patch
import httpx
import pytest
from warden.ca import CAError
from warden.config import PolicyConfig
from warden.models import ActorType, CertSpec
from warden.policy import check_fetch_policy, check_sign_policy
@pytest.mark.parametrize("status", [401, 403])
@pytest.mark.parametrize("failure_mode", ["fail_open", "fail_closed"])
@pytest.mark.parametrize("operation", ["sign", "fetch"])
def test_explicit_refusal_blocks_under_every_outage_mode(tmp_path, status, failure_mode, operation):
cfg = PolicyConfig(flex_auth_url="http://pdp.test")
cfg.failure_modes["unknown"] = failure_mode
public_key = tmp_path / "id.pub"
public_key.write_text("ssh-ed25519 AAAA test\n")
spec = CertSpec(actor_name="agt-example", actor_type=ActorType.AGT,
pubkey_path=public_key, ttl_hours=1, principals=["agt"])
response = httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check"),
text="untrusted response body must not be exposed")
with patch("warden.policy.httpx.post", return_value=response):
with pytest.raises(CAError, match=f"HTTP {status}") as error:
if operation == "sign":
check_sign_policy(cfg, spec)
else:
check_fetch_policy(cfg, need_id="forgejo-admin-api-token",
owner_repo="railiance-platform", domain=None)
assert "untrusted response" not in str(error.value)
if operation == "sign":
assert spec.policy_outcome == "deny"
@pytest.mark.parametrize("status", [500, 503])
@pytest.mark.parametrize("failure_mode", ["fail_open", "fail_closed"])
def test_actual_evaluator_failure_retains_declared_outage_mode(status, failure_mode):
cfg = PolicyConfig(flex_auth_url="http://pdp.test")
cfg.failure_modes["unknown"] = failure_mode
response = httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check"))
with patch("warden.policy.httpx.post", return_value=response):
if failure_mode == "fail_closed":
with pytest.raises(CAError, match=f"HTTP {status}"):
check_fetch_policy(cfg, need_id="example", owner_repo="example", domain=None)
else:
assert check_fetch_policy(cfg, need_id="example", owner_repo="example", domain=None) is None

View file

@ -234,6 +234,28 @@ def test_cli_proxy_requires_caller_auth(monkeypatch, tmp_path):
assert r.exit_code == 3
@pytest.mark.parametrize("status", [401, 403])
def test_cli_explicit_policy_refusal_never_fetches_or_starts_child(monkeypatch, tmp_path, status):
import httpx
_proxy_env(monkeypatch, tmp_path)
cfg = tmp_path / "warden.yaml"
cfg.write_text(cfg.read_text() + "policy:\n flex_auth_url: http://pdp.test\n")
monkeypatch.setenv("VAULT_TOKEN", "caller-test-value")
monkeypatch.setattr(
"warden.policy.httpx.post",
lambda *a, **k: httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check")),
)
calls = []
for name in ("proxy_exec", "proxy_fetch", "proxy_fetch_to_file", "proxy_fetch_wrapped"):
monkeypatch.setattr("warden.proxy." + name, lambda *a, **k: calls.append(True))
result = runner.invoke(app, ["access", "forgejo-admin-api-token", "--exec", "--field", "API_TOKEN", "--", "true"])
assert result.exit_code == 4
assert f"HTTP {status}" in result.output
assert "fail_open applied" not in result.output
assert calls == []
def test_cli_proxy_rejects_retired_no_policy_bypass(monkeypatch, tmp_path):
_proxy_env(monkeypatch, tmp_path)
monkeypatch.setenv("VAULT_TOKEN", "caller")

View file

@ -134,6 +134,11 @@ truth.
## Caller identity
An HTTP 401 or 403 from the policy service refuses the operation under every
outage profile. It is an explicit caller-authentication or system-binding refusal,
not evaluator unavailability. Resolve the admitted caller and resource contract;
do not retry with a different resource owner or disable caller enforcement.
The production flex-auth pin authenticates ops-warden with Kubernetes
TokenReview and binds `resource.system: ops-warden` to
`system:serviceaccount:ops-warden:ops-warden`. Supported token sources are:

View file

@ -0,0 +1,65 @@
---
id: WARDEN-WP-0039
type: workplan
title: "Preserve explicit policy caller refusals before credential and CA effects"
domain: infotech
repo: ops-warden
status: active
owner: codex
topic_slug: custodian
created: "2026-09-08"
updated: "2026-09-08"
origin: residual
origin_ref: HFACT-WP-0001
---
The factory critical-path review reproduced a live HTTP 403 from the reachable
PDP for the configured ops-warden caller and a railiance-platform secret read.
`check_fetch_policy` treated it as evaluator unavailability and used unknown-zone
fail-open. Caller-auth rejection is an explicit refusal, not a transport outage.
## Preserve explicit authentication and binding refusals
```task
id: WARDEN-WP-0039-T01
status: done
priority: high
assignee: the-custodian
```
HTTP 401 and 403 stop signing and credential fetch irrespective of the outage
profile. Do not expose response bodies or alter system identity to bypass the
refusal. Preserve configured behavior for genuine evaluator failures. Prove
both permissive and closed outage profiles and refusal before child execution.
## Verify and publish the correction
```task
id: WARDEN-WP-0039-T02
status: progress
priority: high
assignee: the-custodian
```
Run the policy and proxy suites, retain a value-free live refusal receipt,
publish the reviewed source, and verify the installed CLI refuses before any
credential transport. A refusal is not a successful credential-read admission.
## Resolve the credential proxy's admitted policy binding
```task
id: WARDEN-WP-0039-T03
status: wait
priority: high
assignee: the-custodian
blocking_reason: "The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership."
```
Consume the existing native-lane handoff (WARDEN-WP-0033 / SECRETS-WP-0006)
and flex-auth caller contract. Establish whether this interim transport needs an
admitted dedicated policy route or must finish its native handoff. Retain the
refusal until that contract yields positive and wrong-caller/owner/tenant
negative evidence. No credential read, secret generation, or policy grant is
authorized by this workplan alone. HFACT-WP-0001-T03 consumes this return.
Validation: 429 tests passed (4 integration tests deselected by the repository default); Ruff passed for changed Python files. Full tests used the declared phase-memory source and an isolated temporary memory store. The focused policy/proxy suite passed 69 tests. The existing authenticated SSH policy probe still returns HTTP 200/ALLOW, decision:f3f7c88f9585582a; the credential-owner request returns 403. No CA issue or credential read was performed by these probes.