From 27a7190c2c033bdbe86ecf25e5220eff42c0e9a2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 15:53:17 +0800 Subject: [PATCH] feat(iwooos): commit wazuh live metadata readiness --- .../iwooos_runtime_security_readback.py | 19 ++++- .../iwooos_wazuh_live_metadata_gate.py | 69 ++++++++++++++++--- .../test_iwooos_runtime_security_readback.py | 27 +++++--- apps/web/messages/en.json | 28 ++++---- apps/web/messages/zh-TW.json | 28 ++++---- apps/web/src/app/[locale]/iwooos/page.tsx | 31 +++++---- docs/LOGBOOK.md | 37 ++++++++++ ...donly-live-metadata-env-gate.snapshot.json | 31 +++++---- .../wazuh-readonly-live-metadata-env-gate.py | 29 ++++---- 9 files changed, 213 insertions(+), 86 deletions(-) diff --git a/apps/api/src/services/iwooos_runtime_security_readback.py b/apps/api/src/services/iwooos_runtime_security_readback.py index 99e9c13d..8ed3c540 100644 --- a/apps/api/src/services/iwooos_runtime_security_readback.py +++ b/apps/api/src/services/iwooos_runtime_security_readback.py @@ -94,6 +94,15 @@ def load_latest_iwooos_runtime_security_readback( dispatch_summary = _summary(snapshots["owner_dispatch"]) intrusion_summary = _summary(snapshots["intrusion_prevention"]) live_wazuh = _wazuh_live_summary(wazuh_live_status, wazuh_live_http_status) + live_metadata_gate_ready = all( + _int(live_metadata_gate_summary.get(key)) == 1 + for key in ( + "live_metadata_owner_response_accepted_count", + "secret_source_metadata_accepted_count", + "wazuh_manager_health_ref_accepted_count", + "readonly_account_scope_accepted_count", + ) + ) source_refs = [f"docs/security/{filename}" for filename in _SNAPSHOT_FILES.values()] runtime_gate_count = _max_summary_count( @@ -345,9 +354,13 @@ def load_latest_iwooos_runtime_security_readback( snapshots["wazuh_live_metadata_gate"].get( "status", "blocked_waiting_live_metadata_owner_response" ), - 0, - "locked", - "補齊負責人回覆、機密中繼資料、管理節點健康、唯讀範圍與啟用後讀回", + 40 if live_metadata_gate_ready else 0, + "steady" if live_metadata_gate_ready else "locked", + ( + "進入 server-side env enable controlled review 與 post-enable readback;live query 仍未授權" + if live_metadata_gate_ready + else "補齊負責人回覆、機密中繼資料、管理節點健康、唯讀範圍與啟用後讀回" + ), { "route_readback": live_metadata_gate_summary.get( "production_route_readback_passed_count", 0 diff --git a/apps/api/src/services/iwooos_wazuh_live_metadata_gate.py b/apps/api/src/services/iwooos_wazuh_live_metadata_gate.py index 63d0d23c..ffeeda8e 100644 --- a/apps/api/src/services/iwooos_wazuh_live_metadata_gate.py +++ b/apps/api/src/services/iwooos_wazuh_live_metadata_gate.py @@ -408,8 +408,16 @@ def _items(summary: dict[str, Any]) -> list[dict[str, Any]]: _item( "server_env_owner", "ENV-2", - "waiting_owner_response", - "locked", + ( + "owner_metadata_accepted" + if summary["live_metadata_owner_response_accepted_count"] + else "waiting_owner_response" + ), + ( + "steady" + if summary["live_metadata_owner_response_accepted_count"] + else "locked" + ), { "owner_received": summary["live_metadata_owner_response_received_count"], "owner_accepted": summary["live_metadata_owner_response_accepted_count"], @@ -418,15 +426,23 @@ def _items(summary: dict[str, Any]) -> list[dict[str, Any]]: _item( "secret_metadata", "ENV-3", - "metadata_only_waiting_acceptance", - "locked", + ( + "secret_metadata_accepted" + if summary["secret_source_metadata_accepted_count"] + else "metadata_only_waiting_acceptance" + ), + "steady" if summary["secret_source_metadata_accepted_count"] else "locked", {"secret_metadata_accepted": summary["secret_source_metadata_accepted_count"]}, ), _item( "manager_health", "ENV-4", - "waiting_manager_health_ref", - "warn", + ( + "manager_health_ref_accepted" + if summary["wazuh_manager_health_ref_accepted_count"] + else "waiting_manager_health_ref" + ), + "steady" if summary["wazuh_manager_health_ref_accepted_count"] else "warn", { "manager_health_accepted": summary["wazuh_manager_health_ref_accepted_count"], "live_route_degraded": summary["wazuh_live_route_degraded_count"], @@ -435,8 +451,12 @@ def _items(summary: dict[str, Any]) -> list[dict[str, Any]]: _item( "readonly_scope", "ENV-5", - "waiting_readonly_scope_ref", - "warn", + ( + "readonly_scope_ref_accepted" + if summary["readonly_account_scope_accepted_count"] + else "waiting_readonly_scope_ref" + ), + "steady" if summary["readonly_account_scope_accepted_count"] else "warn", {"readonly_scope_accepted": summary["readonly_account_scope_accepted_count"]}, ), _item( @@ -678,11 +698,42 @@ def _first_blocking_lane(findings: list[dict[str, Any]]) -> str | None: def _require_boundaries(payload: dict[str, Any]) -> None: summary = _summary(payload) + readiness_counts = { + "live_metadata_owner_response_received_count": _int( + summary.get("live_metadata_owner_response_received_count") + ), + "live_metadata_owner_response_accepted_count": _int( + summary.get("live_metadata_owner_response_accepted_count") + ), + "secret_source_metadata_accepted_count": _int( + summary.get("secret_source_metadata_accepted_count") + ), + "wazuh_manager_health_ref_accepted_count": _int( + summary.get("wazuh_manager_health_ref_accepted_count") + ), + "readonly_account_scope_accepted_count": _int( + summary.get("readonly_account_scope_accepted_count") + ), + } + for key, count in readiness_counts.items(): + if count not in {0, 1}: + raise ValueError( + f"Wazuh 即時中繼資料閘門 summary.{key} 必須維持 0 或 1" + ) + accepted_count = readiness_counts["live_metadata_owner_response_accepted_count"] + if accepted_count > readiness_counts["live_metadata_owner_response_received_count"]: + raise ValueError("Wazuh 即時中繼資料閘門 accepted 不得大於 received") for key in ( - "live_metadata_owner_response_accepted_count", "secret_source_metadata_accepted_count", "wazuh_manager_health_ref_accepted_count", "readonly_account_scope_accepted_count", + ): + if readiness_counts[key] > accepted_count: + raise ValueError( + f"Wazuh 即時中繼資料閘門 summary.{key} 不得超過 owner accepted" + ) + + for key in ( "post_enable_readback_passed_count", "wazuh_api_live_query_authorized_count", "wazuh_active_response_authorized_count", diff --git a/apps/api/tests/test_iwooos_runtime_security_readback.py b/apps/api/tests/test_iwooos_runtime_security_readback.py index 6549e6bc..751dd7e6 100644 --- a/apps/api/tests/test_iwooos_runtime_security_readback.py +++ b/apps/api/tests/test_iwooos_runtime_security_readback.py @@ -113,17 +113,17 @@ def test_iwooos_runtime_security_readback_preserves_zero_runtime_gates() -> None assert payload["summary"]["wazuh_live_route_degraded_count"] == 1 assert payload["summary"]["wazuh_live_readonly_api_enabled_count"] == 0 assert payload["summary"]["wazuh_live_metadata_available_count"] == 0 - assert payload["summary"]["wazuh_live_metadata_gate_owner_accepted_count"] == 0 + assert payload["summary"]["wazuh_live_metadata_gate_owner_accepted_count"] == 1 assert ( - payload["summary"]["wazuh_live_metadata_gate_secret_source_accepted_count"] == 0 + payload["summary"]["wazuh_live_metadata_gate_secret_source_accepted_count"] == 1 ) assert ( payload["summary"]["wazuh_live_metadata_gate_manager_health_accepted_count"] - == 0 + == 1 ) assert ( payload["summary"]["wazuh_live_metadata_gate_readonly_scope_accepted_count"] - == 0 + == 1 ) assert ( payload["summary"]["wazuh_live_metadata_gate_post_enable_readback_count"] == 0 @@ -209,7 +209,13 @@ def test_iwooos_runtime_security_readback_lanes_are_candidate_only() -> None: for lane in payload["lanes"] ) assert all( - lane["lane_id"] != "wazuh_live_metadata_gate" or lane["completion_percent"] == 0 + lane["lane_id"] != "wazuh_live_metadata_gate" + or ( + lane["completion_percent"] == 40 + and lane["metrics"]["owner_accepted"] == 1 + and lane["metrics"]["secret_metadata_accepted"] == 1 + and lane["metrics"]["post_enable_readback"] == 0 + ) for lane in payload["lanes"] ) assert all( @@ -252,6 +258,7 @@ def test_iwooos_runtime_security_readback_api_is_public_safe(monkeypatch) -> Non assert data["summary"]["wazuh_live_route_http_status"] == 200 assert data["summary"]["wazuh_live_route_degraded_count"] == 1 assert data["summary"]["wazuh_live_metadata_available_count"] == 0 + assert data["summary"]["wazuh_live_metadata_gate_owner_accepted_count"] == 1 assert data["summary"]["wazuh_live_metadata_gate_live_query_authorized_count"] == 0 assert data["summary"]["wazuh_owner_evidence_accepted_count"] == 0 assert data["summary"]["wazuh_owner_evidence_runtime_gate_count"] == 0 @@ -330,11 +337,13 @@ def test_iwooos_wazuh_live_metadata_gate_api_is_public_safe(monkeypatch) -> None assert response.status_code == 200 data = response.json() assert data["schema_version"] == "iwooos_wazuh_live_metadata_gate_readback_v1" - assert data["status"] == "blocked_waiting_live_metadata_owner_response" + assert data["status"] == "ready_for_server_side_env_enable_review_no_secret_collection" assert data["summary"]["production_route_readback_passed_count"] == 1 - assert data["summary"]["live_metadata_owner_response_accepted_count"] == 0 - assert data["summary"]["secret_source_metadata_accepted_count"] == 0 - assert data["summary"]["readonly_account_scope_accepted_count"] == 0 + assert data["summary"]["live_metadata_owner_response_received_count"] == 1 + assert data["summary"]["live_metadata_owner_response_accepted_count"] == 1 + assert data["summary"]["secret_source_metadata_accepted_count"] == 1 + assert data["summary"]["wazuh_manager_health_ref_accepted_count"] == 1 + assert data["summary"]["readonly_account_scope_accepted_count"] == 1 assert data["summary"]["post_enable_readback_passed_count"] == 0 assert data["summary"]["wazuh_api_live_query_authorized_count"] == 0 assert data["summary"]["wazuh_active_response_authorized_count"] == 0 diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index eabac9b1..fe6b71b5 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -20929,8 +20929,8 @@ }, "wazuhLiveMetadataEnvGate": { "eyebrow": "Wazuh 即時中繼資料環境閘門", - "title": "Wazuh 查詢要等正式路由、負責人與機密中繼資料都過關", - "subtitle": "這張卡把 Wazuh 即時中繼資料啟用前的條件拆開:正式路由讀回、伺服端環境變數負責人、機密來源中繼資料、管理節點健康、唯讀帳號範圍與啟用後讀回都要先補齊;目前即時查詢、主動回應、主機寫入與執行期閘門都是 0。", + "title": "Wazuh 即時中繼資料已完成 metadata readiness,下一關是啟用後讀回", + "subtitle": "這張卡把 Wazuh 即時中繼資料啟用前的條件拆開:正式路由讀回、伺服端環境變數負責人、機密來源中繼資料、管理節點健康與唯讀帳號範圍已接受為脫敏 readiness;啟用後讀回仍為 0,目前即時查詢、主動回應、主機寫入與執行期閘門都是 0。", "checkLabel": "檢核", "stateLabel": "狀態", "loadingBoundary": "正在讀取只讀閘門", @@ -20943,11 +20943,11 @@ }, "owner": { "label": "負責人回覆", - "detail": "伺服端環境變數與啟用責任尚未被審查者接受。" + "detail": "伺服端環境變數與啟用責任已接受為 metadata readiness。" }, "secretMeta": { "label": "機密中繼資料", - "detail": "只允許機密來源中繼資料與負責人,不收密碼、權杖或雜湊值。" + "detail": "機密來源只接受中繼資料 ref,不收密碼、權杖或雜湊值。" }, "liveQuery": { "label": "即時查詢", @@ -20957,14 +20957,18 @@ "status": { "loading": "正在讀取 Wazuh 即時中繼資料閘門", "failed": "Wazuh 即時中繼資料閘門尚未部署或讀取失敗", - "ready": "Wazuh 即時中繼資料閘門已讀回,但執行期仍關閉" + "ready": "Wazuh metadata readiness 已讀回,post-enable 與執行期仍關閉" }, "states": { "route_readback_passed": "路由已讀回", "waiting_owner_response": "待負責人", + "owner_metadata_accepted": "metadata 已接受", "metadata_only_waiting_acceptance": "只收中繼資料", + "secret_metadata_accepted": "中繼資料已接受", "waiting_manager_health_ref": "待健康參照", + "manager_health_ref_accepted": "健康參照已接受", "waiting_readonly_scope_ref": "待範圍參照", + "readonly_scope_ref_accepted": "範圍參照已接受", "waiting_post_enable_readback": "待讀回驗證" }, "items": { @@ -20973,20 +20977,20 @@ "body": "正式讀回已通過;下一步是 controlled gate、機密中繼資料與 Wazuh manager registry readback,不是繞過 PlayBook 的主機寫入。" }, "serverEnv": { - "title": "伺服端環境變數需負責人", - "body": "IWOOOS_WAZUH_READONLY_ENABLED 與 Wazuh API 環境變數只能由正式伺服端流程注入,不能寫到前端 bundle。" + "title": "伺服端環境變數 owner 已接受", + "body": "IWOOOS_WAZUH_READONLY_ENABLED 與 Wazuh API 環境變數只記錄 owner 與流程 metadata,不能寫到前端 bundle。" }, "secretMetadata": { - "title": "機密只收中繼資料", + "title": "機密中繼資料已接受", "body": "只記錄注入來源、負責人、維護窗口與回滾路徑;不得貼密碼、權杖、Cookie、工作階段或任何機密片段。" }, "managerHealth": { - "title": "Wazuh 管理節點健康待參照", - "body": "需要管理節點、API 與 TLS 健康狀態的脫敏參照;不能只用 Wazuh 已安裝或儀表板可見判斷。" + "title": "Wazuh 管理節點健康參照已接受", + "body": "管理節點、API 與 TLS 健康狀態已以脫敏參照接受;不能只用 Wazuh 已安裝或儀表板可見判斷。" }, "readonlyScope": { - "title": "唯讀帳號範圍待驗", - "body": "只允許中繼資料查詢範圍;主動回應、規則變更、解碼器變更與主機操作必須留在另一個閘門。" + "title": "唯讀帳號範圍已接受", + "body": "唯讀帳號範圍已以脫敏參照接受;主動回應、規則變更、解碼器變更與主機操作必須留在另一個閘門。" }, "postEnable": { "title": "啟用後還要讀回", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index eabac9b1..fe6b71b5 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -20929,8 +20929,8 @@ }, "wazuhLiveMetadataEnvGate": { "eyebrow": "Wazuh 即時中繼資料環境閘門", - "title": "Wazuh 查詢要等正式路由、負責人與機密中繼資料都過關", - "subtitle": "這張卡把 Wazuh 即時中繼資料啟用前的條件拆開:正式路由讀回、伺服端環境變數負責人、機密來源中繼資料、管理節點健康、唯讀帳號範圍與啟用後讀回都要先補齊;目前即時查詢、主動回應、主機寫入與執行期閘門都是 0。", + "title": "Wazuh 即時中繼資料已完成 metadata readiness,下一關是啟用後讀回", + "subtitle": "這張卡把 Wazuh 即時中繼資料啟用前的條件拆開:正式路由讀回、伺服端環境變數負責人、機密來源中繼資料、管理節點健康與唯讀帳號範圍已接受為脫敏 readiness;啟用後讀回仍為 0,目前即時查詢、主動回應、主機寫入與執行期閘門都是 0。", "checkLabel": "檢核", "stateLabel": "狀態", "loadingBoundary": "正在讀取只讀閘門", @@ -20943,11 +20943,11 @@ }, "owner": { "label": "負責人回覆", - "detail": "伺服端環境變數與啟用責任尚未被審查者接受。" + "detail": "伺服端環境變數與啟用責任已接受為 metadata readiness。" }, "secretMeta": { "label": "機密中繼資料", - "detail": "只允許機密來源中繼資料與負責人,不收密碼、權杖或雜湊值。" + "detail": "機密來源只接受中繼資料 ref,不收密碼、權杖或雜湊值。" }, "liveQuery": { "label": "即時查詢", @@ -20957,14 +20957,18 @@ "status": { "loading": "正在讀取 Wazuh 即時中繼資料閘門", "failed": "Wazuh 即時中繼資料閘門尚未部署或讀取失敗", - "ready": "Wazuh 即時中繼資料閘門已讀回,但執行期仍關閉" + "ready": "Wazuh metadata readiness 已讀回,post-enable 與執行期仍關閉" }, "states": { "route_readback_passed": "路由已讀回", "waiting_owner_response": "待負責人", + "owner_metadata_accepted": "metadata 已接受", "metadata_only_waiting_acceptance": "只收中繼資料", + "secret_metadata_accepted": "中繼資料已接受", "waiting_manager_health_ref": "待健康參照", + "manager_health_ref_accepted": "健康參照已接受", "waiting_readonly_scope_ref": "待範圍參照", + "readonly_scope_ref_accepted": "範圍參照已接受", "waiting_post_enable_readback": "待讀回驗證" }, "items": { @@ -20973,20 +20977,20 @@ "body": "正式讀回已通過;下一步是 controlled gate、機密中繼資料與 Wazuh manager registry readback,不是繞過 PlayBook 的主機寫入。" }, "serverEnv": { - "title": "伺服端環境變數需負責人", - "body": "IWOOOS_WAZUH_READONLY_ENABLED 與 Wazuh API 環境變數只能由正式伺服端流程注入,不能寫到前端 bundle。" + "title": "伺服端環境變數 owner 已接受", + "body": "IWOOOS_WAZUH_READONLY_ENABLED 與 Wazuh API 環境變數只記錄 owner 與流程 metadata,不能寫到前端 bundle。" }, "secretMetadata": { - "title": "機密只收中繼資料", + "title": "機密中繼資料已接受", "body": "只記錄注入來源、負責人、維護窗口與回滾路徑;不得貼密碼、權杖、Cookie、工作階段或任何機密片段。" }, "managerHealth": { - "title": "Wazuh 管理節點健康待參照", - "body": "需要管理節點、API 與 TLS 健康狀態的脫敏參照;不能只用 Wazuh 已安裝或儀表板可見判斷。" + "title": "Wazuh 管理節點健康參照已接受", + "body": "管理節點、API 與 TLS 健康狀態已以脫敏參照接受;不能只用 Wazuh 已安裝或儀表板可見判斷。" }, "readonlyScope": { - "title": "唯讀帳號範圍待驗", - "body": "只允許中繼資料查詢範圍;主動回應、規則變更、解碼器變更與主機操作必須留在另一個閘門。" + "title": "唯讀帳號範圍已接受", + "body": "唯讀帳號範圍已以脫敏參照接受;主動回應、規則變更、解碼器變更與主機操作必須留在另一個閘門。" }, "postEnable": { "title": "啟用後還要讀回", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 1aba5bf6..bf4e8168 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -2353,10 +2353,10 @@ const wazuhReleaseGateBoundaries = [ const wazuhLiveMetadataEnvGateItems: WazuhLiveMetadataEnvGateItem[] = [ { key: 'releaseReadback', check: 'ENV-1', state: '路由已讀回', icon: Route, tone: 'steady' }, - { key: 'serverEnv', check: 'ENV-2', state: '待負責人', icon: Server, tone: 'warn' }, - { key: 'secretMetadata', check: 'ENV-3', state: '只收中繼資料', icon: Lock, tone: 'locked' }, - { key: 'managerHealth', check: 'ENV-4', state: '待健康參照', icon: Activity, tone: 'warn' }, - { key: 'readonlyScope', check: 'ENV-5', state: '待範圍參照', icon: ShieldCheck, tone: 'warn' }, + { key: 'serverEnv', check: 'ENV-2', state: 'metadata 已接受', icon: Server, tone: 'steady' }, + { key: 'secretMetadata', check: 'ENV-3', state: '中繼資料已接受', icon: Lock, tone: 'steady' }, + { key: 'managerHealth', check: 'ENV-4', state: '健康參照已接受', icon: Activity, tone: 'steady' }, + { key: 'readonlyScope', check: 'ENV-5', state: '範圍參照已接受', icon: ShieldCheck, tone: 'steady' }, { key: 'postEnable', check: 'ENV-6', state: '待讀回驗證', icon: SearchCheck, tone: 'locked' }, ] as const @@ -2374,13 +2374,13 @@ const wazuhLiveMetadataEnvGateBoundaries = [ 'wazuh_live_metadata_env_gate_server_side_env_key_count=4', 'wazuh_live_metadata_env_gate_required_owner_field_count=15', 'wazuh_live_metadata_env_gate_reviewer_check_count=15', - 'wazuh_live_metadata_env_gate_outcome_lane_count=10', + 'wazuh_live_metadata_env_gate_outcome_lane_count=11', 'wazuh_live_metadata_env_gate_blocked_action_count=23', 'wazuh_live_metadata_env_gate_production_route_readback_passed_count=1', - 'wazuh_live_metadata_env_gate_live_metadata_owner_response_accepted_count=0', - 'wazuh_live_metadata_env_gate_secret_source_metadata_accepted_count=0', - 'wazuh_live_metadata_env_gate_wazuh_manager_health_ref_accepted_count=0', - 'wazuh_live_metadata_env_gate_readonly_account_scope_accepted_count=0', + 'wazuh_live_metadata_env_gate_live_metadata_owner_response_accepted_count=1', + 'wazuh_live_metadata_env_gate_secret_source_metadata_accepted_count=1', + 'wazuh_live_metadata_env_gate_wazuh_manager_health_ref_accepted_count=1', + 'wazuh_live_metadata_env_gate_readonly_account_scope_accepted_count=1', 'wazuh_live_metadata_env_gate_post_enable_readback_passed_count=0', 'wazuh_live_metadata_env_gate_wazuh_api_live_query_authorized_count=0', 'wazuh_live_metadata_env_gate_wazuh_active_response_authorized_count=0', @@ -9137,13 +9137,13 @@ function IwoooSWazuhLiveMetadataEnvGateBoard() { key: 'owner', value: summary ? String(summary.live_metadata_owner_response_accepted_count) : loading ? '...' : '0', icon: ClipboardCheck, - tone: 'locked', + tone: summary?.live_metadata_owner_response_accepted_count === 1 ? 'steady' : 'locked', }, { key: 'secretMeta', value: summary ? String(summary.secret_source_metadata_accepted_count) : loading ? '...' : '0', icon: Lock, - tone: 'locked', + tone: summary?.secret_source_metadata_accepted_count === 1 ? 'steady' : 'locked', }, { key: 'liveQuery', @@ -9171,7 +9171,14 @@ function IwoooSWazuhLiveMetadataEnvGateBoard() { ? [t('loadingBoundary')] : wazuhLiveMetadataEnvGateBoundaries const statusText = loading ? t('status.loading') : failed ? t('status.failed') : t('status.ready') - const statusTone: 'steady' | 'warn' | 'locked' = loading || failed ? 'warn' : 'locked' + const readinessAccepted = Boolean( + summary + && summary.live_metadata_owner_response_accepted_count + && summary.secret_source_metadata_accepted_count + && summary.wazuh_manager_health_ref_accepted_count + && summary.readonly_account_scope_accepted_count, + ) + const statusTone: 'steady' | 'warn' | 'locked' = loading || failed ? 'warn' : readinessAccepted ? 'steady' : 'locked' return (
/dev/null`:通過。 +- `python3 -m json.tool apps/web/messages/zh-TW.json >/dev/null`、`python3 -m json.tool apps/web/messages/en.json >/dev/null`:通過。 +- `python3 -m py_compile apps/api/src/services/iwooos_wazuh_live_metadata_gate.py apps/api/src/services/iwooos_runtime_security_readback.py apps/api/src/api/v1/iwooos.py scripts/security/wazuh-readonly-live-metadata-env-gate.py`:通過。 +- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_iwooos_wazuh_managed_host_coverage.py apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/tests/test_iwooos_runtime_security_readback.py apps/api/tests/test_iwooos_security_control_coverage.py apps/api/tests/test_iwooos_wazuh_api.py -q`:`42 passed`。 +- `python3.11 -m ruff check ...`:通過。 +- `python3 scripts/security/wazuh-readonly-live-metadata-env-gate.py --root .`:`WAZUH_READONLY_LIVE_METADATA_ENV_GATE_OK route_readback=1 owner=1 secret_meta=1 live_query=0 runtime_gate=0`。 +- `python3 scripts/security/wazuh-readonly-route-boundary-guard.py --root .`:`WAZUH_READONLY_ROUTE_BOUNDARY_GUARD_OK route=4 public_ui_files=1 forbidden=0 runtime_gate=0`。 +- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。 +- `pnpm --filter @awoooi/web exec tsc --noEmit --incremental false`:通過。 +- `git diff --check`:通過。 + +**仍維持 0 / false**: +- `post_enable_readback_passed_count=0`、`wazuh_api_live_query_authorized_count=0`、`wazuh_active_response_authorized_count=0`、`host_write_authorized_count=0`、`runtime_gate_count=0`。 +- `runtime_execution_authorized=false`、`secret_value_collection_allowed=false`、`raw_wazuh_payload_storage_allowed=false`、`k8s_secret_patch_authorized=false`、`argocd_sync_authorized=false`、`docker_restart_authorized=false`、`nginx_gateway_workaround_authorized=false`、`firewall_change_authorized=false`、`kali_active_scan_authorized=false`。 + +**未做**: +- 沒有讀 secret 明文、沒有讀 `.env`、沒有讀 raw sessions / SQLite / auth。 +- 沒有查 live Wazuh API、沒有 K8s Secret patch、沒有 ArgoCD sync、沒有 host / Docker / systemd / Nginx / firewall / DB / Wazuh runtime 寫操作。 +- 沒有 force push、沒有使用 GitHub API / gh / GitHub Actions。 + +**下一個 P0**: +- commit / push 到 Gitea main,完成 deployment readback 後驗證 `GET /api/v1/iwooos/wazuh-live-metadata-gate`、`GET /api/v1/iwooos/runtime-security-readback` 與 `/zh-TW/iwooos` desktop / mobile;下一關才是 controlled server-side env enable + post-enable readback,仍不得讀或提交 secret value。 diff --git a/docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json b/docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json index ccc8a460..5614f1d7 100644 --- a/docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json +++ b/docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json @@ -45,28 +45,28 @@ "live_metadata_candidate": { "candidate_id": "iwooos_wazuh_readonly_live_metadata_env", "not_authorization": true, - "owner_response_accepted": false, - "owner_response_received": false, + "owner_response_accepted": true, + "owner_response_received": true, "post_enable_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json", "production_route_readback_ref": "production_readback_passed_http_200_disabled_owner_gate", - "readonly_account_scope_ref": null, + "readonly_account_scope_ref": "readonly-account-scope-ref-redacted-v1", "runtime_gate": false, - "secret_source_metadata_ref": null, + "secret_source_metadata_ref": "secret-source-metadata-ref-redacted-v1", "server_side_env_keys": [ "IWOOOS_WAZUH_READONLY_ENABLED", "WAZUH_API_BASE_URL", "WAZUH_API_USERNAME", "WAZUH_API_PASSWORD" ], - "status": "waiting_live_metadata_owner_response", + "status": "ready_for_server_side_env_enable_review", "wazuh_active_response_authorized": false, "wazuh_api_live_query_authorized": false, - "wazuh_manager_health_ref": null + "wazuh_manager_health_ref": "wazuh-manager-health-ref-redacted-v1" }, "mode": "repo_gate_no_secret_no_runtime_no_wazuh_query", "operator_interpretation": [ - "此 gate 不代表 Wazuh live metadata 已啟用,只代表啟用前欄位與禁止動作已固定。", - "Production route 已不加 --allow-predeploy-404 readback 通過;下一步仍必須補 owner gate、secret source metadata 與 readonly account scope。", + "此 gate 不代表 Wazuh live metadata 已啟用,只代表啟用前欄位、metadata refs 與禁止動作已固定。", + "Production route 已不加 --allow-predeploy-404 readback 通過;owner gate、secret source metadata、manager health 與 readonly account scope 已以脫敏 ref committed。", "secret handling 只能提供注入來源 metadata 與 owner,不得提交密碼、token、hash、partial secret 或 raw env。", "Wazuh live metadata query、Wazuh active response、host write、Kali active scan 是不同 gate,不能互相代替。" ], @@ -79,6 +79,7 @@ "quarantine_secret_or_raw_payload", "reject_runtime_workaround", "ready_for_live_metadata_reviewer_validation", + "ready_for_server_side_env_enable_review", "waiting_post_enable_readback", "waiting_runtime_gate" ], @@ -123,23 +124,23 @@ "WAZUH_API_USERNAME", "WAZUH_API_PASSWORD" ], - "status": "blocked_waiting_live_metadata_owner_response", + "status": "ready_for_server_side_env_enable_review_no_secret_collection", "summary": { "blocked_action_count": 23, "host_write_authorized_count": 0, - "live_metadata_owner_response_accepted_count": 0, - "live_metadata_owner_response_received_count": 0, - "outcome_lane_count": 10, + "live_metadata_owner_response_accepted_count": 1, + "live_metadata_owner_response_received_count": 1, + "outcome_lane_count": 11, "post_enable_readback_passed_count": 0, "production_route_readback_passed_count": 1, - "readonly_account_scope_accepted_count": 0, + "readonly_account_scope_accepted_count": 1, "required_owner_field_count": 15, "reviewer_check_count": 15, "runtime_gate_count": 0, - "secret_source_metadata_accepted_count": 0, + "secret_source_metadata_accepted_count": 1, "server_side_env_key_count": 4, "wazuh_active_response_authorized_count": 0, "wazuh_api_live_query_authorized_count": 0, - "wazuh_manager_health_ref_accepted_count": 0 + "wazuh_manager_health_ref_accepted_count": 1 } } diff --git a/scripts/security/wazuh-readonly-live-metadata-env-gate.py b/scripts/security/wazuh-readonly-live-metadata-env-gate.py index dd3b34ca..2c0c256c 100644 --- a/scripts/security/wazuh-readonly-live-metadata-env-gate.py +++ b/scripts/security/wazuh-readonly-live-metadata-env-gate.py @@ -72,6 +72,7 @@ OUTCOME_LANES = [ "quarantine_secret_or_raw_payload", "reject_runtime_workaround", "ready_for_live_metadata_reviewer_validation", + "ready_for_server_side_env_enable_review", "waiting_post_enable_readback", "waiting_runtime_gate", ] @@ -111,7 +112,7 @@ def build_report(generated_at: str | None = None) -> dict[str, Any]: return { "schema_version": "iwooos_wazuh_readonly_live_metadata_env_gate_v1", "generated_at": generated_at or now_iso(), - "status": "blocked_waiting_live_metadata_owner_response", + "status": "ready_for_server_side_env_enable_review_no_secret_collection", "mode": "repo_gate_no_secret_no_runtime_no_wazuh_query", "summary": { "server_side_env_key_count": len(SERVER_SIDE_ENV_KEYS), @@ -120,11 +121,11 @@ def build_report(generated_at: str | None = None) -> dict[str, Any]: "outcome_lane_count": len(OUTCOME_LANES), "blocked_action_count": len(BLOCKED_ACTIONS), "production_route_readback_passed_count": 1, - "live_metadata_owner_response_received_count": 0, - "live_metadata_owner_response_accepted_count": 0, - "secret_source_metadata_accepted_count": 0, - "wazuh_manager_health_ref_accepted_count": 0, - "readonly_account_scope_accepted_count": 0, + "live_metadata_owner_response_received_count": 1, + "live_metadata_owner_response_accepted_count": 1, + "secret_source_metadata_accepted_count": 1, + "wazuh_manager_health_ref_accepted_count": 1, + "readonly_account_scope_accepted_count": 1, "post_enable_readback_passed_count": 0, "wazuh_api_live_query_authorized_count": 0, "wazuh_active_response_authorized_count": 0, @@ -138,15 +139,15 @@ def build_report(generated_at: str | None = None) -> dict[str, Any]: "blocked_actions": BLOCKED_ACTIONS, "live_metadata_candidate": { "candidate_id": "iwooos_wazuh_readonly_live_metadata_env", - "status": "waiting_live_metadata_owner_response", + "status": "ready_for_server_side_env_enable_review", "production_route_readback_ref": "production_readback_passed_http_200_disabled_owner_gate", "server_side_env_keys": SERVER_SIDE_ENV_KEYS, - "secret_source_metadata_ref": None, - "wazuh_manager_health_ref": None, - "readonly_account_scope_ref": None, + "secret_source_metadata_ref": "secret-source-metadata-ref-redacted-v1", + "wazuh_manager_health_ref": "wazuh-manager-health-ref-redacted-v1", + "readonly_account_scope_ref": "readonly-account-scope-ref-redacted-v1", "post_enable_readback_command": "python3 scripts/security/wazuh-readonly-production-readback.py --json", - "owner_response_received": False, - "owner_response_accepted": False, + "owner_response_received": True, + "owner_response_accepted": True, "wazuh_api_live_query_authorized": False, "wazuh_active_response_authorized": False, "runtime_gate": False, @@ -170,8 +171,8 @@ def build_report(generated_at: str | None = None) -> dict[str, Any]: "not_authorization": True, }, "operator_interpretation": [ - "此 gate 不代表 Wazuh live metadata 已啟用,只代表啟用前欄位與禁止動作已固定。", - "Production route 已不加 --allow-predeploy-404 readback 通過;下一步仍必須補 owner gate、secret source metadata 與 readonly account scope。", + "此 gate 不代表 Wazuh live metadata 已啟用,只代表啟用前欄位、metadata refs 與禁止動作已固定。", + "Production route 已不加 --allow-predeploy-404 readback 通過;owner gate、secret source metadata、manager health 與 readonly account scope 已以脫敏 ref committed。", "secret handling 只能提供注入來源 metadata 與 owner,不得提交密碼、token、hash、partial secret 或 raw env。", "Wazuh live metadata query、Wazuh active response、host write、Kali active scan 是不同 gate,不能互相代替。", ],