2026-06-27 18:10:54 +02:00
|
|
|
"""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"
|
2026-07-18 16:59:37 +02:00
|
|
|
# WARDEN-WP-0029 third axis
|
|
|
|
|
assert c.organization_posture.id == "build"
|
|
|
|
|
assert "workstation_oidc_acceptable" in c.organization_posture.relaxations
|
2026-06-27 18:10:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- 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"},
|
2026-07-18 16:59:37 +02:00
|
|
|
"organization_posture": {
|
|
|
|
|
"id": "build",
|
|
|
|
|
"summary": "test build posture",
|
|
|
|
|
"relaxations": ["workstation_oidc_acceptable"],
|
|
|
|
|
"graduation_triggers": ["first_customer_data"],
|
|
|
|
|
},
|
2026-06-27 18:10:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-07-18 16:59:37 +02:00
|
|
|
assert payload["organization_posture"]["id"] == "build"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cli_policy_show_organization(monkeypatch):
|
|
|
|
|
monkeypatch.setenv("WARDEN_POSTURE_CATALOG", str(_repo_posture()))
|
|
|
|
|
r = runner.invoke(app, ["policy", "show", "build", "--json"])
|
|
|
|
|
assert r.exit_code == 0
|
|
|
|
|
payload = json.loads(r.stdout)
|
|
|
|
|
assert payload["axis"] == "organization_posture"
|
|
|
|
|
assert payload["id"] == "build"
|
2026-06-27 18:10:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|