#!/usr/bin/env python3
"""
IwoooS Wazuh / 主機入侵 read-only 接入計畫產生器。

本工具只產生 repo 內 snapshot，將已建置的 Wazuh 平台、host-110 /
host-188 入侵跡象、外部 Agent 高風險操作宣稱與後續 containment /
recovery proof 轉成 IwoooS 可收件、可拒收、可補件的脫敏欄位。

它不連 Wazuh API、不讀主機、不 SSH、不啟動 Wazuh active response、
不隔離主機、不封鎖端口、不重啟服務、不收 secret / private key /
runner token / webhook secret，也不把外部 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))

READBACK_FIELDS = [
    "readback_candidate_id",
    "scope_alias",
    "label",
    "control_tier",
    "priority",
    "source_kind",
    "wazuh_platform_reported_ready",
    "requires_owner_evidence",
    "requires_wazuh_event_refs",
    "requires_host_forensics_refs",
    "requires_cross_project_sync",
    "wazuh_manager_health_ref",
    "wazuh_agent_status_ref",
    "wazuh_alert_rule_refs",
    "wazuh_event_refs",
    "host_auth_log_refs",
    "process_tree_refs",
    "network_connection_refs",
    "file_integrity_refs",
    "package_inventory_refs",
    "persistence_mechanism_refs",
    "runner_or_gateway_config_diff_refs",
    "actor_attribution_ref",
    "incident_time_window_ref",
    "affected_scope",
    "impact_summary_ref",
    "containment_decision",
    "containment_reason_ref",
    "recovery_proof_refs",
    "postcheck_readback_ref",
    "recurrence_guard_ref",
    "cross_project_sync_ref",
    "rollback_owner",
    "followup_owner",
    "reviewer_outcome",
    "not_authorization",
]

REQUIRED_READBACK_FIELDS = [
    "wazuh_manager_health_ref",
    "wazuh_agent_status_ref",
    "wazuh_event_refs",
    "host_auth_log_refs",
    "process_tree_refs",
    "network_connection_refs",
    "file_integrity_refs",
    "package_inventory_refs",
    "persistence_mechanism_refs",
    "actor_attribution_ref",
    "incident_time_window_ref",
    "affected_scope",
    "impact_summary_ref",
    "containment_decision",
    "containment_reason_ref",
    "recovery_proof_refs",
    "postcheck_readback_ref",
    "recurrence_guard_ref",
    "cross_project_sync_ref",
    "rollback_owner",
    "followup_owner",
    "redacted_evidence_refs",
    "no_secret_value_attestation",
    "no_raw_log_attestation",
    "no_raw_wazuh_payload_attestation",
    "no_private_key_or_runner_token_attestation",
    "no_false_green_attestation",
    "external_agent_claim_not_accepted_attestation",
    "active_response_not_enabled_attestation",
    "independent_reviewer_attestation",
]

REVIEWER_CHECKS = [
    {"check_id": "wazuh_platform_ready_is_metadata_only", "instruction": "Wazuh 已建置只能當作平台存在訊號，不能直接視為 IwoooS 已完成事件接入。"},
    {"check_id": "manager_health_ref_present", "instruction": "必須有脫敏 manager / indexer / dashboard / API health ref。"},
    {"check_id": "agent_status_ref_present", "instruction": "host-110、host-188 或其他受影響 host 必須有 agent 狀態 ref。"},
    {"check_id": "event_refs_present", "instruction": "必須有 Wazuh event / alert refs，不能只寫已發現或已阻擋。"},
    {"check_id": "host_forensics_refs_present", "instruction": "需要 auth log、process tree、network connection、file integrity、package inventory 或 persistence refs。"},
    {"check_id": "actor_attribution_present", "instruction": "需要 actor role / team / tool attribution；外部 Agent 操作不能匿名通過。"},
    {"check_id": "time_window_present", "instruction": "需要 incident / operation / recovery time window，供事件與變更對齊。"},
    {"check_id": "affected_scope_present", "instruction": "需列 host alias、服務 alias、產品 alias 與受影響配置類別，不顯示原始內部命名空間。"},
    {"check_id": "containment_decision_present", "instruction": "需有 containment decision 與理由；未決定時維持 waiting_containment_decision。"},
    {"check_id": "external_agent_claim_not_trusted", "instruction": "外部 Agent 宣稱已清除、已修復、已阻擋或已 Zero-Trust 不能直接 accepted。"},
    {"check_id": "runner_gateway_change_reviewed", "instruction": "runner label、runner config、gateway、firewall、workflow 或 secret 相關事件需串到既有高價值配置 gate。"},
    {"check_id": "secret_material_absent", "instruction": "不得收 private key、password、runner token、webhook secret、cookie、env dump、hash 或 partial token。"},
    {"check_id": "raw_payload_absent", "instruction": "不得保存 raw Wazuh payload、raw syslog、raw journal、完整截圖或未脫敏 command output。"},
    {"check_id": "hardcoded_wazuh_credential_rejected", "instruction": "硬編碼 Wazuh URL、使用者、密碼或關閉 TLS 驗證的程式碼必須拒收或隔離。"},
    {"check_id": "readonly_api_disabled_by_default", "instruction": "IwoooS Wazuh API code path 必須預設關閉，沒有 owner gate 與 server-side env 時不得查詢。"},
    {"check_id": "host_write_not_opened", "instruction": "不得因 Wazuh 告警就自動 SSH、kill process、封鎖端口、restart 或修改主機。"},
    {"check_id": "active_response_not_opened", "instruction": "Wazuh active response、firewall drop、isolation 或 remediation 必須另有 break-glass 與人工批准。"},
    {"check_id": "cross_project_sync_present", "instruction": "若事件影響 AWOOOI、AwoooP、IwoooS、任務媒合、股票研究、公開網站或監控，需有同步 ref。"},
    {"check_id": "recovery_proof_present", "instruction": "需要 recovery proof refs；服務 route 200 或 dashboard 可見不能單獨當恢復證據。"},
    {"check_id": "postcheck_independent", "instruction": "postcheck 必須獨立於操作人與外部 Agent。"},
    {"check_id": "recurrence_guard_present", "instruction": "需要防再發 guard，例如 credential hygiene、runner config control、firewall diff control、Wazuh rule coverage。"},
    {"check_id": "owner_response_not_auto_accepted", "instruction": "owner response received / accepted count 只能由 reviewer record 推進。"},
    {"check_id": "no_false_green", "instruction": "Wazuh online、agent active、route 200、CD success、UI 可見都不能單獨當事件驗收。"},
    {"check_id": "runtime_stays_zero", "instruction": "readback plan 不得打開 active response、host write、workflow change、secret rotation 或 action button。"},
]

OUTCOME_LANES = [
    {"lane_id": "waiting_wazuh_owner_evidence", "meaning": "Wazuh 已建置但尚未收到 IwoooS 可接受的脫敏事件 refs。"},
    {"lane_id": "request_manager_health_supplement", "meaning": "缺 Wazuh manager / indexer / dashboard / API health ref 時要求補件。"},
    {"lane_id": "request_agent_status_supplement", "meaning": "缺 host agent 狀態、agent id 對照或 last seen ref 時要求補件。"},
    {"lane_id": "request_host_forensics_supplement", "meaning": "缺 auth / process / network / FIM / package / persistence refs 時要求補件。"},
    {"lane_id": "request_actor_or_time_supplement", "meaning": "缺 actor attribution、工具來源或事件時間窗時要求補件。"},
    {"lane_id": "request_containment_decision", "meaning": "缺 containment decision、理由、owner 或 rollback owner 時要求補件。"},
    {"lane_id": "quarantine_secret_or_raw_payload", "meaning": "收到 secret、private key、runner token、raw payload、raw log 或未脫敏截圖時隔離。"},
    {"lane_id": "reject_external_agent_claim_only", "meaning": "只有外部 Agent 口頭宣稱已修復、已阻擋或已清除時拒收。"},
    {"lane_id": "route_to_high_value_config_gate", "meaning": "涉及 runner、workflow、secret、gateway、firewall、systemd、Docker 或 K8s 時串到高價值配置 gate。"},
    {"lane_id": "ready_for_intrusion_reviewer_review", "meaning": "metadata 合格後只能進 reviewer review，不自動執行。"},
    {"lane_id": "recurrence_guard_backfill_required", "meaning": "需補 Wazuh rule coverage、runner/gateway/secret guard、credential hygiene 或 change freeze。"},
    {"lane_id": "waiting_runtime_gate", "meaning": "即使事件 refs accepted，active response 與 host containment 仍需獨立人工批准。"},
]

BLOCKED_ACTIONS = [
    "wazuh_active_response_enable",
    "wazuh_agent_install",
    "wazuh_agent_restart",
    "wazuh_manager_restart",
    "wazuh_rule_change",
    "wazuh_decoder_change",
    "wazuh_api_live_query_without_owner_gate",
    "hardcode_wazuh_url",
    "hardcode_wazuh_username",
    "hardcode_wazuh_password",
    "disable_tls_verification",
    "store_raw_wazuh_payload",
    "store_raw_syslog",
    "store_raw_journal",
    "store_raw_command_output",
    "store_unredacted_screenshot",
    "collect_password",
    "collect_private_key",
    "collect_runner_token",
    "collect_webhook_secret",
    "collect_cookie_or_session",
    "collect_env_dump",
    "ssh_read",
    "ssh_write",
    "sudo_action",
    "host_file_write",
    "kill_process",
    "quarantine_host",
    "firewall_drop",
    "port_close",
    "port_open",
    "docker_restart",
    "docker_kill",
    "systemctl_restart",
    "nginx_reload",
    "workflow_modification",
    "runner_label_change",
    "runner_config_change",
    "repo_secret_change",
    "secret_rotation",
    "active_scan",
    "production_write",
    "mark_malware_removed_without_evidence",
    "mark_rce_fixed_without_evidence",
    "accept_external_agent_claim_without_wazuh_refs",
    "accept_route_200_as_recovery",
    "accept_agent_active_as_incident_closed",
    "open_runtime_gate",
    "add_action_button",
]

READBACK_CANDIDATES = [
    {
        "readback_candidate_id": "wazuh_intrusion_readback:wazuh_control_plane",
        "scope_alias": "wazuh-control-plane",
        "label": "Wazuh manager / indexer / dashboard / API",
        "control_tier": "C0",
        "priority": "P0",
        "source_kind": "wazuh_platform",
        "requires_wazuh_event_refs": False,
        "requires_host_forensics_refs": False,
        "requires_cross_project_sync": True,
    },
    {
        "readback_candidate_id": "wazuh_intrusion_readback:host110_intrusion_signal",
        "scope_alias": "host-110",
        "label": "host-110 入侵 / gateway / runner / service 異常訊號",
        "control_tier": "C0",
        "priority": "P0",
        "source_kind": "host_intrusion",
        "requires_wazuh_event_refs": True,
        "requires_host_forensics_refs": True,
        "requires_cross_project_sync": True,
    },
    {
        "readback_candidate_id": "wazuh_intrusion_readback:host188_intrusion_signal",
        "scope_alias": "host-188",
        "label": "host-188 入侵 / 惡意程式 / RCE 宣稱回讀",
        "control_tier": "C0",
        "priority": "P0",
        "source_kind": "host_intrusion",
        "requires_wazuh_event_refs": True,
        "requires_host_forensics_refs": True,
        "requires_cross_project_sync": True,
    },
    {
        "readback_candidate_id": "wazuh_intrusion_readback:external_agent_live_change_claim",
        "scope_alias": "external-agent-handoff",
        "label": "外部 Agent live 變更與修復宣稱驗證",
        "control_tier": "C0",
        "priority": "P0",
        "source_kind": "external_agent_claim",
        "requires_wazuh_event_refs": True,
        "requires_host_forensics_refs": True,
        "requires_cross_project_sync": True,
    },
    {
        "readback_candidate_id": "wazuh_intrusion_readback:runner_secret_gateway_control",
        "scope_alias": "runner-secret-gateway",
        "label": "Runner / secret / gateway 高價值配置連動",
        "control_tier": "C0",
        "priority": "P0",
        "source_kind": "high_value_config_link",
        "requires_wazuh_event_refs": True,
        "requires_host_forensics_refs": True,
        "requires_cross_project_sync": True,
    },
    {
        "readback_candidate_id": "wazuh_intrusion_readback:containment_recovery_postcheck",
        "scope_alias": "containment-recovery",
        "label": "Containment / recovery proof / postcheck",
        "control_tier": "C0",
        "priority": "P0",
        "source_kind": "incident_response",
        "requires_wazuh_event_refs": True,
        "requires_host_forensics_refs": True,
        "requires_cross_project_sync": 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_wazuh_owner_evidence",
        "wazuh_platform_reported_ready": True,
        "requires_owner_evidence": True,
        "wazuh_manager_health_ref": None,
        "wazuh_agent_status_ref": None,
        "wazuh_alert_rule_refs": [],
        "wazuh_event_refs": [],
        "host_auth_log_refs": [],
        "process_tree_refs": [],
        "network_connection_refs": [],
        "file_integrity_refs": [],
        "package_inventory_refs": [],
        "persistence_mechanism_refs": [],
        "runner_or_gateway_config_diff_refs": [],
        "actor_attribution_ref": None,
        "incident_time_window_ref": None,
        "affected_scope": "pending_redacted_scope",
        "impact_summary_ref": None,
        "containment_decision": "waiting_containment_decision",
        "containment_reason_ref": None,
        "recovery_proof_refs": [],
        "postcheck_readback_ref": None,
        "recurrence_guard_ref": None,
        "cross_project_sync_ref": None,
        "rollback_owner": "pending_owner_response",
        "followup_owner": "pending_owner_response",
        "reviewer_outcome": "waiting_wazuh_intrusion_review",
        "not_approval": True,
        "runtime_gate": False,
        "action_buttons_allowed": False,
        "active_response_authorized": False,
        "host_write_authorized": False,
        "secret_value_collection_allowed": False,
        "blocked_actions": BLOCKED_ACTIONS,
        "readback_fields": READBACK_FIELDS,
        "required_readback_fields": REQUIRED_READBACK_FIELDS,
        "reviewer_checks": [item["check_id"] for item in REVIEWER_CHECKS],
        "outcome_lanes": [item["lane_id"] for item in OUTCOME_LANES],
    }


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 READBACK_CANDIDATES]
    return {
        "schema_version": "wazuh_iwooos_intrusion_readback_plan_v1",
        "generated_at": report_time,
        "git_commit": git_short_sha(root),
        "status": "wazuh_intrusion_readback_plan_ready_no_runtime_action",
        "mode": "metadata_only_no_wazuh_api_no_host_read_no_active_response",
        "source_context": {
            "wazuh_platform_reported_ready": True,
            "iwooos_runtime_ingestion_accepted": False,
            "affected_host_aliases": ["host-110", "host-188"],
            "external_agent_transcript_published_to_frontend": False,
            "raw_secret_or_private_key_collection_allowed": False,
        },
        "summary": {
            "wazuh_platform_reported_count": 1,
            "readback_candidate_count": len(candidates),
            "c0_readback_candidate_count": sum(1 for item in candidates if item["control_tier"] == "C0"),
            "affected_host_alias_count": 2,
            "wazuh_event_required_candidate_count": sum(1 for item in candidates if item["requires_wazuh_event_refs"]),
            "host_forensics_required_candidate_count": sum(1 for item in candidates if item["requires_host_forensics_refs"]),
            "cross_project_sync_required_candidate_count": sum(1 for item in candidates if item["requires_cross_project_sync"]),
            "readback_field_count": len(READBACK_FIELDS),
            "required_readback_field_count": len(REQUIRED_READBACK_FIELDS),
            "reviewer_check_count": len(REVIEWER_CHECKS),
            "outcome_lane_count": len(OUTCOME_LANES),
            "blocked_action_count": len(BLOCKED_ACTIONS),
            "wazuh_manager_health_ref_received_count": 0,
            "wazuh_agent_status_ref_received_count": 0,
            "wazuh_event_ref_received_count": 0,
            "host_forensics_ref_received_count": 0,
            "containment_decision_received_count": 0,
            "recovery_proof_received_count": 0,
            "postcheck_readback_received_count": 0,
            "recurrence_guard_received_count": 0,
            "cross_project_sync_received_count": 0,
            "owner_response_received_count": 0,
            "owner_response_accepted_count": 0,
            "wazuh_event_accepted_count": 0,
            "host_forensics_accepted_count": 0,
            "containment_decision_accepted_count": 0,
            "recovery_proof_accepted_count": 0,
            "postcheck_readback_accepted_count": 0,
            "recurrence_guard_accepted_count": 0,
            "readonly_api_code_path_present_count": 1,
            "readonly_api_enabled_count": 0,
            "active_response_authorized_count": 0,
            "host_write_authorized_count": 0,
            "secret_value_collection_allowed_count": 0,
            "runtime_gate_count": 0,
            "action_button_count": 0,
            "coverage_percent_after_readback_plan": 72,
        },
        "execution_boundaries": {
            "runtime_execution_authorized": False,
            "wazuh_api_live_query_authorized": False,
            "wazuh_active_response_authorized": False,
            "wazuh_rule_change_authorized": False,
            "wazuh_agent_install_authorized": False,
            "host_read_authorized": False,
            "host_write_authorized": False,
            "ssh_read_authorized": False,
            "ssh_write_authorized": False,
            "sudo_action_authorized": False,
            "firewall_change_authorized": False,
            "process_kill_authorized": False,
            "host_isolation_authorized": False,
            "runner_change_authorized": False,
            "workflow_modification_authorized": False,
            "repo_secret_change_authorized": False,
            "secret_value_collection_allowed": False,
            "raw_wazuh_payload_storage_allowed": False,
            "raw_log_storage_allowed": False,
            "private_key_collection_allowed": False,
            "active_scan_authorized": False,
            "production_write_authorized": False,
            "frontend_raw_transcript_display_allowed": False,
            "action_buttons_allowed": False,
            "not_authorization": True,
        },
        "readback_candidates": candidates,
        "reviewer_checks": REVIEWER_CHECKS,
        "outcome_lanes": OUTCOME_LANES,
        "blocked_actions": BLOCKED_ACTIONS,
        "operator_interpretation": [
            "Wazuh 已建置是偵測平台存在訊號，不代表 IwoooS 已驗收 110 / 188 入侵事件。",
            "IwoooS 只能接收脫敏 refs、hash、ticket、artifact pointer 與 owner response，不收 secret、private key、runner token 或 raw payload。",
            "外部 Agent 對木馬、RCE、Zero-Trust、清除或封鎖的宣稱必須用 Wazuh event refs 與 host forensic refs 交叉驗證。",
            "active response、host containment、firewall drop、restart、runner change、secret rotation 與 production write 仍全部需要獨立人工批准。",
        ],
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="IwoooS Wazuh / 主機入侵 read-only 接入計畫產生器")
    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(
        "WAZUH_IWOOOS_INTRUSION_READBACK_PLAN_OK "
        f"candidates={summary['readback_candidate_count']} "
        f"hosts={summary['affected_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())
