Files
ops-warden/tests/test_posture.py
tegwick 0812d7303d feat(WARDEN-WP-0015): T2 — machine-readable posture descriptors + warden policy
Adds registry/policy/security-posture.yaml (Axis A env postures, Axis B
maturity levels M0-M3, dataclass_floor, lattice rule — no secret
material) and src/warden/posture.py: typed loader with validation
(unique/contiguous ranks, floor references known levels) and the pure
can_deliver() lattice helper (no-write-down: prod posture + workload
maturity >= secret required_maturity + dataclass floor). New `warden
policy list|show` read-only lookup mirroring `warden route`.
tests/test_posture.py covers load, the allow/deny lattice matrix,
validation rejections, and CLI. 184 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:10:54 +02:00

145 lines
4.8 KiB
Python

"""Tests for Workload Security Posture descriptors + lattice (WP-0015 T2)."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
import yaml
from typer.testing import CliRunner
from warden.cli import app
from warden.posture import PostureError, load_posture
runner = CliRunner()
def _repo_posture() -> Path:
return Path(__file__).resolve().parents[1] / "registry" / "policy" / "security-posture.yaml"
# --- real descriptors load + shape -----------------------------------------
def test_real_descriptors_load():
c = load_posture(_repo_posture())
assert {e.id for e in c.env_postures} == {"dev", "test", "prod"}
assert {m.id for m in c.maturity_levels} == {"M0", "M1", "M2", "M3"}
assert c.requires_env_posture == "prod"
# YAML `on` gotcha must not have become a boolean
assert c.env("test").audit == "on"
# --- the secret-flow lattice -----------------------------------------------
def test_lattice_allows_matched_prod_workload():
c = load_posture(_repo_posture())
ok, why = c.can_deliver(
workload_env="prod", workload_maturity="M3",
secret_required_maturity="M3", secret_dataclass="restricted",
)
assert ok and why == []
def test_lattice_denies_below_required_maturity():
c = load_posture(_repo_posture())
ok, why = c.can_deliver(
workload_env="prod", workload_maturity="M1",
secret_required_maturity="M3", secret_dataclass="restricted",
)
assert not ok
assert any("maturity M1 < required M3" in r for r in why)
assert any("floor M3" in r for r in why)
def test_lattice_denies_non_prod_posture():
c = load_posture(_repo_posture())
ok, why = c.can_deliver(
workload_env="test", workload_maturity="M3",
secret_required_maturity="M1", secret_dataclass="internal",
)
assert not ok and any("env posture" in r for r in why)
def test_lattice_unknown_maturity_raises():
c = load_posture(_repo_posture())
with pytest.raises(PostureError, match="unknown maturity"):
c.can_deliver(
workload_env="prod", workload_maturity="M9",
secret_required_maturity="M1",
)
# --- validation ------------------------------------------------------------
def _write(tmp_path, data) -> Path:
p = tmp_path / "security-posture.yaml"
p.write_text(yaml.dump(data))
return p
def _valid_data() -> dict:
return {
"version": 1,
"env_postures": [
{"id": "dev", "rank": 0, "backend": "m", "real_values": "f",
"unseal": "n", "real_user_data": "never", "audit": "optional"},
{"id": "prod", "rank": 1, "backend": "b", "real_values": "g",
"unseal": "s", "real_user_data": "allowed", "audit": "full"},
],
"maturity_levels": [
{"id": "M0", "rank": 0, "phase": "poc", "max_dataclass": "synthetic", "promotion_gate": []},
{"id": "M1", "rank": 1, "phase": "ga", "max_dataclass": "internal", "promotion_gate": ["x"]},
],
"dataclass_floor": {"synthetic": "M0", "internal": "M1"},
"lattice": {"requires_env_posture": "prod", "rule": "no-write-down"},
}
def test_valid_minimal_loads(tmp_path):
c = load_posture(_write(tmp_path, _valid_data()))
assert c.requires_env_posture == "prod"
def test_non_contiguous_ranks_rejected(tmp_path):
data = _valid_data()
data["maturity_levels"][1]["rank"] = 5
with pytest.raises(PostureError, match="contiguous"):
load_posture(_write(tmp_path, data))
def test_dataclass_floor_unknown_level_rejected(tmp_path):
data = _valid_data()
data["dataclass_floor"]["internal"] = "M9"
with pytest.raises(PostureError, match="not a known maturity level"):
load_posture(_write(tmp_path, data))
def test_lattice_requires_known_env_posture(tmp_path):
data = _valid_data()
data["lattice"]["requires_env_posture"] = "staging"
with pytest.raises(PostureError, match="not an env posture"):
load_posture(_write(tmp_path, data))
# --- CLI -------------------------------------------------------------------
def test_cli_policy_list(monkeypatch):
monkeypatch.setenv("WARDEN_POSTURE_CATALOG", str(_repo_posture()))
r = runner.invoke(app, ["policy", "list"])
assert r.exit_code == 0
assert "environment posture" in r.stdout and "workload maturity" in r.stdout
def test_cli_policy_list_json(monkeypatch):
monkeypatch.setenv("WARDEN_POSTURE_CATALOG", str(_repo_posture()))
r = runner.invoke(app, ["policy", "list", "--json"])
payload = json.loads(r.stdout)
assert payload["requires_env_posture"] == "prod"
assert len(payload["maturity_levels"]) == 4
def test_cli_policy_show_unknown_exits_1(monkeypatch):
monkeypatch.setenv("WARDEN_POSTURE_CATALOG", str(_repo_posture()))
r = runner.invoke(app, ["policy", "show", "nope"])
assert r.exit_code == 1