Files
awoooi/scripts/security/external-host-intrusion-prevention-control.py
Your Name 5820ca90cc
Some checks failed
CD Pipeline / tests (push) Successful in 1m45s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 17m44s
CD Pipeline / post-deploy-checks (push) Has been cancelled
feat(iwooos): 新增外部入侵主機防堵控制
2026-06-18 10:24:33 +08:00

658 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
IwoooS 外部入侵主機防堵控制矩陣產生器。
本工具只產生 repo 內 snapshot將外部入口、SSH / sudo、端口 /
防火牆、Nginx、runner、secret、Docker / systemd、K8s / ArgoCD、
Wazuh、套件更新、backup / restore 與跨專案同步收斂成同一份可審核
控制矩陣。
它不連線主機、不 SSH、不讀 live config、不改 Nginx / firewall / K8s、
不啟用 Wazuh active response、不重啟服務、不更新套件、不輪替 secret、
不做 active scan也不把 UI 可見、服務 200 或外部 Agent 宣稱當成已防堵。
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TAIPEI = timezone(timedelta(hours=8))
HOST_ALIASES = ["host-110", "host-188", "dev-host-111", "dev-host-168"]
SENSOR_ALIASES = ["sensor-kali-112"]
PREVENTION_DOMAINS = [
{
"domain_id": "public_ingress_gateway",
"label": "公開入口 / DNS / TLS / Nginx / public gateway",
"control_tier": "C0",
"immediate_risk": "未受控 reload、route drift、上游錯配或 TLS / ACME 變更會造成外部暴露或服務中斷。",
},
{
"domain_id": "ssh_sudo_access",
"label": "SSH / sudo / authorized_keys / known_hosts",
"control_tier": "C0",
"immediate_risk": "未受控登入、sudo 權限、host key 漂移或跳板憑證會擴大入侵面。",
},
{
"domain_id": "firewall_network_policy",
"label": "Firewall / WireGuard / NodePort / NetworkPolicy",
"control_tier": "C0",
"immediate_risk": "端口開關、內網路由、NodePort 或 NetworkPolicy 漂移會直接改變可攻擊面。",
},
{
"domain_id": "host_runtime_services",
"label": "Docker / systemd / process / port binding",
"control_tier": "C0",
"immediate_risk": "容器重啟、failed unit、未知 process、port binding 或 persistence 會影響服務與入侵駐留判讀。",
},
{
"domain_id": "k8s_gitops_runtime",
"label": "K8s / ArgoCD / RBAC / Secret metadata",
"control_tier": "C0",
"immediate_risk": "GitOps drift、RBAC / Secret metadata、image pull 或 pending workload 可能隱藏惡意部署與供應鏈變更。",
},
{
"domain_id": "runner_workflow_supply_chain",
"label": "Gitea runner / workflow / deploy key / webhook",
"control_tier": "C0",
"immediate_risk": "runner、workflow、deploy key 或 webhook 被改動會直接打開 production 寫入路徑。",
},
{
"domain_id": "secret_credential_exposure",
"label": "Secret / session / API token / credential hygiene",
"control_tier": "C0",
"immediate_risk": "secret、session、API token 或 env dump 外洩會讓修復失效並擴散到其他產品。",
},
{
"domain_id": "wazuh_detection_response",
"label": "Wazuh manager / agent / rule / active response",
"control_tier": "C0",
"immediate_risk": "偵測與 response 邊界不清會造成假綠燈、誤隔離、漏封鎖或錯信外部宣稱。",
},
{
"domain_id": "package_patch_vulnerability",
"label": "套件更新 / CVE / maintenance window",
"control_tier": "C1",
"immediate_risk": "未修補套件與服務版本過舊會保留可利用入口,但無維護窗口直接升級也會造成中斷。",
},
{
"domain_id": "backup_restore_resilience",
"label": "Backup / restore / offsite / escrow / rollback",
"control_tier": "C1",
"immediate_risk": "沒有可驗證 restore 與 rollback入侵後清除、重建與服務恢復都沒有可信退路。",
},
{
"domain_id": "monitoring_alerting_false_green",
"label": "Monitoring / alerting / no-false-green",
"control_tier": "C1",
"immediate_risk": "route 200、dashboard up 或 alert quiet 若被當成資安通過,會掩蓋持久化與告警失效。",
},
{
"domain_id": "cross_project_freeze_sync",
"label": "跨專案 freeze / operator sync / break-glass",
"control_tier": "C1",
"immediate_risk": "多 session 或多專案並行修改會造成重複修復、rebase 衝突、錯誤 reload 或未同步封鎖。",
},
]
REQUIRED_OWNER_FIELDS = [
"control_id",
"owner_role",
"owner_team",
"decision",
"decision_reason",
"affected_scope_aliases",
"exposed_surface_aliases",
"current_risk",
"immediate_harm_if_delayed",
"redacted_evidence_refs",
"wazuh_event_refs",
"host_forensic_refs",
"config_diff_refs",
"before_state_ref",
"after_state_ref",
"maintenance_window",
"break_glass_reason",
"rollback_owner",
"rollback_plan_ref",
"validation_metrics",
"postcheck_owner",
"postcheck_ref",
"cross_project_sync_ref",
"communication_channel",
"secret_value_absence_attestation",
"raw_payload_absence_attestation",
"no_internal_name_publication_attestation",
"no_false_green_attestation",
"service_impact_assessment",
"ai_provider_impact_assessment",
"monitoring_alert_impact_assessment",
"backup_restore_impact_assessment",
"recurrence_guard_ref",
"followup_owner",
"expiry_or_review_date",
"reviewer_attestation",
]
REVIEWER_CHECKS = [
{"check_id": "scope_alias_only", "instruction": "只接受 host alias、service alias、product alias不得公開內網 IP、帳號 namespace 或 repo owner 原名。"},
{"check_id": "owner_role_and_team_present", "instruction": "每個防堵候選都需要 owner role / team不能匿名批准。"},
{"check_id": "decision_and_reason_present", "instruction": "需要明確 decision 與 decision reason不得只寫批准或已處理。"},
{"check_id": "wazuh_event_refs_required_for_intrusion", "instruction": "主機入侵、惡意程式或 RCE 類候選必須有 Wazuh event refs。"},
{"check_id": "host_forensic_refs_required", "instruction": "需要 auth、process、network、FIM、package 或 persistence refs服務恢復不能取代鑑識。"},
{"check_id": "gateway_diff_required", "instruction": "公開入口、Nginx、TLS、route 或 upstream 變更需有 source-to-live diff refs。"},
{"check_id": "firewall_before_after_required", "instruction": "端口、防火牆、WireGuard、NodePort 或 NetworkPolicy 需有 before / after state。"},
{"check_id": "ssh_sudo_scope_required", "instruction": "SSH、sudo、deploy key 或 known_hosts 需標示範圍與回滾 owner。"},
{"check_id": "runner_workflow_diff_required", "instruction": "runner、workflow、webhook 或 deploy key 需有 diff、run ref、permission scope 與 workspace cleanup。"},
{"check_id": "secret_values_absent", "instruction": "不得收 secret value、hash、partial token、cookie、env dump、private key 或 runner token。"},
{"check_id": "raw_payload_absent", "instruction": "不得保存 raw Wazuh payload、raw log、raw journal、完整命令輸出或未脫敏截圖。"},
{"check_id": "maintenance_window_required", "instruction": "任何可能中斷服務的防堵都需要 maintenance window。"},
{"check_id": "rollback_owner_required", "instruction": "任何 host、gateway、firewall、runner、K8s 或 secret 相關候選都需要 rollback owner。"},
{"check_id": "validation_metrics_required", "instruction": "防堵後驗證需列 route、API、agent、alert、backup 或 service health 指標。"},
{"check_id": "cross_project_sync_required", "instruction": "影響 AWOOOI、AwoooP、IwoooS、VibeWork、agent-bounty-protocol、stock 或監控需有同步 ref。"},
{"check_id": "no_false_green", "instruction": "route 200、dashboard up、agent active、CD success、UI 可見都不能單獨當資安驗收。"},
{"check_id": "external_agent_claim_not_trusted", "instruction": "外部 Agent 宣稱已封鎖、清除或 Zero-Trust 不能直接 accepted。"},
{"check_id": "active_response_dry_run_first", "instruction": "Wazuh active response 必須先 dry-run、驗證 blast radius 與 rollback不得直接 production enable。"},
{"check_id": "firewall_change_break_glass_only", "instruction": "firewall drop、port close/open 需 break-glass 或 maintenance window不得由候選自動執行。"},
{"check_id": "host_service_write_blocked", "instruction": "不得由本 artifact 觸發 Docker、systemd、kill process、package upgrade 或 reboot。"},
{"check_id": "k8s_argocd_write_blocked", "instruction": "不得由本 artifact 觸發 ArgoCD sync、kubectl、Helm、NetworkPolicy 或 RBAC apply。"},
{"check_id": "backup_restore_not_assumed", "instruction": "備份存在、冷啟動分數或 UI 可見不能代表 restore / rollback 可用。"},
{"check_id": "credential_rotation_requires_decision", "instruction": "credential rotation 需 owner decision、blast radius、dependent services 與 rollback plan。"},
{"check_id": "package_upgrade_requires_window", "instruction": "套件更新、kernel、Docker、Nginx、Wazuh agent 或 Kali 更新需維護窗口與 postcheck。"},
{"check_id": "public_frontend_no_internal_transcript", "instruction": "前台不得顯示工作視窗、內部對話、抱怨、raw namespace 或截圖原文。"},
{"check_id": "production_write_stays_zero", "instruction": "防堵矩陣不是 production write 授權runtime gate 與 action button 必須維持 0。"},
{"check_id": "evidence_refs_redacted", "instruction": "只收脫敏 ref、ticket、artifact pointer不得貼 raw secret 或 raw payload。"},
{"check_id": "parallel_session_conflict_checked", "instruction": "需確認其他 session 是否正在操作同一主機、Nginx、runner、workflow 或 secret。"},
{"check_id": "service_dependency_map_present", "instruction": "端口、gateway、host service 或 AI provider 變更需列服務依賴與受影響產品 alias。"},
{"check_id": "operator_notification_present", "instruction": "即時危害或可能中斷的防堵需有 operator notification / receipt plan。"},
{"check_id": "postcheck_independent", "instruction": "postcheck 必須由獨立於操作人的 reviewer 或 automation readback 驗證。"},
{"check_id": "recurrence_guard_present", "instruction": "需補防再發 guard例如 config drift、runner freeze、secret hygiene、Wazuh rule coverage 或 firewall diff guard。"},
{"check_id": "expiry_or_review_date_present", "instruction": "臨時 freeze、break-glass 或例外需有到期或複審日期。"},
{"check_id": "counts_transition_safe", "instruction": "received / accepted / authorized / executed / allowed 類計數只能由正式驗收推進。"},
]
OUTCOME_LANES = [
{"lane_id": "waiting_owner_prevention_packet", "meaning": "防堵候選已建立,等待 owner 補齊欄位與脫敏證據。"},
{"lane_id": "request_wazuh_event_supplement", "meaning": "缺 Wazuh event / alert refs 時要求補件。"},
{"lane_id": "request_host_forensics_supplement", "meaning": "缺主機鑑識 refs 時要求補件。"},
{"lane_id": "request_config_diff_supplement", "meaning": "缺 Nginx、firewall、runner、workflow、K8s 或 secret metadata diff 時要求補件。"},
{"lane_id": "request_maintenance_window", "meaning": "涉及服務中斷、重啟、封鎖、更新或 active response 時要求維護窗口。"},
{"lane_id": "request_break_glass_record", "meaning": "即時封鎖或緊急變更需補 break-glass reason 與通知紀錄。"},
{"lane_id": "quarantine_secret_or_raw_payload", "meaning": "收到 secret、raw log、raw payload、env dump 或未脫敏截圖時隔離。"},
{"lane_id": "reject_claim_without_evidence", "meaning": "只有外部宣稱、route 200、dashboard up 或 agent active 時拒收。"},
{"lane_id": "route_to_high_value_config_gate", "meaning": "涉及高價值配置時串到既有配置控管 gate。"},
{"lane_id": "ready_for_prevention_reviewer_review", "meaning": "metadata 合格後進 reviewer review不自動執行。"},
{"lane_id": "waiting_runtime_authorization", "meaning": "即使 accepted也需獨立批准才可進 runtime 防堵。"},
{"lane_id": "blocked_no_rollback_or_postcheck", "meaning": "缺 rollback owner、validation 或 postcheck 時不得進下一關。"},
]
BLOCKED_ACTIONS = [
"ssh_read",
"ssh_write",
"sudo_action",
"host_file_read",
"host_file_write",
"host_live_config_read",
"iptables_change",
"ufw_change",
"firewall_drop",
"firewall_allow",
"port_close",
"port_open",
"wireguard_change",
"nodeport_change",
"network_policy_apply",
"nginx_test",
"nginx_reload",
"nginx_conf_write",
"certbot_renew",
"dns_record_change",
"route_change",
"upstream_change",
"websocket_route_change",
"docker_restart",
"docker_kill",
"docker_compose_up",
"docker_compose_down",
"systemctl_restart",
"systemctl_stop",
"systemctl_start",
"kill_process",
"quarantine_host",
"reboot_host",
"apt_update",
"apt_upgrade",
"package_install",
"kernel_upgrade",
"wazuh_active_response_enable",
"wazuh_agent_install",
"wazuh_agent_restart",
"wazuh_rule_change",
"wazuh_decoder_change",
"wazuh_api_live_query_without_owner_gate",
"argocd_sync",
"kubectl_apply",
"kubectl_delete",
"helm_upgrade",
"rbac_change",
"k8s_secret_change",
"workflow_modification",
"gitea_action_dispatch",
"runner_label_change",
"runner_config_change",
"deploy_key_change",
"webhook_change",
"repo_secret_change",
"secret_rotation",
"secret_store_read",
"collect_password",
"collect_private_key",
"collect_runner_token",
"collect_webhook_secret",
"collect_cookie_or_session",
"collect_env_dump",
"store_raw_wazuh_payload",
"store_raw_syslog",
"store_raw_journal",
"store_raw_command_output",
"store_unredacted_screenshot",
"active_scan",
"credentialed_scan",
"exploit_validation",
"backup_run",
"restore_run",
"offsite_sync",
"remote_delete",
"retention_change",
"database_migration",
"production_write",
"add_action_button",
"open_runtime_gate",
"force_push",
]
CONTROL_CANDIDATES = [
{
"control_id": "external_host_prevention:public_gateway_freeze_diff",
"scope_alias": "public-gateway",
"label": "公開入口 / Nginx 變更 freeze 與 source-to-live diff",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "public_ingress_gateway",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:ssh_sudo_access_lockdown",
"scope_alias": "ssh-sudo-access",
"label": "SSH / sudo / authorized_keys / known_hosts 存取收斂",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "ssh_sudo_access",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": True,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:firewall_port_baseline_guard",
"scope_alias": "firewall-port-baseline",
"label": "端口 / 防火牆 / WireGuard / NodePort baseline",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "firewall_network_policy",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": True,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:host_service_persistence_baseline",
"scope_alias": "host-service-persistence",
"label": "Docker / systemd / process / persistence baseline",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "host_runtime_services",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": True,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:k8s_argocd_drift_containment",
"scope_alias": "k8s-argocd-drift",
"label": "K8s / ArgoCD drift、RBAC 與 Secret metadata containment",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "k8s_gitops_runtime",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:runner_workflow_secret_freeze",
"scope_alias": "runner-workflow-secret-freeze",
"label": "Runner / workflow / deploy key / secret freeze",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "runner_workflow_supply_chain",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": True,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:credential_exposure_rotation_gate",
"scope_alias": "credential-exposure",
"label": "疑似 credential exposure 的輪替決策 gate",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "secret_credential_exposure",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": True,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:wazuh_event_triage_gate",
"scope_alias": "wazuh-event-triage",
"label": "Wazuh event triage 與事件分流 gate",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "wazuh_detection_response",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": False,
},
{
"control_id": "external_host_prevention:wazuh_active_response_dry_run",
"scope_alias": "wazuh-active-response-dry-run",
"label": "Wazuh active response dry-run / blast radius / rollback",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "wazuh_detection_response",
"requires_wazuh_event_refs": True,
"requires_host_forensics_refs": True,
"requires_config_diff_refs": False,
},
{
"control_id": "external_host_prevention:backup_restore_recovery_gate",
"scope_alias": "backup-restore-recovery",
"label": "Backup / restore / rollback 可用性防堵 gate",
"control_tier": "C0",
"priority": "P0",
"prevention_domain": "backup_restore_resilience",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": False,
},
{
"control_id": "external_host_prevention:package_patch_window",
"scope_alias": "package-patch-window",
"label": "套件更新 / CVE 修補維護窗口候選",
"control_tier": "C1",
"priority": "P0",
"prevention_domain": "package_patch_vulnerability",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": False,
},
{
"control_id": "external_host_prevention:public_runtime_auth_route_guard",
"scope_alias": "public-runtime-auth-route",
"label": "公開 / 後台 / API runtime auth 與 route guard",
"control_tier": "C1",
"priority": "P0",
"prevention_domain": "public_ingress_gateway",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": True,
},
{
"control_id": "external_host_prevention:monitoring_no_false_green_gate",
"scope_alias": "monitoring-no-false-green",
"label": "監控 / 告警 / route 200 no-false-green gate",
"control_tier": "C1",
"priority": "P0",
"prevention_domain": "monitoring_alerting_false_green",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": False,
},
{
"control_id": "external_host_prevention:cross_project_break_glass_sync",
"scope_alias": "cross-project-break-glass-sync",
"label": "跨專案 freeze、break-glass 與 operator sync",
"control_tier": "C1",
"priority": "P0",
"prevention_domain": "cross_project_freeze_sync",
"requires_wazuh_event_refs": False,
"requires_host_forensics_refs": False,
"requires_config_diff_refs": False,
},
]
EXECUTION_BOUNDARIES = {
"runtime_execution_authorized": False,
"host_read_authorized": False,
"host_write_authorized": False,
"ssh_read_authorized": False,
"ssh_write_authorized": False,
"sudo_action_authorized": False,
"host_live_config_read_authorized": False,
"firewall_change_authorized": False,
"port_close_authorized": False,
"port_open_authorized": False,
"nginx_test_authorized": False,
"nginx_reload_authorized": False,
"dns_tls_change_authorized": False,
"certbot_renew_authorized": False,
"docker_restart_authorized": False,
"systemctl_restart_authorized": False,
"process_kill_authorized": False,
"host_isolation_authorized": False,
"package_upgrade_authorized": False,
"reboot_authorized": False,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"wazuh_rule_change_authorized": False,
"argocd_sync_authorized": False,
"kubectl_action_authorized": False,
"workflow_modification_authorized": False,
"runner_change_authorized": False,
"repo_secret_change_authorized": False,
"secret_rotation_authorized": False,
"secret_value_collection_allowed": False,
"raw_payload_storage_allowed": False,
"active_scan_authorized": False,
"backup_run_authorized": False,
"restore_run_authorized": False,
"production_write_authorized": False,
"action_buttons_allowed": False,
"frontend_internal_transcript_display_allowed": False,
"not_authorization": True,
}
def git_short_sha(root: Path) -> str:
try:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
cwd=root,
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
except Exception:
return "unknown"
def build_candidate(source: dict[str, Any]) -> dict[str, Any]:
return {
**source,
"status": "waiting_owner_prevention_packet",
"host_aliases": HOST_ALIASES,
"sensor_aliases": SENSOR_ALIASES,
"requires_owner_approval": True,
"requires_maintenance_window": True,
"requires_break_glass_record": source["control_tier"] == "C0",
"requires_cross_project_sync": True,
"requires_rollback_plan": True,
"requires_validation_plan": True,
"owner_role": "pending_owner_response",
"owner_team": "pending_owner_response",
"maintenance_window": "pending_owner_response",
"rollback_owner": "pending_owner_response",
"followup_owner": "pending_owner_response",
"redacted_evidence_refs": [],
"wazuh_event_refs": [],
"host_forensic_refs": [],
"config_diff_refs": [],
"validation_metric_refs": [],
"postcheck_ref": None,
"cross_project_sync_ref": None,
"reviewer_outcome": "waiting_prevention_reviewer_review",
"runtime_gate": False,
"action_buttons_allowed": False,
"not_authorization": True,
"blocked_actions": BLOCKED_ACTIONS,
"required_owner_fields": REQUIRED_OWNER_FIELDS,
"reviewer_checks": [item["check_id"] for item in REVIEWER_CHECKS],
"outcome_lanes": [item["lane_id"] for item in OUTCOME_LANES],
}
def count_if(candidates: list[dict[str, Any]], key: str) -> int:
return sum(1 for item in candidates if item[key])
def build_report(root: Path, generated_at: str | None) -> dict[str, Any]:
report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
candidates = [build_candidate(item) for item in CONTROL_CANDIDATES]
return {
"schema_version": "external_host_intrusion_prevention_control_v1",
"generated_at": report_time,
"git_commit": git_short_sha(root),
"status": "external_host_intrusion_prevention_control_ready_no_runtime_action",
"mode": "control_matrix_only_no_host_write_no_firewall_change_no_active_response",
"source_context": {
"host_aliases": HOST_ALIASES,
"sensor_aliases": SENSOR_ALIASES,
"raw_ip_or_account_namespace_published_to_frontend": False,
"work_session_transcript_published_to_frontend": False,
"external_agent_claim_accepted_without_evidence": False,
"runtime_prevention_enabled": False,
},
"summary": {
"prevention_domain_count": len(PREVENTION_DOMAINS),
"host_alias_count": len(HOST_ALIASES),
"sensor_alias_count": len(SENSOR_ALIASES),
"control_candidate_count": len(candidates),
"c0_control_candidate_count": sum(1 for item in candidates if item["control_tier"] == "C0"),
"c1_control_candidate_count": sum(1 for item in candidates if item["control_tier"] == "C1"),
"p0_control_candidate_count": sum(1 for item in candidates if item["priority"] == "P0"),
"urgent_prevention_candidate_count": len(candidates),
"maintenance_window_required_candidate_count": count_if(candidates, "requires_maintenance_window"),
"break_glass_required_candidate_count": count_if(candidates, "requires_break_glass_record"),
"owner_approval_required_candidate_count": count_if(candidates, "requires_owner_approval"),
"cross_project_sync_required_candidate_count": count_if(candidates, "requires_cross_project_sync"),
"rollback_required_candidate_count": count_if(candidates, "requires_rollback_plan"),
"validation_required_candidate_count": count_if(candidates, "requires_validation_plan"),
"wazuh_event_required_candidate_count": count_if(candidates, "requires_wazuh_event_refs"),
"host_forensics_required_candidate_count": count_if(candidates, "requires_host_forensics_refs"),
"config_diff_required_candidate_count": count_if(candidates, "requires_config_diff_refs"),
"backup_restore_required_candidate_count": sum(1 for item in candidates if item["prevention_domain"] == "backup_restore_resilience"),
"no_false_green_required_candidate_count": len(candidates),
"required_owner_field_count": len(REQUIRED_OWNER_FIELDS),
"reviewer_check_count": len(REVIEWER_CHECKS),
"outcome_lane_count": len(OUTCOME_LANES),
"blocked_action_count": len(BLOCKED_ACTIONS),
"coverage_percent_after_prevention_control": 74,
"docker_compose_systemd_host_config_coverage_percent_after_prevention_control": 68,
"ssh_firewall_network_access_coverage_percent_after_prevention_control": 70,
"monitoring_alerting_observability_coverage_percent_after_prevention_control": 74,
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"evidence_ref_received_count": 0,
"evidence_ref_accepted_count": 0,
"prevention_control_accepted_count": 0,
"containment_decision_accepted_count": 0,
"maintenance_window_accepted_count": 0,
"rollback_plan_accepted_count": 0,
"postcheck_accepted_count": 0,
"wazuh_active_response_enabled_count": 0,
"host_write_authorized_count": 0,
"ssh_write_authorized_count": 0,
"firewall_change_authorized_count": 0,
"nginx_reload_authorized_count": 0,
"docker_restart_authorized_count": 0,
"systemctl_restart_authorized_count": 0,
"argocd_sync_authorized_count": 0,
"workflow_modification_authorized_count": 0,
"runner_change_authorized_count": 0,
"repo_secret_change_authorized_count": 0,
"secret_value_collection_allowed_count": 0,
"active_scan_authorized_count": 0,
"package_upgrade_authorized_count": 0,
"production_write_authorized_count": 0,
"runtime_gate_count": 0,
"action_button_count": 0,
},
"execution_boundaries": EXECUTION_BOUNDARIES,
"prevention_domains": PREVENTION_DOMAINS,
"control_candidates": candidates,
"required_owner_fields": REQUIRED_OWNER_FIELDS,
"reviewer_checks": REVIEWER_CHECKS,
"outcome_lanes": OUTCOME_LANES,
"blocked_actions": BLOCKED_ACTIONS,
"operator_interpretation": [
"這份矩陣把外部入侵防堵前移成 P0 控制候選,但不是 host write 或 firewall change 授權。",
"IwoooS 先拒收假綠燈route 200、dashboard up、CD success、UI 可見、agent active 或外部 Agent 宣稱都不能單獨當成已防堵。",
"真正能進 runtime 的防堵需 owner role / team、decision reason、維護窗口、rollback owner、validation metrics、postcheck 與跨專案同步。",
"前台只能顯示 alias、候選數、缺口與 0/false 邊界,不顯示內網 IP、帳號 namespace、raw log、secret 或工作視窗內容。",
],
}
def main() -> int:
parser = argparse.ArgumentParser(description="IwoooS 外部入侵主機防堵控制矩陣")
parser.add_argument("--root", default=".", help="repo root")
parser.add_argument("--output", help="寫出 JSON 報告")
parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用")
args = parser.parse_args()
root = Path(args.root).resolve()
report = build_report(root, args.generated_at)
payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)
if args.output:
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(payload + "\n", encoding="utf-8")
else:
print(payload)
summary = report["summary"]
print(
"EXTERNAL_HOST_INTRUSION_PREVENTION_CONTROL_OK "
f"domains={summary['prevention_domain_count']} "
f"candidates={summary['control_candidate_count']} "
f"hosts={summary['host_alias_count']} "
f"checks={summary['reviewer_check_count']} "
f"blocked={summary['blocked_action_count']} "
f"runtime_gate={summary['runtime_gate_count']}",
file=sys.stderr,
)
return 0
if __name__ == "__main__":
sys.exit(main())