net-kingdom/tools/security-scenario-composer/tests/test_security_scenario_composer.py
tegwick d96aab2321
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
feat(orchestration): compose security scenarios
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
2026-08-23 12:40:52 +02:00

266 lines
9.1 KiB
Python

import copy
import importlib.util
import sys
from pathlib import Path
import yaml
TOOL_DIR = Path(__file__).resolve().parents[1]
TOOL_PATH = TOOL_DIR / "security_scenario_composer.py"
sys.path.insert(0, str(TOOL_DIR))
SPEC = importlib.util.spec_from_file_location("security_scenario_composer", TOOL_PATH)
composer = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = composer
SPEC.loader.exec_module(composer)
def declaration_data(
declaration_id="owner.c0",
capability="c0.bootstrap-identity",
tier="C0",
requires=None,
satisfies=None,
):
requires = ["bare_host_trust"] if requires is None else requires
satisfies = ["bootstrap_identity_trust"] if satisfies is None else satisfies
return {
"apiVersion": composer.contract.API_VERSION,
"kind": composer.contract.KIND,
"metadata": {
"id": declaration_id,
"name": declaration_id,
"owner": "owner",
"repo": "owner-repo",
"domain": "infotech",
"contract_version": "0.1",
},
"spec": {
"playbook": {
"path": "playbooks/reference.yaml",
"type": "reference",
"invocation": "make reference",
"description": "Reference entry point.",
},
"capabilities": [
{
"id": capability,
"tier": tier,
"resource_kinds": ["identities"],
"description": "Reference capability.",
}
],
"parameters": [
{
"name": "target",
"type": "string",
"required": True,
"default": "reference",
"sensitivity": "operational",
"tuning_authority": "netkingdom_tunable",
"description": "Reference target.",
},
{
"name": "secure_mode",
"type": "boolean",
"required": False,
"default": True,
"sensitivity": "security_sensitive",
"tuning_authority": "platform_only",
"description": "Security-sensitive reference switch.",
},
],
"responsibilities": [
{
"resource_kind": "identities",
"owner": "owner",
"resources": ["identity:reference"],
"repo_owns": "Reference execution.",
"netkingdom_orchestrates": "Reference selection.",
}
],
"trust": {
"requires": [
{"state": state, "readiness_checks": []}
for state in requires
],
"satisfies": [
{
"state": state,
"readiness_checks": [
{
"id": f"{state}-ready",
"description": f"{state} is ready.",
"evidence": "reference evidence",
}
],
}
for state in satisfies
],
},
"catalog": {
"publish": f"capabilities/playbooks/{declaration_id}.yaml",
"maturity": "reference",
"consumers": ["netkingdom-security-scenario-composer"],
},
},
}
def declaration(tmp_path, data, filename="declaration.yaml"):
path = tmp_path / filename
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
return composer.Declaration(path=path, data=data)
def scenario(capabilities=None):
capabilities = ["c0.bootstrap-identity"] if capabilities is None else capabilities
return {
"id": "scenario:test",
"authority": "netkingdom",
"initial_trust": ["bare_host_trust"],
"requires": {"capabilities": capabilities},
"providers": {},
"parameter_overrides": {},
}
def messages(issues):
return [item.message for item in issues if item.level == "ERROR"]
def test_single_provider_emits_plan_only_handoff(tmp_path):
item = declaration(tmp_path, declaration_data())
issues, plan = composer.compose_scenario([item], scenario())
assert messages(issues) == []
assert plan["execution"] == {
"mode": "plan-only",
"permitted": False,
"reason": "Composition does not authorize or perform provider execution.",
}
assert plan["execution_steps"][0]["executor_owner"] == "owner"
assert plan["execution_steps"][0]["readiness_obligations"][0]["checks"]
assert plan["responsibility_map"][0]["declaration_id"] == "owner.c0"
assert plan["composition_digest"].startswith("sha256:")
def test_ambiguous_provider_requires_pin(tmp_path):
first = declaration(tmp_path, declaration_data("owner.c0-a"), "a.yaml")
second = declaration(tmp_path, declaration_data("owner.c0-b"), "b.yaml")
issues, plan = composer.compose_scenario([second, first], scenario())
assert plan is None
assert any("ambiguous providers" in message for message in messages(issues))
def test_explicit_provider_pin_resolves_ambiguity(tmp_path):
first = declaration(tmp_path, declaration_data("owner.c0-a"), "a.yaml")
second = declaration(tmp_path, declaration_data("owner.c0-b"), "b.yaml")
request = scenario()
request["providers"] = {"c0.bootstrap-identity": "owner.c0-b"}
issues, plan = composer.compose_scenario([second, first], request)
assert messages(issues) == []
assert plan["capability_providers"] == {"c0.bootstrap-identity": "owner.c0-b"}
def test_bad_provider_pin_fails_closed(tmp_path):
item = declaration(tmp_path, declaration_data())
request = scenario()
request["providers"] = {"c0.bootstrap-identity": "owner.missing"}
issues, plan = composer.compose_scenario([item], request)
assert plan is None
assert any("does not provide" in message for message in messages(issues))
def test_override_for_unselected_declaration_fails(tmp_path):
item = declaration(tmp_path, declaration_data())
request = scenario()
request["parameter_overrides"] = {"owner.other": {"target": "wrong"}}
issues, plan = composer.compose_scenario([item], request)
assert plan is None
assert "override targets an unselected declaration" in messages(issues)
def test_tenant_cannot_override_platform_only_parameter(tmp_path):
item = declaration(tmp_path, declaration_data())
request = scenario()
request["authority"] = "tenant"
request["parameter_overrides"] = {"owner.c0": {"secure_mode": False}}
issues, plan = composer.compose_scenario([item], request)
assert plan is None
assert any("tenant authority cannot override" in message for message in messages(issues))
def test_trust_dependencies_override_request_order(tmp_path):
c0 = declaration(tmp_path, declaration_data(), "c0.yaml")
c1_data = declaration_data(
"owner.c1",
"c1.lightweight-sso",
"C1",
requires=["bootstrap_identity_trust"],
satisfies=["runtime_identity_trust"],
)
c1 = declaration(tmp_path, c1_data, "c1.yaml")
request = scenario(["c1.lightweight-sso", "c0.bootstrap-identity"])
issues, plan = composer.compose_scenario([c1, c0], request)
assert messages(issues) == []
assert [step["declaration_id"] for step in plan["execution_steps"]] == [
"owner.c0",
"owner.c1",
]
assert "runtime_identity_trust" in plan["planned_final_trust"]
def test_unresolved_trust_fails_closed(tmp_path):
item = declaration(tmp_path, declaration_data())
request = scenario()
request["initial_trust"] = []
issues, plan = composer.compose_scenario([item], request)
assert plan is None
assert any("unresolved trust states" in message for message in messages(issues))
def test_duplicate_declaration_ids_fail_closed(tmp_path):
data = declaration_data()
first = declaration(tmp_path, data, "a.yaml")
second = declaration(tmp_path, copy.deepcopy(data), "b.yaml")
issues, plan = composer.compose_scenario([first, second], scenario())
assert plan is None
assert any("duplicate declaration id" in message for message in messages(issues))
def test_checked_in_c0_scenario_composes():
repo_root = Path(__file__).resolve().parents[3]
declaration_path = repo_root / "capabilities/playbooks/net-kingdom.local-identity.yaml"
scenario_path = repo_root / "examples/security-scenarios/c0-local-identity.yaml"
item = composer.Declaration(
path=declaration_path,
data=composer.contract.load_yaml(declaration_path),
)
request = composer.contract.load_yaml(scenario_path)
issues, plan = composer.compose_scenario([item], request)
assert messages(issues) == []
assert plan["capability_providers"] == {
"c0.bootstrap-identity": "net-kingdom.local-identity"
}
assert plan["execution_steps"][0]["repo"] == "net-kingdom"