fix(iwooos): add security tool closure readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 3m55s
CD Pipeline / build-and-deploy (push) Has started running
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
ogt
2026-07-10 16:20:21 +08:00
parent d6e69b1a90
commit e513318e68
7 changed files with 1216 additions and 0 deletions

View File

@@ -34,6 +34,9 @@ from src.services.iwooos_security_operating_system import (
from src.services.iwooos_security_control_coverage import (
load_latest_iwooos_security_control_coverage,
)
from src.services.iwooos_security_tool_closure_readback import (
load_latest_iwooos_security_tool_closure_readback,
)
from src.services.iwooos_wazuh_allowlisted_check_mode_dry_run import (
load_latest_iwooos_wazuh_allowlisted_check_mode_dry_run,
)
@@ -702,6 +705,38 @@ async def get_iwooos_runtime_security_readback() -> dict[str, Any]:
) from exc
@router.get(
"/api/v1/iwooos/security-tool-closure-readback",
response_model=dict[str, Any],
summary="取得 IwoooS 資安工具閉環讀回",
description=(
"彙整 Wazuh、SBOM / vulnerability、source policy、K8s policy、runtime detection、"
"DAST、Sigma / OCSF 與 AI controlled apply 的 committed readback形成公開安全的"
"工具閉環控制台。此端點不查 live host、不讀 secret、不執行 scanner、不送 SOAR、"
"不啟用 active response、不開 runtime gate。"
),
)
async def get_iwooos_security_tool_closure_readback() -> dict[str, Any]:
"""回傳 IwoooS 資安工具閉環公開安全只讀總表。"""
try:
alert_receipt_readback = await _alertmanager_receipt_readback()
payload = await asyncio.to_thread(
load_latest_iwooos_security_tool_closure_readback,
alert_receipt_readback=alert_receipt_readback,
)
return redact_public_lan_topology(payload)
except FileNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
except (json.JSONDecodeError, ValueError) as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"IwoooS security tool closure readback 無效:{exc}",
) from exc
@router.get(
"/api/v1/iwooos/security-control-coverage",
response_model=dict[str, Any],

View File

@@ -0,0 +1,511 @@
"""
IwoooS security tool closure readback.
This service joins the committed Wazuh, security operating-system and coverage
readbacks into a public-safe tool closure cockpit. It does not query live hosts,
read secrets, run scanners, trigger SOAR, or authorize runtime actions.
"""
from __future__ import annotations
from typing import Any
from src.services.iwooos_security_control_coverage import (
load_latest_iwooos_security_control_coverage,
)
from src.services.iwooos_security_operating_system import (
load_latest_iwooos_security_operating_system,
)
from src.services.iwooos_wazuh_allowlisted_check_mode_dry_run import (
load_latest_iwooos_wazuh_allowlisted_check_mode_dry_run,
)
from src.services.iwooos_wazuh_managed_host_coverage import (
load_latest_iwooos_wazuh_managed_host_coverage,
)
from src.services.iwooos_wazuh_manager_registry_reviewer_validation import (
load_latest_iwooos_wazuh_manager_registry_reviewer_validation,
)
from src.services.iwooos_wazuh_runtime_controlled_apply_preflight import (
load_latest_iwooos_wazuh_runtime_controlled_apply_preflight,
)
from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
load_latest_iwooos_wazuh_runtime_gate_owner_review_readback,
)
_SCHEMA_VERSION = "iwooos_security_tool_closure_readback_v1"
_REQUIRED_ZERO_SUMMARY_KEYS = {
"runtime_gate_count",
"host_write_authorized_count",
"active_response_authorized_count",
"wazuh_active_response_authorized_count",
"secret_value_collection_allowed_count",
"secret_value_collected_count",
}
def load_latest_iwooos_security_tool_closure_readback(
alert_receipt_readback: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Return the public-safe external tool closure rollup."""
coverage = load_latest_iwooos_security_control_coverage()
security_os = load_latest_iwooos_security_operating_system(
alert_receipt_readback=alert_receipt_readback
)
managed_hosts = load_latest_iwooos_wazuh_managed_host_coverage()
registry = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
controlled_apply = load_latest_iwooos_wazuh_runtime_controlled_apply_preflight()
owner_review = load_latest_iwooos_wazuh_runtime_gate_owner_review_readback()
dry_run = load_latest_iwooos_wazuh_allowlisted_check_mode_dry_run()
source_payloads = [
coverage,
security_os,
managed_hosts,
registry,
controlled_apply,
owner_review,
dry_run,
]
_require_runtime_boundaries(source_payloads)
source_summaries = {
"coverage": _summary(coverage),
"security_os": _summary(security_os),
"managed_hosts": _summary(managed_hosts),
"registry": _summary(registry),
"controlled_apply": _summary(controlled_apply),
"owner_review": _summary(owner_review),
"dry_run": _summary(dry_run),
}
tracks = _tool_tracks(source_summaries)
summary = _summary_rollup(source_summaries, tracks)
return {
"schema_version": _SCHEMA_VERSION,
"status": "tool_closure_readback_ready_runtime_actions_closed",
"mode": "committed_readback_rollup_no_live_tool_execution",
"summary": summary,
"tool_tracks": tracks,
"next_executable_queue": _next_executable_queue(source_summaries),
"acceptance_definition": [
"wazuh_registry_fim_vulnerability_alert_receipt_green_with_post_verifier",
"sbom_and_vulnerability_reports_attached_to_package_and_image_artifacts",
"runtime_detection_events_normalized_and_reviewed_without_auto_block",
"web_api_dast_scope_rate_limit_and_baseline_report_closed",
"ai_controlled_apply_has_selector_diff_check_mode_rollback_post_verifier_km_writeback",
],
"boundary_markers": _boundary_markers(summary),
"boundaries": {
"live_host_query_performed": False,
"live_wazuh_query_performed": False,
"scanner_execution_performed": False,
"payload_persisted": False,
"runtime_execution_authorized": False,
"runtime_gate_open": False,
"host_write_authorized": False,
"wazuh_active_response_authorized": False,
"soar_action_authorized": False,
"secret_value_collection_allowed": False,
"not_authorization": True,
},
"no_false_green_rules": [
"tool closure readback is a cockpit rollup, not an active scanner or SOAR switch",
"Wazuh manager registry accepted does not close FIM, vulnerability, alert receipt, or active response",
"SBOM, DAST, runtime detection and policy tracks remain not closed until artifacts and post-verifiers exist",
"runtime gate, host write, active response and secret collection must remain zero until a separate post-verifier gate passes",
],
"source_refs": [
*coverage.get("source_refs", []),
*security_os.get("source_refs", []),
*managed_hosts.get("source_refs", []),
*registry.get("source_refs", []),
*controlled_apply.get("source_refs", []),
*owner_review.get("source_refs", []),
*dry_run.get("source_refs", []),
],
}
def _tool_tracks(source_summaries: dict[str, dict[str, Any]]) -> list[dict[str, Any]]:
coverage = source_summaries["coverage"]
security_os = source_summaries["security_os"]
managed_hosts = source_summaries["managed_hosts"]
registry = source_summaries["registry"]
controlled_apply = source_summaries["controlled_apply"]
owner_review = source_summaries["owner_review"]
dry_run = source_summaries["dry_run"]
expected_hosts = max(
_int(managed_hosts.get("expected_host_scope_count")),
_int(registry.get("expected_scope_alias_count")),
_int(coverage.get("wazuh_expected_host_scope_count")),
)
accepted_hosts = max(
_int(managed_hosts.get("manager_registry_accepted_count")),
_int(registry.get("manager_registry_accepted_count")),
_int(coverage.get("wazuh_manager_registry_accepted_count")),
_int(security_os.get("wazuh_registry_accepted_count")),
)
registry_ready = expected_hosts > 0 and accepted_hosts >= expected_hosts
alert_ready = _int(security_os.get("alert_receipt_accepted_count")) > 0
controlled_apply_ready_signals = sum(
1
for key in (
"target_selector_count",
"source_of_truth_diff_count",
"check_mode_plan_count",
"rollback_plan_count",
"post_apply_verifier_count",
"km_playbook_writeback_count",
"controlled_apply_preflight_ready_count",
)
if _int(controlled_apply.get(key)) > 0
)
owner_review_ready_signals = sum(
1
for key in (
"target_selector_count",
"source_of_truth_diff_count",
"check_mode_plan_count",
"dry_run_evidence_count",
"rollback_plan_count",
"post_apply_verifier_count",
"km_playbook_writeback_count",
"owner_review_packet_review_ready_count",
)
if _int(owner_review.get(key)) > 0
)
dry_run_ready_signals = sum(
1
for key in (
"allowlisted_target_selector_count",
"check_mode_plan_count",
"dry_run_evidence_ref_count",
"dry_run_result_ref_count",
"post_dry_run_verifier_count",
"rollback_revalidation_count",
"km_playbook_writeback_ready_count",
)
if _int(dry_run.get(key)) > 0
)
return [
_track(
track_id="wazuh_detection_response",
priority="P0",
label="Wazuh detection and response",
tool_ids=["wazuh"],
ready_signal_count=(1 if registry_ready else 0) + (1 if alert_ready else 0),
required_signal_count=6,
next_executable_action="close_fim_vulnerability_alert_receipt_then_keep_active_response_gated",
required_verifier="wazuh_registry_fim_vulnerability_alert_receipt_post_verifier",
source_refs=[
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json",
"docs/security/iwooos-security-operating-system.snapshot.json",
],
),
_track(
track_id="supply_chain_sbom_scan",
priority="P0",
label="Supply-chain SBOM and vulnerability scan",
tool_ids=["trivy", "syft", "grype"],
ready_signal_count=0,
required_signal_count=5,
next_executable_action="attach_sbom_vulnerability_report_and_no_secret_output_guard",
required_verifier="sbom_vulnerability_artifact_post_verifier",
source_refs=["docs/security/security-asset-control-ledger.snapshot.json"],
),
_track(
track_id="source_control_scorecard",
priority="P1",
label="Source-control hygiene scorecard",
tool_ids=["openssf_scorecard", "gitea_source_policy"],
ready_signal_count=0,
required_signal_count=4,
next_executable_action="map_branch_protection_dependency_update_and_build_policy_readback",
required_verifier="source_control_policy_scorecard_verifier",
source_refs=["docs/evaluations/runtime_surface_inventory_latest"],
),
_track(
track_id="k8s_policy_attestation",
priority="P1",
label="Kubernetes policy and image attestation",
tool_ids=["kyverno", "cosign"],
ready_signal_count=0,
required_signal_count=5,
next_executable_action="stage_policy_check_mode_image_signature_and_exception_register",
required_verifier="k8s_policy_attestation_post_verifier",
source_refs=["docs/security/host-service-config-inventory.snapshot.json"],
),
_track(
track_id="runtime_threat_detection",
priority="P1",
label="Runtime threat detection",
tool_ids=["falco"],
ready_signal_count=0,
required_signal_count=4,
next_executable_action="stage_rule_dry_run_event_redaction_and_wazuh_correlation",
required_verifier="runtime_detection_event_post_verifier",
source_refs=["docs/security/monitoring-alerting-observability-inventory.snapshot.json"],
),
_track(
track_id="web_api_dast",
priority="P1",
label="Web and API DAST",
tool_ids=["owasp_zap_or_equivalent_dast"],
ready_signal_count=0,
required_signal_count=5,
next_executable_action="prepare_bounded_target_selector_rate_limit_and_baseline_report",
required_verifier="web_api_dast_scope_and_report_verifier",
source_refs=["docs/security/ssh-network-access-inventory.snapshot.json"],
),
_track(
track_id="normalized_detection_schema",
priority="P1",
label="Normalized detection schema",
tool_ids=["sigma", "ocsf"],
ready_signal_count=(1 if alert_ready else 0),
required_signal_count=4,
next_executable_action="normalize_wazuh_alertmanager_app_events_into_review_cards",
required_verifier="sigma_ocsf_event_mapping_regression_verifier",
source_refs=["docs/security/iwooos-security-operating-system.snapshot.json"],
),
_track(
track_id="ai_agent_controlled_apply",
priority="P0",
label="AI Agent controlled apply loop",
tool_ids=["ai_agent", "mcp", "rag", "km_playbook"],
ready_signal_count=min(
controlled_apply_ready_signals
+ owner_review_ready_signals
+ dry_run_ready_signals,
9,
),
required_signal_count=9,
next_executable_action="run_allowlisted_check_mode_with_rollback_post_verifier_and_km_writeback",
required_verifier="ai_controlled_apply_post_verifier_and_learning_writeback",
source_refs=[
"docs/security/wazuh-runtime-controlled-apply-preflight.snapshot.json",
"docs/security/wazuh-runtime-gate-owner-review-readback.snapshot.json",
"docs/security/wazuh-allowlisted-check-mode-dry-run.snapshot.json",
],
),
]
def _track(
*,
track_id: str,
priority: str,
label: str,
tool_ids: list[str],
ready_signal_count: int,
required_signal_count: int,
next_executable_action: str,
required_verifier: str,
source_refs: list[str],
) -> dict[str, Any]:
ready_count = max(min(ready_signal_count, required_signal_count), 0)
missing_count = max(required_signal_count - ready_count, 0)
closure_percent = int(round((ready_count / required_signal_count) * 100)) if required_signal_count else 0
if missing_count == 0:
closure_state = "readback_ready_runtime_gate_still_closed"
elif ready_count > 0:
closure_state = "partial_readback_missing_verifier"
else:
closure_state = "required_not_closed"
return {
"track_id": track_id,
"priority": priority,
"label": label,
"tool_ids": tool_ids,
"closure_state": closure_state,
"closure_percent": closure_percent,
"ready_signal_count": ready_count,
"required_signal_count": required_signal_count,
"missing_signal_count": missing_count,
"runtime_gate_open": False,
"active_response_authorized": False,
"host_write_authorized": False,
"secret_value_collection_allowed": False,
"next_executable_action": next_executable_action,
"required_verifier": required_verifier,
"source_refs": source_refs,
}
def _summary_rollup(
source_summaries: dict[str, dict[str, Any]],
tracks: list[dict[str, Any]],
) -> dict[str, Any]:
coverage = source_summaries["coverage"]
security_os = source_summaries["security_os"]
managed_hosts = source_summaries["managed_hosts"]
registry = source_summaries["registry"]
controlled_apply = source_summaries["controlled_apply"]
owner_review = source_summaries["owner_review"]
dry_run = source_summaries["dry_run"]
expected_hosts = max(
_int(managed_hosts.get("expected_host_scope_count")),
_int(registry.get("expected_scope_alias_count")),
_int(coverage.get("wazuh_expected_host_scope_count")),
)
accepted_hosts = max(
_int(managed_hosts.get("manager_registry_accepted_count")),
_int(registry.get("manager_registry_accepted_count")),
_int(coverage.get("wazuh_manager_registry_accepted_count")),
_int(security_os.get("wazuh_registry_accepted_count")),
)
total_required = sum(_int(track.get("required_signal_count")) for track in tracks)
total_ready = sum(_int(track.get("ready_signal_count")) for track in tracks)
return {
"source_readback_count": len(source_summaries),
"tool_track_count": len(tracks),
"p0_tool_track_count": sum(1 for track in tracks if track.get("priority") == "P0"),
"closed_tool_track_count": sum(
1 for track in tracks if _int(track.get("missing_signal_count")) == 0
),
"partial_tool_track_count": sum(
1
for track in tracks
if _int(track.get("ready_signal_count")) > 0
and _int(track.get("missing_signal_count")) > 0
),
"missing_tool_track_count": sum(
1 for track in tracks if _int(track.get("ready_signal_count")) == 0
),
"total_ready_signal_count": total_ready,
"total_required_signal_count": total_required,
"tool_closure_percent": int(round((total_ready / total_required) * 100))
if total_required
else 0,
"wazuh_expected_host_scope_count": expected_hosts,
"wazuh_manager_registry_accepted_count": accepted_hosts,
"wazuh_registry_complete_count": 1 if expected_hosts > 0 and accepted_hosts >= expected_hosts else 0,
"wazuh_fim_vulnerability_alert_closed_count": 0,
"alert_receipt_observed_count": _int(
security_os.get("alert_receipt_observed_count")
),
"alert_receipt_accepted_count": _int(
security_os.get("alert_receipt_accepted_count")
),
"alert_receipt_source_status": str(
security_os.get("alert_receipt_source_status") or "not_connected"
),
"controlled_apply_preflight_ready_count": _int(
controlled_apply.get("controlled_apply_preflight_ready_count")
),
"owner_review_packet_accepted_count": _int(
owner_review.get("owner_review_packet_accepted_count")
),
"allowlisted_dry_run_result_ref_count": _int(
dry_run.get("dry_run_result_ref_count")
),
"supply_chain_scan_closed_count": 0,
"runtime_detection_closed_count": 0,
"web_api_dast_closed_count": 0,
"normalized_detection_schema_closed_count": 0,
"runtime_gate_count": 0,
"host_write_authorized_count": 0,
"active_response_authorized_count": 0,
"secret_value_collection_allowed_count": 0,
}
def _next_executable_queue(
source_summaries: dict[str, dict[str, Any]],
) -> list[dict[str, Any]]:
managed_hosts = source_summaries["managed_hosts"]
registry = source_summaries["registry"]
controlled_apply = source_summaries["controlled_apply"]
dry_run = source_summaries["dry_run"]
expected_hosts = max(
_int(managed_hosts.get("expected_host_scope_count")),
_int(registry.get("expected_scope_alias_count")),
)
accepted_hosts = max(
_int(managed_hosts.get("manager_registry_accepted_count")),
_int(registry.get("manager_registry_accepted_count")),
)
registry_gate = (
"wazuh_registry_complete_move_to_fim_vulnerability_alert"
if expected_hosts > 0 and accepted_hosts >= expected_hosts
else "complete_wazuh_manager_registry_alias_matrix"
)
return [
{
"queue_id": "P0-03",
"track_id": "wazuh_detection_response",
"state": registry_gate,
"next_action": "attach_fim_vulnerability_alert_receipt_post_verifier",
"runtime_gate_open": False,
},
{
"queue_id": "P0-07",
"track_id": "supply_chain_sbom_scan",
"state": "sbom_and_scan_artifacts_missing",
"next_action": "stage_trivy_syft_grype_check_mode_reports",
"runtime_gate_open": False,
},
{
"queue_id": "P0-08",
"track_id": "ai_agent_controlled_apply",
"state": "preflight_ready_waiting_review_and_dry_run_acceptance",
"next_action": "connect_target_selector_diff_check_mode_rollback_post_verifier_km",
"ready_signal_count": _int(
controlled_apply.get("controlled_apply_preflight_ready_count")
)
+ _int(dry_run.get("dry_run_result_ref_count")),
"runtime_gate_open": False,
},
]
def _summary(payload: dict[str, Any]) -> dict[str, Any]:
summary = payload.get("summary")
return summary if isinstance(summary, dict) else {}
def _int(value: Any) -> int:
return value if isinstance(value, int) else 0
def _require_runtime_boundaries(payloads: list[dict[str, Any]]) -> None:
for payload in payloads:
summary = _summary(payload)
for key in _REQUIRED_ZERO_SUMMARY_KEYS:
if key in summary and _int(summary.get(key)) != 0:
raise ValueError(f"{payload.get('schema_version')}: summary.{key} must remain 0")
boundaries = payload.get("boundaries")
if isinstance(boundaries, dict):
for key in (
"runtime_execution_authorized",
"runtime_gate_open",
"host_write_authorized",
"wazuh_active_response_authorized",
"secret_value_collection_allowed",
):
if key in boundaries and boundaries.get(key) is not False:
raise ValueError(
f"{payload.get('schema_version')}: boundaries.{key} must remain false"
)
def _boundary_markers(summary: dict[str, Any]) -> list[str]:
return [
"iwooos_security_tool_closure_readback_visible=true",
f"iwooos_security_tool_closure_tool_track_count={summary['tool_track_count']}",
f"iwooos_security_tool_closure_p0_tool_track_count={summary['p0_tool_track_count']}",
f"iwooos_security_tool_closure_percent={summary['tool_closure_percent']}",
f"iwooos_security_tool_closure_wazuh_manager_registry_accepted_count={summary['wazuh_manager_registry_accepted_count']}",
f"iwooos_security_tool_closure_alert_receipt_accepted_count={summary['alert_receipt_accepted_count']}",
"iwooos_security_tool_closure_runtime_gate_count=0",
"host_write_authorized=false",
"wazuh_active_response_authorized=false",
"secret_value_collection_allowed=false",
"not_authorization=true",
]