401 lines
20 KiB
Python
401 lines
20 KiB
Python
"""
|
||
IwoooS security control coverage rollup.
|
||
|
||
This service consolidates committed security inventory snapshots into one
|
||
read-only control-plane view. It never queries live hosts, Wazuh, Kali,
|
||
Kubernetes, Docker, Nginx, Telegram, or secret stores.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from src.services.snapshot_paths import default_evaluations_dir, default_security_dir
|
||
|
||
_DEFAULT_SECURITY_DIR = default_security_dir(Path(__file__))
|
||
_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
|
||
_SCHEMA_VERSION = "iwooos_security_control_coverage_v1"
|
||
|
||
_SECURITY_SNAPSHOTS = {
|
||
"asset_ledger": "security-asset-control-ledger.snapshot.json",
|
||
"host_service": "host-service-config-inventory.snapshot.json",
|
||
"monitoring": "monitoring-alerting-observability-inventory.snapshot.json",
|
||
"ssh_network": "ssh-network-access-inventory.snapshot.json",
|
||
"wazuh_hosts": "wazuh-managed-host-coverage-gate.snapshot.json",
|
||
"agent_bounty": "agent-bounty-iwooos-onboarding-handoff.snapshot.json",
|
||
}
|
||
|
||
|
||
def load_latest_iwooos_security_control_coverage(
|
||
security_dir: Path | None = None,
|
||
evaluations_dir: Path | None = None,
|
||
) -> dict[str, Any]:
|
||
"""Load committed security-control inventory snapshots as one rollup."""
|
||
sec_dir = security_dir or _DEFAULT_SECURITY_DIR
|
||
eval_dir = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
|
||
|
||
snapshots = {
|
||
key: _load_json(sec_dir / filename)
|
||
for key, filename in _SECURITY_SNAPSHOTS.items()
|
||
}
|
||
runtime_inventory = _load_latest(eval_dir, "runtime_surface_inventory_*.json")
|
||
ai_inventory = _load_latest(eval_dir, "ai_agent_automation_inventory_snapshot_*.json")
|
||
snapshots["runtime_surface"] = runtime_inventory
|
||
snapshots["ai_agent_automation"] = ai_inventory
|
||
|
||
_require_snapshot_schemas(snapshots)
|
||
_require_zero_runtime_boundaries(snapshots)
|
||
|
||
domains = _build_domains(snapshots)
|
||
summary = _build_summary(snapshots, domains)
|
||
|
||
return {
|
||
"schema_version": _SCHEMA_VERSION,
|
||
"status": "committed_scope_rollup_ready_with_controlled_apply_exception",
|
||
"mode": "committed_snapshot_rollup_only_no_live_runtime_query",
|
||
"summary": summary,
|
||
"domains": domains,
|
||
"p0_next_actions": _build_p0_next_actions(),
|
||
"no_false_green_rules": [
|
||
"納管覆蓋總表只代表 committed snapshot 可讀,不代表所有主機已被 Wazuh manager registry 驗收。",
|
||
"route 200、transport observed、UI 可見、一般工作批准都不能當成 runtime 授權。",
|
||
"IwoooS ledger 的 owner response received / accepted、live evidence accepted、active scan、active response、Telegram send、host write 仍維持 0 / false;這不阻擋 AwoooP allowlisted controlled apply。",
|
||
"Nginx、Firewall、Workflow、Secret、K8s、Docker、systemd、AI Agent provider 變更都必須先進 execution packet、check-mode、rollback、verifier 與 KM / PlayBook trust,不得繞過受控路由。",
|
||
],
|
||
"source_refs": [
|
||
"docs/security/security-asset-control-ledger.snapshot.json",
|
||
"docs/security/host-service-config-inventory.snapshot.json",
|
||
"docs/security/monitoring-alerting-observability-inventory.snapshot.json",
|
||
"docs/security/ssh-network-access-inventory.snapshot.json",
|
||
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json",
|
||
"docs/security/agent-bounty-iwooos-onboarding-handoff.snapshot.json",
|
||
f"docs/evaluations/{runtime_inventory['_snapshot_name']}",
|
||
f"docs/evaluations/{ai_inventory['_snapshot_name']}",
|
||
],
|
||
}
|
||
|
||
|
||
def _load_json(path: Path) -> dict[str, Any]:
|
||
with path.open(encoding="utf-8") as handle:
|
||
payload = json.load(handle)
|
||
if not isinstance(payload, dict):
|
||
raise ValueError(f"{path}: expected JSON object")
|
||
payload["_snapshot_name"] = path.name
|
||
return payload
|
||
|
||
|
||
def _load_latest(directory: Path, pattern: str) -> dict[str, Any]:
|
||
candidates = sorted(directory.glob(pattern))
|
||
if not candidates:
|
||
raise FileNotFoundError(f"no snapshots found: {directory / pattern}")
|
||
return _load_json(candidates[-1])
|
||
|
||
|
||
def _require_snapshot_schemas(snapshots: dict[str, dict[str, Any]]) -> None:
|
||
expected = {
|
||
"asset_ledger": "security_asset_control_ledger_v1",
|
||
"host_service": "host_service_config_inventory_v1",
|
||
"monitoring": "monitoring_alerting_observability_inventory_v1",
|
||
"ssh_network": "ssh_network_access_inventory_v1",
|
||
"wazuh_hosts": "wazuh_managed_host_coverage_gate_v1",
|
||
"agent_bounty": "agent_bounty_iwooos_onboarding_handoff_v1",
|
||
"runtime_surface": "runtime_surface_inventory_v1",
|
||
"ai_agent_automation": "ai_agent_automation_inventory_snapshot_v1",
|
||
}
|
||
for key, schema in expected.items():
|
||
actual = snapshots[key].get("schema_version")
|
||
if actual != schema:
|
||
raise ValueError(f"{key}: expected schema_version={schema}, got {actual!r}")
|
||
|
||
|
||
def _require_zero_runtime_boundaries(snapshots: dict[str, dict[str, Any]]) -> None:
|
||
for key in ("asset_ledger", "host_service", "monitoring", "ssh_network", "wazuh_hosts"):
|
||
boundaries = snapshots[key].get("execution_boundaries") or {}
|
||
allowed = sorted(
|
||
name
|
||
for name, value in boundaries.items()
|
||
if name != "not_authorization" and value is not False
|
||
)
|
||
if allowed:
|
||
raise ValueError(f"{key}: execution boundaries must stay false: {allowed}")
|
||
|
||
runtime_approvals = snapshots["runtime_surface"].get("approval_boundaries") or {}
|
||
allowed_runtime = sorted(name for name, value in runtime_approvals.items() if value is not False)
|
||
if allowed_runtime:
|
||
raise ValueError(f"runtime_surface: approval boundaries must stay false: {allowed_runtime}")
|
||
|
||
runtime_operations = snapshots["runtime_surface"].get("operation_boundaries") or {}
|
||
if runtime_operations.get("read_only_api_allowed") is not True:
|
||
raise ValueError("runtime_surface: read_only_api_allowed must remain true")
|
||
blocked_runtime_ops = {
|
||
"live_k8s_query_allowed",
|
||
"kubectl_allowed",
|
||
"rollout_allowed",
|
||
"restart_allowed",
|
||
"scale_allowed",
|
||
"delete_allowed",
|
||
"secret_read_allowed",
|
||
"secret_plaintext_allowed",
|
||
"active_scan_allowed",
|
||
"production_route_change_allowed",
|
||
}
|
||
allowed_ops = sorted(name for name in blocked_runtime_ops if runtime_operations.get(name) is not False)
|
||
if allowed_ops:
|
||
raise ValueError(f"runtime_surface: operation boundaries must stay false: {allowed_ops}")
|
||
|
||
ai_program = snapshots["ai_agent_automation"].get("program_status") or {}
|
||
if ai_program.get("read_only_mode") is not True:
|
||
raise ValueError("ai_agent_automation: read_only_mode must remain true")
|
||
ai_approvals = snapshots["ai_agent_automation"].get("approval_boundaries") or {}
|
||
allowed_ai = sorted(name for name, value in ai_approvals.items() if value is not False)
|
||
if allowed_ai:
|
||
raise ValueError(f"ai_agent_automation: approval boundaries must stay false: {allowed_ai}")
|
||
|
||
agent_bounty_summary = snapshots["agent_bounty"].get("summary") or {}
|
||
blocked_agent_bounty = {
|
||
"owner_response_received",
|
||
"owner_response_accepted",
|
||
"repo_refs_truth_accepted",
|
||
"data_classification_accepted",
|
||
"deployment_boundary_accepted",
|
||
"external_agent_boundary_accepted",
|
||
"runtime_execution_authorized",
|
||
"runtime_gate_open",
|
||
"production_deploy_authorized",
|
||
"workflow_modification_authorized",
|
||
"secret_value_collection_authorized",
|
||
"external_agent_autonomy_authorized",
|
||
"auto_claim_submit_authorized",
|
||
"bounty_payout_authorized",
|
||
}
|
||
allowed_agent_bounty = sorted(
|
||
name for name in blocked_agent_bounty if agent_bounty_summary.get(name) is not False
|
||
)
|
||
if allowed_agent_bounty:
|
||
raise ValueError(f"agent_bounty: blocked summary flags must stay false: {allowed_agent_bounty}")
|
||
|
||
|
||
def _build_domains(snapshots: dict[str, dict[str, Any]]) -> list[dict[str, Any]]:
|
||
asset_summary = snapshots["asset_ledger"].get("summary") or {}
|
||
host_summary = snapshots["host_service"].get("summary") or {}
|
||
monitoring_summary = snapshots["monitoring"].get("summary") or {}
|
||
ssh_summary = snapshots["ssh_network"].get("summary") or {}
|
||
wazuh_summary = snapshots["wazuh_hosts"].get("summary") or {}
|
||
runtime_rollups = snapshots["runtime_surface"].get("rollups") or {}
|
||
runtime_program = snapshots["runtime_surface"].get("program_status") or {}
|
||
agent_bounty_summary = snapshots["agent_bounty"].get("summary") or {}
|
||
ai_program = snapshots["ai_agent_automation"].get("program_status") or {}
|
||
ai_assets = snapshots["ai_agent_automation"].get("assets") or []
|
||
|
||
return [
|
||
{
|
||
"domain_id": "high_value_asset_control",
|
||
"label": "高價值資產與配置總帳",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(asset_summary.get("security_asset_control_ledger_completion_percent")),
|
||
"scope_count": _as_int(asset_summary.get("asset_group_count")),
|
||
"write_capable_count": _as_int(asset_summary.get("c0_asset_group_count")),
|
||
"accepted_count": _as_int(asset_summary.get("owner_response_accepted_count")),
|
||
"blocked_count": _as_int(asset_summary.get("owner_packet_required_count")),
|
||
"status": "owner_packet_required",
|
||
"next_gate": "owner_packet_and_redacted_live_evidence",
|
||
"source_refs": ["docs/security/security-asset-control-ledger.snapshot.json"],
|
||
},
|
||
{
|
||
"domain_id": "host_service_runtime",
|
||
"label": "主機服務 / Docker / systemd",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(host_summary.get("coverage_percent_after_inventory")),
|
||
"scope_count": _as_int(host_summary.get("surface_count")),
|
||
"write_capable_count": _as_int(host_summary.get("write_capable_surface_count")),
|
||
"accepted_count": _as_int(host_summary.get("owner_response_accepted_count")),
|
||
"blocked_count": _as_int(host_summary.get("surfaces_requiring_owner_response_count")),
|
||
"status": "waiting_live_hash_and_owner_response",
|
||
"next_gate": "host_live_hash_restart_window_rollback_owner",
|
||
"source_refs": ["docs/security/host-service-config-inventory.snapshot.json"],
|
||
},
|
||
{
|
||
"domain_id": "monitoring_alerting_observability",
|
||
"label": "監控 / 告警 / 可觀測性 / 通知",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(monitoring_summary.get("coverage_percent_after_inventory")),
|
||
"scope_count": _as_int(monitoring_summary.get("surface_count")),
|
||
"write_capable_count": _as_int(monitoring_summary.get("write_capable_surface_count")),
|
||
"accepted_count": _as_int(monitoring_summary.get("owner_response_accepted_count")),
|
||
"blocked_count": _as_int(monitoring_summary.get("surfaces_requiring_owner_response_count")),
|
||
"status": "waiting_receiver_route_and_receipt_evidence",
|
||
"next_gate": "alert_readability_receiver_receipt_reload_owner",
|
||
"source_refs": ["docs/security/monitoring-alerting-observability-inventory.snapshot.json"],
|
||
},
|
||
{
|
||
"domain_id": "ssh_firewall_network_access",
|
||
"label": "SSH / Firewall / WireGuard / NodePort / NetworkPolicy",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(ssh_summary.get("coverage_percent_after_inventory")),
|
||
"scope_count": _as_int(ssh_summary.get("surface_count")),
|
||
"write_capable_count": _as_int(ssh_summary.get("write_capable_surface_count")),
|
||
"accepted_count": _as_int(ssh_summary.get("owner_response_accepted_count")),
|
||
"blocked_count": _as_int(ssh_summary.get("surfaces_requiring_owner_response_count")),
|
||
"status": "waiting_actor_before_after_and_recurrence_guard",
|
||
"next_gate": "post_incident_network_change_readback",
|
||
"source_refs": ["docs/security/ssh-network-access-inventory.snapshot.json"],
|
||
},
|
||
{
|
||
"domain_id": "awoooi_runtime_surfaces",
|
||
"label": "AWOOOI runtime 工作負載 / Secret / Ingress / CronJob",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(runtime_program.get("overall_completion_percent")),
|
||
"scope_count": _as_int(runtime_rollups.get("total_surfaces")),
|
||
"write_capable_count": len(runtime_rollups.get("secret_surface_ids") or []),
|
||
"accepted_count": 0,
|
||
"blocked_count": len(runtime_rollups.get("action_required_surface_ids") or []),
|
||
"status": "manifest_mapped_read_only_runtime_gate_closed",
|
||
"next_gate": "live_runtime_binding_owner_acceptance",
|
||
"source_refs": [f"docs/evaluations/{snapshots['runtime_surface']['_snapshot_name']}"],
|
||
},
|
||
{
|
||
"domain_id": "wazuh_managed_host_coverage",
|
||
"label": "Wazuh 主機納管與 manager registry",
|
||
"priority": "P0",
|
||
"coverage_percent": 70 if _as_int(wazuh_summary.get("manager_registry_accepted_count")) else 0,
|
||
"scope_count": _as_int(wazuh_summary.get("expected_host_scope_count")),
|
||
"write_capable_count": _as_int(wazuh_summary.get("agent_reenroll_authorized_count")),
|
||
"accepted_count": _as_int(wazuh_summary.get("manager_registry_accepted_count")),
|
||
"blocked_count": max(
|
||
_as_int(wazuh_summary.get("expected_host_scope_count"))
|
||
- _as_int(wazuh_summary.get("manager_registry_accepted_count")),
|
||
0,
|
||
),
|
||
"status": (
|
||
"manager_registry_readback_accepted_runtime_gate_closed"
|
||
if _as_int(wazuh_summary.get("manager_registry_accepted_count"))
|
||
else "waiting_manager_registry_readback"
|
||
),
|
||
"next_gate": "runtime_gate_owner_review_and_postcheck",
|
||
"source_refs": ["docs/security/wazuh-managed-host-coverage-gate.snapshot.json"],
|
||
},
|
||
{
|
||
"domain_id": "agent_bounty_protocol",
|
||
"label": "agent-bounty-protocol 產品邊界 / MCP / A2A",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(agent_bounty_summary.get("onboarding_handoff_completion_percent")),
|
||
"scope_count": len(snapshots["agent_bounty"].get("product_surfaces") or []),
|
||
"write_capable_count": 0,
|
||
"accepted_count": 0,
|
||
"blocked_count": len(snapshots["agent_bounty"].get("product_surfaces") or []),
|
||
"status": "draft_waiting_owner_review_runtime_gate_closed",
|
||
"next_gate": "repo_refs_data_classification_deployment_boundary_owner_acceptance",
|
||
"source_refs": ["docs/security/agent-bounty-iwooos-onboarding-handoff.snapshot.json"],
|
||
},
|
||
{
|
||
"domain_id": "ai_agent_automation",
|
||
"label": "AI Agent / Provider / 自動化資產",
|
||
"priority": "P0",
|
||
"coverage_percent": _as_int(ai_program.get("overall_completion_percent")),
|
||
"scope_count": len(ai_assets),
|
||
"write_capable_count": 0,
|
||
"accepted_count": 0,
|
||
"blocked_count": len(
|
||
[
|
||
asset
|
||
for asset in ai_assets
|
||
if isinstance(asset, dict) and asset.get("status") != "done"
|
||
]
|
||
),
|
||
"status": "read_only_inventory_runtime_write_gate_closed",
|
||
"next_gate": "owner_approved_runtime_write_gate_and_replay_shadow_canary",
|
||
"source_refs": [f"docs/evaluations/{snapshots['ai_agent_automation']['_snapshot_name']}"],
|
||
},
|
||
]
|
||
|
||
|
||
def _build_summary(snapshots: dict[str, dict[str, Any]], domains: list[dict[str, Any]]) -> dict[str, Any]:
|
||
visible_scope_count = sum(_as_int(domain.get("scope_count")) for domain in domains)
|
||
write_capable_count = sum(_as_int(domain.get("write_capable_count")) for domain in domains)
|
||
blocked_count = sum(_as_int(domain.get("blocked_count")) for domain in domains)
|
||
accepted_count = sum(_as_int(domain.get("accepted_count")) for domain in domains)
|
||
coverage_values = [_as_int(domain.get("coverage_percent")) for domain in domains]
|
||
|
||
return {
|
||
"source_snapshot_count": 8,
|
||
"control_domain_count": len(domains),
|
||
"visible_scope_unit_count": visible_scope_count,
|
||
"write_capable_scope_count": write_capable_count,
|
||
"blocked_scope_count": blocked_count,
|
||
"accepted_scope_count": accepted_count,
|
||
"control_plane_visibility_percent": round(sum(coverage_values) / len(coverage_values)),
|
||
"actual_runtime_acceptance_percent": 0,
|
||
"runtime_gate_count": 0,
|
||
"owner_response_received_count": 0,
|
||
"owner_response_accepted_count": 0,
|
||
"live_evidence_accepted_count": 0,
|
||
"wazuh_manager_registry_accepted_count": _summary_int(
|
||
snapshots["wazuh_hosts"],
|
||
"manager_registry_accepted_count",
|
||
),
|
||
"active_scan_authorized_count": 0,
|
||
"active_response_authorized_count": 0,
|
||
"telegram_send_authorized_count": 0,
|
||
"host_write_authorized_count": 0,
|
||
"secret_value_collected_count": 0,
|
||
"agent_bounty_runtime_gate_open_count": 0,
|
||
"ai_agent_runtime_write_gate_open_count": 0,
|
||
"asset_group_count": _summary_int(snapshots["asset_ledger"], "asset_group_count"),
|
||
"host_service_surface_count": _summary_int(snapshots["host_service"], "surface_count"),
|
||
"monitoring_surface_count": _summary_int(snapshots["monitoring"], "surface_count"),
|
||
"ssh_network_surface_count": _summary_int(snapshots["ssh_network"], "surface_count"),
|
||
"runtime_surface_count": _as_int((snapshots["runtime_surface"].get("rollups") or {}).get("total_surfaces")),
|
||
"wazuh_expected_host_scope_count": _summary_int(snapshots["wazuh_hosts"], "expected_host_scope_count"),
|
||
"agent_bounty_product_surface_count": len(snapshots["agent_bounty"].get("product_surfaces") or []),
|
||
"ai_agent_asset_count": len(snapshots["ai_agent_automation"].get("assets") or []),
|
||
"all_scope_runtime_controlled": False,
|
||
"allowlisted_controlled_apply_bypasses_iwooos_ledger": True,
|
||
"controlled_apply_policy": "low_medium_high_allowed_after_allowlist_check_mode_rollback_verifier_km",
|
||
"critical_break_glass_required": True,
|
||
}
|
||
|
||
|
||
def _build_p0_next_actions() -> list[dict[str, str]]:
|
||
return [
|
||
{
|
||
"priority": "P0-01",
|
||
"title": "Wazuh manager registry 全主機交叉驗收",
|
||
"required_evidence": "manager registry、agent status、dashboard API / RBAC / TLS readback、缺席主機 owner decision。",
|
||
},
|
||
{
|
||
"priority": "P0-02",
|
||
"title": "Host / Docker / systemd live hash 與維護窗口",
|
||
"required_evidence": "live config hash、restart window、rollback owner、post-check 指標、drift disposition。",
|
||
},
|
||
{
|
||
"priority": "P0-03",
|
||
"title": "Nginx / 公開入口 / Firewall 變更控管",
|
||
"required_evidence": "脫敏 live conf export、rendered diff、nginx -t、route smoke、actor before / after。",
|
||
},
|
||
{
|
||
"priority": "P0-04",
|
||
"title": "監控告警卡片化與 receipt",
|
||
"required_evidence": "可讀卡片格式、receiver route、Telegram / webhook receipt、silence / dedup / inhibit review。",
|
||
},
|
||
{
|
||
"priority": "P0-05",
|
||
"title": "AI Agent runtime write gate",
|
||
"required_evidence": "allowlist、check-mode、rollback、verifier、KM / PlayBook trust;付費 provider / replacement 另需 replay / shadow / canary。",
|
||
},
|
||
{
|
||
"priority": "P0-06",
|
||
"title": "agent-bounty-protocol 安全納管",
|
||
"required_evidence": "repo refs truth、data classification、MCP / A2A abuse boundary、deployment boundary、owner acceptance。",
|
||
},
|
||
]
|
||
|
||
|
||
def _summary_int(snapshot: dict[str, Any], key: str) -> int:
|
||
return _as_int((snapshot.get("summary") or {}).get(key))
|
||
|
||
|
||
def _as_int(value: Any) -> int:
|
||
return value if isinstance(value, int) else 0
|