diff --git a/apps/api/src/services/iwooos_security_asset_control_plane.py b/apps/api/src/services/iwooos_security_asset_control_plane.py index 72b5639cc..16640878c 100644 --- a/apps/api/src/services/iwooos_security_asset_control_plane.py +++ b/apps/api/src/services/iwooos_security_asset_control_plane.py @@ -349,6 +349,191 @@ def _build_control_functions( ] +def _build_security_program_domains( + *, + by_type: dict[str, int], + asset_scopes: list[dict[str, Any]], + coverage: list[dict[str, Any]], + compliance: list[dict[str, Any]], + control_functions: list[dict[str, Any]], + siem_stage_percent: int, + siem_evidence_count: int, + ai_stage_percent: int, + ai_evidence_count: int, + audit_evidence_count: int, +) -> list[dict[str, Any]]: + scope_by_id = {row["scope_id"]: row for row in asset_scopes} + function_by_id = {row["function_id"]: row for row in control_functions} + compliance_by_id = {row["dimension"]: row for row in compliance} + + def function_percent(function_id: str) -> int: + return int(function_by_id.get(function_id, {}).get("controlled_percent", 0)) + + def scope_percent(scope_id: str) -> int: + return int(scope_by_id.get(scope_id, {}).get("type_coverage_percent", 0)) + + def scope_assets(scope_id: str) -> int: + return int(scope_by_id.get(scope_id, {}).get("managed_asset_count", 0)) + + def compliance_percent(dimension: str) -> int: + return int(compliance_by_id.get(dimension, {}).get("compliant_percent", 0)) + + def compliance_total(dimension: str) -> int: + return int(compliance_by_id.get(dimension, {}).get("total", 0)) + + def average(*values: int) -> int: + return round(sum(values) / len(values)) if values else 0 + + domain_values = ( + ( + "governance_risk", + "治理、風險與責任", + function_percent("govern"), + sum(by_type.values()), + "asset_owner_or_policy_evidence_missing", + ), + ( + "asset_attack_surface", + "資產與攻擊面", + function_percent("identify"), + sum(by_type.values()), + "asset_identity_or_relationship_gap", + ), + ( + "identity_zero_trust", + "IAM 與 Zero Trust", + compliance_percent("access_reviewed"), + compliance_total("access_reviewed"), + "access_review_runtime_evidence_missing", + ), + ( + "data_secrets_privacy", + "資料、Secret 與隱私", + average( + compliance_percent("secret_rotated"), + compliance_percent("encryption_at_rest"), + ), + compliance_total("secret_rotated") + compliance_total("encryption_at_rest"), + "secret_rotation_or_encryption_evidence_missing", + ), + ( + "vulnerability_exposure", + "漏洞與暴露面", + average( + compliance_percent("cve_scan"), + compliance_percent("ssl_cert_valid"), + ), + compliance_total("cve_scan") + compliance_total("ssl_cert_valid"), + "cve_or_external_exposure_verifier_missing", + ), + ( + "endpoint_xdr_wazuh", + "端點、XDR 與 Wazuh", + 0, + 0, + "wazuh_runtime_identity_not_joined_to_asset_graph", + ), + ( + "network_dns_tls", + "網路、DNS 與 TLS", + average( + 100 if by_type.get("network", 0) > 0 else 0, + 100 if by_type.get("certificate", 0) > 0 else 0, + compliance_percent("ssl_cert_valid"), + ), + by_type.get("network", 0) + + by_type.get("certificate", 0) + + compliance_total("ssl_cert_valid"), + "network_dns_tls_runtime_evidence_missing", + ), + ( + "cloud_container_k8s", + "容器、K8s 與執行環境", + scope_percent("infrastructure"), + scope_assets("infrastructure"), + "infrastructure_scope_or_policy_evidence_missing", + ), + ( + "appsec_api", + "AppSec、API 與 DAST", + average( + scope_percent("products"), + compliance_percent("cve_scan"), + compliance_percent("ssl_cert_valid"), + ), + scope_assets("products") + compliance_total("cve_scan"), + "appsec_sast_dast_api_evidence_missing", + ), + ( + "software_supply_chain", + "軟體供應鏈與 SBOM", + average( + scope_percent("supply_chain"), + compliance_percent("cve_scan"), + ), + scope_assets("supply_chain") + compliance_total("cve_scan"), + "sbom_signature_dependency_evidence_missing", + ), + ( + "telemetry_detection", + "遙測、偵測與可觀測性", + function_percent("detect"), + by_type.get("log_stream", 0) + by_type.get("monitoring_target", 0), + "telemetry_or_detection_coverage_missing", + ), + ( + "siem_soc_threat_intel", + "SIEM、SOC 與威脅情報", + average(siem_stage_percent, 0), + siem_evidence_count, + "siem_stage_or_threat_intelligence_evidence_missing", + ), + ( + "incident_response_soar", + "事故應變、SOAR 與鑑識", + average(function_percent("respond"), function_percent("recover")), + siem_evidence_count, + "case_response_verifier_or_forensic_evidence_missing", + ), + ( + "backup_dr_resilience", + "備份、DR 與韌性", + compliance_percent("backup_tested"), + by_type.get("backup_target", 0) + compliance_total("backup_tested"), + "backup_restore_drill_evidence_missing", + ), + ( + "ai_security_automation", + "AI 安全與 Agent 自動化", + ai_stage_percent, + ai_evidence_count, + "same_run_ai_security_closure_missing", + ), + ( + "compliance_audit", + "合規、稽核與證據保存", + average( + compliance_percent("audit_log_enabled"), + 100 if audit_evidence_count > 0 else 0, + ), + audit_evidence_count + compliance_total("audit_log_enabled"), + "audit_control_or_immutable_retention_evidence_missing", + ), + ) + + return [ + { + "domain_id": domain_id, + "label": label, + "controlled_percent": percent, + "status": _status_for_percent(percent, has_evidence=evidence_count > 0), + "evidence_count": evidence_count, + "gap_code": None if percent >= 100 else gap_code, + } + for domain_id, label, percent, evidence_count, gap_code in domain_values + ] + + def _build_siem_pipeline( *, by_type: dict[str, int], @@ -628,6 +813,23 @@ def build_iwooos_security_asset_control_plane( "immutable_external_audit_store_evidenced": False, } + security_program_domains = _build_security_program_domains( + by_type=by_type, + asset_scopes=asset_scopes, + coverage=coverage, + compliance=compliance, + control_functions=control_functions, + siem_stage_percent=_percent(siem_observed_stages, len(_SIEM_STAGES)), + siem_evidence_count=siem_observed_stages + siem["incident_total_24h"], + ai_stage_percent=_percent(observed_automation_stages, len(_AI_LOOP_STAGES)), + ai_evidence_count=accepted_ai_trace_count + verified_receipts, + audit_evidence_count=( + audit["k8s_execution_audit_count_24h"] + + audit["mcp_tool_audit_count_24h"] + + audit["automation_operation_count_24h"] + ), + ) + work_items: list[dict[str, Any]] = [] if asset_total == 0 or not discovery_fresh: work_items.append( @@ -737,6 +939,10 @@ def build_iwooos_security_asset_control_plane( sum(function["controlled_percent"] for function in control_functions) / len(control_functions) ) + security_program_percent = round( + sum(domain["controlled_percent"] for domain in security_program_domains) + / len(security_program_domains) + ) return { "schema_version": _SCHEMA_VERSION, @@ -763,6 +969,13 @@ def build_iwooos_security_asset_control_plane( "compliance_violation_count": compliance_violations, "compliance_unknown_count": compliance_unknown, "nist_controlled_percent": overall_control_percent, + "security_program_domain_count": len(security_program_domains), + "security_program_controlled_domain_count": sum( + 1 + for domain in security_program_domains + if domain["status"] == "controlled" + ), + "security_program_controlled_percent": security_program_percent, "siem_stage_count": len(_SIEM_STAGES), "siem_observed_stage_count": siem_observed_stages, "siem_stage_coverage_percent": _percent( @@ -781,6 +994,7 @@ def build_iwooos_security_asset_control_plane( "coverage_dimensions": coverage, "compliance_dimensions": compliance, "nist_csf_functions": control_functions, + "security_program_domains": security_program_domains, "siem": {**siem, "pipeline": siem_pipeline}, "ai_automation": { "window_hours": _WINDOW_HOURS, @@ -836,6 +1050,9 @@ def build_unavailable_iwooos_security_asset_control_plane( "compliance_violation_count": 0, "compliance_unknown_count": 0, "nist_controlled_percent": 0, + "security_program_domain_count": 16, + "security_program_controlled_domain_count": 0, + "security_program_controlled_percent": 0, "siem_stage_count": len(_SIEM_STAGES), "siem_observed_stage_count": 0, "siem_stage_coverage_percent": 0, @@ -890,6 +1107,34 @@ def build_unavailable_iwooos_security_asset_control_plane( ("recover", "復原"), ) ], + "security_program_domains": [ + { + "domain_id": domain_id, + "label": label, + "controlled_percent": 0, + "status": "not_evidenced", + "evidence_count": 0, + "gap_code": "live_database_unavailable", + } + for domain_id, label in ( + ("governance_risk", "治理、風險與責任"), + ("asset_attack_surface", "資產與攻擊面"), + ("identity_zero_trust", "IAM 與 Zero Trust"), + ("data_secrets_privacy", "資料、Secret 與隱私"), + ("vulnerability_exposure", "漏洞與暴露面"), + ("endpoint_xdr_wazuh", "端點、XDR 與 Wazuh"), + ("network_dns_tls", "網路、DNS 與 TLS"), + ("cloud_container_k8s", "容器、K8s 與執行環境"), + ("appsec_api", "AppSec、API 與 DAST"), + ("software_supply_chain", "軟體供應鏈與 SBOM"), + ("telemetry_detection", "遙測、偵測與可觀測性"), + ("siem_soc_threat_intel", "SIEM、SOC 與威脅情報"), + ("incident_response_soar", "事故應變、SOAR 與鑑識"), + ("backup_dr_resilience", "備份、DR 與韌性"), + ("ai_security_automation", "AI 安全與 Agent 自動化"), + ("compliance_audit", "合規、稽核與證據保存"), + ) + ], "siem": { "pipeline": [ { diff --git a/apps/api/tests/test_iwooos_security_asset_control_plane.py b/apps/api/tests/test_iwooos_security_asset_control_plane.py index d22917ecc..2883db591 100644 --- a/apps/api/tests/test_iwooos_security_asset_control_plane.py +++ b/apps/api/tests/test_iwooos_security_asset_control_plane.py @@ -132,6 +132,13 @@ def test_builds_live_asset_siem_audit_and_ai_control_plane_without_false_closure assert payload["ai_automation"]["same_run_closed_loop_proven"] is False assert payload["ai_automation"]["same_run_closed_loop_count"] == 0 assert len(payload["nist_csf_functions"]) == 6 + assert payload["summary"]["security_program_domain_count"] == 16 + assert len(payload["security_program_domains"]) == 16 + domain_by_id = { + domain["domain_id"]: domain for domain in payload["security_program_domains"] + } + assert domain_by_id["endpoint_xdr_wazuh"]["status"] == "not_evidenced" + assert domain_by_id["siem_soc_threat_intel"]["controlled_percent"] == 50 assert len(payload["siem"]["pipeline"]) == 8 assert payload["security_audit"]["mcp_tool_audit_count_24h"] == 11 assert any( diff --git a/apps/web/src/components/iwooos/security-asset-control-plane-cockpit.tsx b/apps/web/src/components/iwooos/security-asset-control-plane-cockpit.tsx index 0a109cee6..1bd34df16 100644 --- a/apps/web/src/components/iwooos/security-asset-control-plane-cockpit.tsx +++ b/apps/web/src/components/iwooos/security-asset-control-plane-cockpit.tsx @@ -34,6 +34,7 @@ type Copy = { aiLoop: string; verified: string; functions: string; + framework: string; siemFlow: string; aiFlow: string; scope: string; @@ -57,6 +58,7 @@ const COPY: Record<"zh" | "en", Copy> = { aiLoop: "AI 閉環", verified: "24h 已驗證", functions: "治理六功能", + framework: "完整資安框架", siemFlow: "SIEM 八段", aiFlow: "AI Agent 七段", scope: "全資產範圍", @@ -78,6 +80,7 @@ const COPY: Record<"zh" | "en", Copy> = { aiLoop: "AI closure", verified: "Verified 24h", functions: "Six functions", + framework: "Security framework", siemFlow: "SIEM pipeline", aiFlow: "AI Agent loop", scope: "Asset scope", @@ -309,6 +312,44 @@ export function SecurityAssetControlPlaneCockpit({ +