generated from coulomb/repo-seed
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>
145 lines
4.8 KiB
Python
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
|