Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
266 lines
9.1 KiB
Python
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"
|