feat(iwooos): add wazuh controlled apply preflight
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Successful in 1m16s
CD Pipeline / tests (push) Failing after 35s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Successful in 17s

This commit is contained in:
Your Name
2026-06-28 10:10:39 +08:00
parent 5d5832e558
commit b010afdbf6
9 changed files with 1626 additions and 90 deletions

View File

@@ -22,6 +22,7 @@ _SNAPSHOT_FILES = {
"wazuh_runtime": "wazuh-agent-visibility-runtime-gate.snapshot.json",
"wazuh_live_metadata_gate": "wazuh-readonly-live-metadata-env-gate.snapshot.json",
"wazuh_owner_evidence_preflight": "wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
"wazuh_runtime_apply_preflight": "wazuh-runtime-controlled-apply-preflight.snapshot.json",
"kali_status": "kali-integration-status.snapshot.json",
"soc_control": "soc-siem-kali-wazuh-integration-control.snapshot.json",
"alert_readability": "telegram-alert-readability-guard.snapshot.json",
@@ -35,6 +36,7 @@ _EXPECTED_SCHEMAS = {
"wazuh_runtime": "wazuh_agent_visibility_runtime_gate_v1",
"wazuh_live_metadata_gate": "iwooos_wazuh_readonly_live_metadata_env_gate_v1",
"wazuh_owner_evidence_preflight": "wazuh_agent_visibility_owner_evidence_preflight_v1",
"wazuh_runtime_apply_preflight": "wazuh_runtime_controlled_apply_preflight_v1",
"kali_status": "kali_integration_status_v1",
"soc_control": "soc_siem_kali_wazuh_integration_control_v1",
"alert_readability": "telegram_alert_readability_guard_v1",
@@ -69,13 +71,21 @@ def load_latest_iwooos_runtime_security_readback(
) -> dict[str, Any]:
"""Load and normalize the current IwoooS runtime security readback."""
directory = security_dir or _DEFAULT_SECURITY_DIR
snapshots = {key: _load_snapshot(directory, key, filename) for key, filename in _SNAPSHOT_FILES.items()}
snapshots = {
key: _load_snapshot(directory, key, filename)
for key, filename in _SNAPSHOT_FILES.items()
}
_require_runtime_boundaries(snapshots)
owner_gap_summary = _summary(snapshots["owner_gap"])
wazuh_summary = _summary(snapshots["wazuh_coverage"])
live_metadata_gate_summary = _summary(snapshots["wazuh_live_metadata_gate"])
owner_evidence_preflight_summary = _summary(snapshots["wazuh_owner_evidence_preflight"])
owner_evidence_preflight_summary = _summary(
snapshots["wazuh_owner_evidence_preflight"]
)
runtime_apply_preflight_summary = _summary(
snapshots["wazuh_runtime_apply_preflight"]
)
soc_summary = _summary(snapshots["soc_control"])
alert_summary = _summary(snapshots["alert_readability"])
dispatch_summary = _summary(snapshots["owner_dispatch"])
@@ -98,40 +108,60 @@ def load_latest_iwooos_runtime_security_readback(
"source_refs": source_refs,
"summary": {
"source_snapshot_count": len(source_refs),
"p0_lane_count": 9,
"p0_lane_count": 10,
"control_plane_visibility_percent": _average_percent(
soc_summary.get("coverage_percent_after_soc_integration_control"),
intrusion_summary.get("coverage_percent_after_prevention_control"),
_alert_contract_percent(alert_summary),
),
"actual_runtime_acceptance_percent": 0,
"owner_response_received_count": _int(owner_gap_summary.get("owner_response_received_count")),
"owner_response_accepted_count": _int(owner_gap_summary.get("owner_response_accepted_count")),
"owner_response_received_count": _int(
owner_gap_summary.get("owner_response_received_count")
),
"owner_response_accepted_count": _int(
owner_gap_summary.get("owner_response_accepted_count")
),
"redacted_evidence_refs_received_count": 0,
"request_sent_count": _int(dispatch_summary.get("request_sent_count")),
"wazuh_expected_host_scope_count": _int(wazuh_summary.get("expected_host_scope_count")),
"wazuh_manager_registry_accepted_count": _int(wazuh_summary.get("manager_registry_accepted_count")),
"wazuh_transport_observed_count": _int(wazuh_summary.get("manager_transport_established_connection_count")),
"wazuh_expected_host_scope_count": _int(
wazuh_summary.get("expected_host_scope_count")
),
"wazuh_manager_registry_accepted_count": _int(
wazuh_summary.get("manager_registry_accepted_count")
),
"wazuh_transport_observed_count": _int(
wazuh_summary.get("manager_transport_established_connection_count")
),
"wazuh_dashboard_api_degraded_observed_count": _int(
wazuh_summary.get("dashboard_api_degraded_observed_count")
),
"wazuh_live_route_http_status": live_wazuh["http_status"],
"wazuh_live_route_degraded_count": live_wazuh["degraded_count"],
"wazuh_live_readonly_api_enabled_count": live_wazuh["readonly_api_enabled_count"],
"wazuh_live_readonly_api_enabled_count": live_wazuh[
"readonly_api_enabled_count"
],
"wazuh_live_agent_total": live_wazuh["agent_total"],
"wazuh_live_agent_active": live_wazuh["agent_active"],
"wazuh_live_registry_empty_count": live_wazuh["agent_registry_empty_count"],
"wazuh_live_below_expected_count": live_wazuh["agent_below_expected_minimum_count"],
"wazuh_live_metadata_available_count": live_wazuh["metadata_available_count"],
"wazuh_live_below_expected_count": live_wazuh[
"agent_below_expected_minimum_count"
],
"wazuh_live_metadata_available_count": live_wazuh[
"metadata_available_count"
],
"wazuh_live_status": live_wazuh["status"],
"wazuh_live_metadata_gate_owner_accepted_count": _int(
live_metadata_gate_summary.get("live_metadata_owner_response_accepted_count")
live_metadata_gate_summary.get(
"live_metadata_owner_response_accepted_count"
)
),
"wazuh_live_metadata_gate_secret_source_accepted_count": _int(
live_metadata_gate_summary.get("secret_source_metadata_accepted_count")
),
"wazuh_live_metadata_gate_manager_health_accepted_count": _int(
live_metadata_gate_summary.get("wazuh_manager_health_ref_accepted_count")
live_metadata_gate_summary.get(
"wazuh_manager_health_ref_accepted_count"
)
),
"wazuh_live_metadata_gate_readonly_scope_accepted_count": _int(
live_metadata_gate_summary.get("readonly_account_scope_accepted_count")
@@ -172,12 +202,56 @@ def load_latest_iwooos_runtime_security_readback(
"wazuh_owner_evidence_runtime_gate_count": _int(
owner_evidence_preflight_summary.get("runtime_gate_count")
),
"kali_active_scan_authorized_count": _int(soc_summary.get("kali_active_scan_authorized_count")),
"kali_execute_authorized_count": _int(soc_summary.get("kali_execute_authorized_count")),
"kali_finding_envelope_accepted_count": _int(soc_summary.get("kali_finding_envelope_accepted_count")),
"alert_formatter_contract_marker_count": _int(alert_summary.get("source_formatter_marker_count")),
"alert_receipt_runtime_send_count": _int(alert_summary.get("telegram_send_authorized_count")),
"intrusion_prevention_candidate_count": _int(intrusion_summary.get("urgent_prevention_candidate_count")),
"wazuh_runtime_apply_preflight_ready_count": _int(
runtime_apply_preflight_summary.get(
"controlled_apply_preflight_ready_count"
)
),
"wazuh_runtime_apply_target_selector_count": _int(
runtime_apply_preflight_summary.get("target_selector_count")
),
"wazuh_runtime_apply_source_diff_count": _int(
runtime_apply_preflight_summary.get("source_of_truth_diff_count")
),
"wazuh_runtime_apply_check_mode_plan_count": _int(
runtime_apply_preflight_summary.get("check_mode_plan_count")
),
"wazuh_runtime_apply_dry_run_required_count": _int(
runtime_apply_preflight_summary.get("dry_run_required_count")
),
"wazuh_runtime_apply_rollback_plan_count": _int(
runtime_apply_preflight_summary.get("rollback_plan_count")
),
"wazuh_runtime_apply_post_apply_verifier_count": _int(
runtime_apply_preflight_summary.get("post_apply_verifier_count")
),
"wazuh_runtime_apply_km_writeback_count": _int(
runtime_apply_preflight_summary.get("km_playbook_writeback_count")
),
"wazuh_runtime_apply_owner_review_ready_count": _int(
runtime_apply_preflight_summary.get("owner_review_ready_count")
),
"wazuh_runtime_apply_runtime_gate_count": _int(
runtime_apply_preflight_summary.get("runtime_gate_count")
),
"kali_active_scan_authorized_count": _int(
soc_summary.get("kali_active_scan_authorized_count")
),
"kali_execute_authorized_count": _int(
soc_summary.get("kali_execute_authorized_count")
),
"kali_finding_envelope_accepted_count": _int(
soc_summary.get("kali_finding_envelope_accepted_count")
),
"alert_formatter_contract_marker_count": _int(
alert_summary.get("source_formatter_marker_count")
),
"alert_receipt_runtime_send_count": _int(
alert_summary.get("telegram_send_authorized_count")
),
"intrusion_prevention_candidate_count": _int(
intrusion_summary.get("urgent_prevention_candidate_count")
),
"runtime_gate_count": runtime_gate_count,
},
"lanes": [
@@ -204,8 +278,12 @@ def load_latest_iwooos_runtime_security_readback(
"管理器清單交叉驗收已讀回runtime gate 仍關閉",
{
"expected_hosts": wazuh_summary.get("expected_host_scope_count", 0),
"transport_observed": wazuh_summary.get("manager_transport_established_connection_count", 0),
"registry_accepted": wazuh_summary.get("manager_registry_accepted_count", 0),
"transport_observed": wazuh_summary.get(
"manager_transport_established_connection_count", 0
),
"registry_accepted": wazuh_summary.get(
"manager_registry_accepted_count", 0
),
},
["docs/security/wazuh-managed-host-coverage-gate.snapshot.json"],
),
@@ -226,13 +304,19 @@ def load_latest_iwooos_runtime_security_readback(
),
_lane(
"wazuh_live_metadata_gate",
snapshots["wazuh_live_metadata_gate"].get("status", "blocked_waiting_live_metadata_owner_response"),
snapshots["wazuh_live_metadata_gate"].get(
"status", "blocked_waiting_live_metadata_owner_response"
),
0,
"locked",
"補齊負責人回覆、機密中繼資料、管理節點健康、唯讀範圍與啟用後讀回",
{
"route_readback": live_metadata_gate_summary.get("production_route_readback_passed_count", 0),
"owner_accepted": live_metadata_gate_summary.get("live_metadata_owner_response_accepted_count", 0),
"route_readback": live_metadata_gate_summary.get(
"production_route_readback_passed_count", 0
),
"owner_accepted": live_metadata_gate_summary.get(
"live_metadata_owner_response_accepted_count", 0
),
"secret_metadata_accepted": live_metadata_gate_summary.get(
"secret_source_metadata_accepted_count",
0,
@@ -245,12 +329,16 @@ def load_latest_iwooos_runtime_security_readback(
"readonly_account_scope_accepted_count",
0,
),
"post_enable_readback": live_metadata_gate_summary.get("post_enable_readback_passed_count", 0),
"post_enable_readback": live_metadata_gate_summary.get(
"post_enable_readback_passed_count", 0
),
"live_query_authorized": live_metadata_gate_summary.get(
"wazuh_api_live_query_authorized_count",
0,
),
"runtime_gate": live_metadata_gate_summary.get("runtime_gate_count", 0),
"runtime_gate": live_metadata_gate_summary.get(
"runtime_gate_count", 0
),
},
["docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json"],
),
@@ -264,19 +352,86 @@ def load_latest_iwooos_runtime_security_readback(
"locked",
"補齊 manager registry 脫敏封包、逐主機矩陣、Dashboard API 狀態與負責人決策",
{
"required_fields": owner_evidence_preflight_summary.get("required_field_count", 0),
"reviewer_checks": owner_evidence_preflight_summary.get("reviewer_check_count", 0),
"outcome_lanes": owner_evidence_preflight_summary.get("outcome_lane_count", 0),
"forbidden_payloads": owner_evidence_preflight_summary.get("forbidden_payload_count", 0),
"owner_received": owner_evidence_preflight_summary.get("owner_evidence_received_count", 0),
"owner_accepted": owner_evidence_preflight_summary.get("owner_evidence_accepted_count", 0),
"required_fields": owner_evidence_preflight_summary.get(
"required_field_count", 0
),
"reviewer_checks": owner_evidence_preflight_summary.get(
"reviewer_check_count", 0
),
"outcome_lanes": owner_evidence_preflight_summary.get(
"outcome_lane_count", 0
),
"forbidden_payloads": owner_evidence_preflight_summary.get(
"forbidden_payload_count", 0
),
"owner_received": owner_evidence_preflight_summary.get(
"owner_evidence_received_count", 0
),
"owner_accepted": owner_evidence_preflight_summary.get(
"owner_evidence_accepted_count", 0
),
"registry_export_accepted": owner_evidence_preflight_summary.get(
"registry_export_accepted_count",
0,
),
"runtime_gate": owner_evidence_preflight_summary.get("runtime_gate_count", 0),
"runtime_gate": owner_evidence_preflight_summary.get(
"runtime_gate_count", 0
),
},
["docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json"],
[
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json"
],
),
_lane(
"wazuh_runtime_controlled_apply_preflight",
snapshots["wazuh_runtime_apply_preflight"].get(
"status",
"controlled_apply_preflight_ready_no_runtime_action",
),
45
if _int(
runtime_apply_preflight_summary.get(
"controlled_apply_preflight_ready_count"
)
)
else 0,
"steady"
if _int(
runtime_apply_preflight_summary.get(
"controlled_apply_preflight_ready_count"
)
)
else "locked",
"執行前仍需 allowlisted check-mode、dry-run evidence 與 post-apply verifier readback",
{
"target_selectors": runtime_apply_preflight_summary.get(
"target_selector_count", 0
),
"source_diff": runtime_apply_preflight_summary.get(
"source_of_truth_diff_count", 0
),
"check_mode": runtime_apply_preflight_summary.get(
"check_mode_plan_count", 0
),
"dry_run": runtime_apply_preflight_summary.get(
"dry_run_required_count", 0
),
"rollback": runtime_apply_preflight_summary.get(
"rollback_plan_count", 0
),
"post_apply_verifier": runtime_apply_preflight_summary.get(
"post_apply_verifier_count", 0
),
"km_writeback": runtime_apply_preflight_summary.get(
"km_playbook_writeback_count", 0
),
"runtime_gate": runtime_apply_preflight_summary.get(
"runtime_gate_count", 0
),
},
[
"docs/security/wazuh-runtime-controlled-apply-preflight.snapshot.json"
],
),
_lane(
"wazuh_dashboard_api",
@@ -285,9 +440,13 @@ def load_latest_iwooos_runtime_security_readback(
"warn",
"儀表板 API、RBAC 與 TLS 修復後重新讀回",
{
"dashboard_api_degraded": wazuh_summary.get("dashboard_api_degraded_observed_count", 0),
"dashboard_api_degraded": wazuh_summary.get(
"dashboard_api_degraded_observed_count", 0
),
"runtime_gate": wazuh_summary.get("runtime_gate_count", 0),
"accepted_evidence": _accepted_evidence_count(snapshots["wazuh_runtime"]),
"accepted_evidence": _accepted_evidence_count(
snapshots["wazuh_runtime"]
),
},
[
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json",
@@ -301,9 +460,15 @@ def load_latest_iwooos_runtime_security_readback(
"locked",
"資安觀測範圍與 finding envelope 先被接受",
{
"active_scan_authorized": soc_summary.get("kali_active_scan_authorized_count", 0),
"execute_authorized": soc_summary.get("kali_execute_authorized_count", 0),
"finding_envelope_accepted": soc_summary.get("kali_finding_envelope_accepted_count", 0),
"active_scan_authorized": soc_summary.get(
"kali_active_scan_authorized_count", 0
),
"execute_authorized": soc_summary.get(
"kali_execute_authorized_count", 0
),
"finding_envelope_accepted": soc_summary.get(
"kali_finding_envelope_accepted_count", 0
),
},
[
"docs/security/kali-integration-status.snapshot.json",
@@ -317,37 +482,57 @@ def load_latest_iwooos_runtime_security_readback(
"warn",
"補齊告警路由 receipt 與實發驗證",
{
"formatter_markers": alert_summary.get("source_formatter_marker_count", 0),
"required_markers": alert_summary.get("required_output_marker_count", 0),
"telegram_send": alert_summary.get("telegram_send_authorized_count", 0),
"formatter_markers": alert_summary.get(
"source_formatter_marker_count", 0
),
"required_markers": alert_summary.get(
"required_output_marker_count", 0
),
"telegram_send": alert_summary.get(
"telegram_send_authorized_count", 0
),
},
["docs/security/telegram-alert-readability-guard.snapshot.json"],
),
_lane(
"owner_dispatch",
snapshots["owner_dispatch"].get("status", "owner_request_draft_ready_not_dispatched"),
snapshots["owner_dispatch"].get(
"status", "owner_request_draft_ready_not_dispatched"
),
0,
"locked",
"正式負責人回覆封包送達與接受",
{
"request_drafts": dispatch_summary.get("request_draft_count", 0),
"request_sent": dispatch_summary.get("request_sent_count", 0),
"owner_accepted": dispatch_summary.get("owner_response_accepted_count", 0),
"owner_accepted": dispatch_summary.get(
"owner_response_accepted_count", 0
),
},
["docs/security/monitoring-owner-request-draft.snapshot.json"],
),
_lane(
"intrusion_prevention",
"candidate_only_no_runtime_containment",
_int(intrusion_summary.get("coverage_percent_after_prevention_control")),
_int(
intrusion_summary.get("coverage_percent_after_prevention_control")
),
"warn",
"補脫敏證據參照與維護窗口",
{
"urgent_candidates": intrusion_summary.get("urgent_prevention_candidate_count", 0),
"evidence_received": intrusion_summary.get("evidence_ref_received_count", 0),
"containment_accepted": intrusion_summary.get("containment_decision_accepted_count", 0),
"urgent_candidates": intrusion_summary.get(
"urgent_prevention_candidate_count", 0
),
"evidence_received": intrusion_summary.get(
"evidence_ref_received_count", 0
),
"containment_accepted": intrusion_summary.get(
"containment_decision_accepted_count", 0
),
},
["docs/security/external-host-intrusion-prevention-control.snapshot.json"],
[
"docs/security/external-host-intrusion-prevention-control.snapshot.json"
],
),
],
"boundaries": {
@@ -376,6 +561,7 @@ def load_latest_iwooos_runtime_security_readback(
"Wazuh 正式只讀路由 disabled 或退化時仍是 P0 紅燈",
"Wazuh 即時中繼資料必須先通過負責人、機密中繼資料、唯讀範圍與啟用後讀回",
"Wazuh 負責人證據預檢 ready 不代表已收件、已接受或可啟用 active response",
"Wazuh controlled apply preflight ready 不代表 runtime gate 已開或已執行修復",
],
}
@@ -403,7 +589,9 @@ def _int(value: Any) -> int:
return value if isinstance(value, int) else 0
def _wazuh_live_summary(payload: dict[str, Any] | None, http_status: int) -> dict[str, Any]:
def _wazuh_live_summary(
payload: dict[str, Any] | None, http_status: int
) -> dict[str, Any]:
if not isinstance(payload, dict):
return {
"status": "not_checked_by_snapshot_loader",
@@ -428,7 +616,9 @@ def _wazuh_live_summary(payload: dict[str, Any] | None, http_status: int) -> dic
"agent_total": _int(summary.get("agent_total")),
"agent_active": _int(summary.get("agent_active")),
"agent_registry_empty_count": _int(summary.get("agent_registry_empty_count")),
"agent_below_expected_minimum_count": _int(summary.get("agent_below_expected_minimum_count")),
"agent_below_expected_minimum_count": _int(
summary.get("agent_below_expected_minimum_count")
),
"metadata_available_count": 1 if metadata_available else 0,
}
@@ -448,7 +638,11 @@ def _accepted_evidence_count(payload: dict[str, Any]) -> int:
evidence = payload.get("required_evidence_before_green")
if not isinstance(evidence, list):
return 0
return sum(1 for item in evidence if isinstance(item, dict) and item.get("accepted") is True)
return sum(
1
for item in evidence
if isinstance(item, dict) and item.get("accepted") is True
)
def _lane(
@@ -475,7 +669,14 @@ def _max_summary_count(
snapshots: dict[str, dict[str, Any]],
*keys: str,
) -> int:
return max((_int(_summary(payload).get(key)) for payload in snapshots.values() for key in keys), default=0)
return max(
(
_int(_summary(payload).get(key))
for payload in snapshots.values()
for key in keys
),
default=0,
)
def _require_runtime_boundaries(snapshots: dict[str, dict[str, Any]]) -> None:
@@ -505,7 +706,11 @@ def _require_runtime_boundaries(snapshots: dict[str, dict[str, Any]]) -> None:
boundaries = payload.get("execution_boundaries")
if isinstance(boundaries, dict):
invalid = sorted(
key for key in _FALSE_BOUNDARY_KEYS if key in boundaries and boundaries.get(key) is not False
key
for key in _FALSE_BOUNDARY_KEYS
if key in boundaries and boundaries.get(key) is not False
)
if invalid:
raise ValueError(f"{name}: execution boundaries must remain false: {invalid}")
raise ValueError(
f"{name}: execution boundaries must remain false: {invalid}"
)