feat(iwooos): expose wazuh manager registry reviewer validation
Some checks failed
Code Review / ai-code-review (push) Successful in 16s
CD Pipeline / tests (push) Successful in 1m37s
CD Pipeline / build-and-deploy (push) Successful in 4m43s
CD Pipeline / post-deploy-checks (push) Has been cancelled
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 15:32:32 +08:00
parent 2278fd6c99
commit 5f5a171edd
11 changed files with 1610 additions and 0 deletions

View File

@@ -35,6 +35,9 @@ from src.services.iwooos_wazuh_live_metadata_gate import (
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_owner_evidence_preflight import (
load_latest_iwooos_wazuh_owner_evidence_preflight,
)
@@ -147,6 +150,34 @@ async def get_iwooos_wazuh_managed_host_coverage() -> dict[str, Any]:
) from exc
@router.get(
"/api/v1/iwooos/wazuh-manager-registry-reviewer-validation",
response_model=dict[str, Any],
summary="取得 Wazuh manager registry reviewer validation 只讀讀回",
description=(
"讀取已提交的 Wazuh manager registry reviewer validation contract回傳 owner export "
"必要欄位、reviewer 檢查、evidence slots、結果分流、拒收內容與 0 / false 邊界。"
"此端點不收 raw payload、不查 Wazuh API、不讀主機、不重新註冊 agent、不重啟服務、"
"不保存機密、不啟用主動回應、不改 Nginx / Docker / K8s / firewall。"
),
)
async def get_iwooos_wazuh_manager_registry_reviewer_validation() -> dict[str, Any]:
"""回傳 Wazuh manager registry reviewer validation 公開安全只讀狀態。"""
try:
payload = await asyncio.to_thread(load_latest_iwooos_wazuh_manager_registry_reviewer_validation)
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 Wazuh manager registry reviewer validation 無效:{exc}",
) from exc
@router.get(
"/api/v1/iwooos/runtime-security-readback",
response_model=dict[str, Any],

View File

@@ -0,0 +1,227 @@
"""
IwoooS Wazuh manager registry reviewer validation readback.
This service exposes a committed reviewer-validation contract for future
owner-provided redacted Wazuh manager registry exports. It never receives raw
payloads, queries Wazuh, reads host data, reads secrets, or authorizes runtime
actions.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from src.services.snapshot_paths import default_security_dir
_DEFAULT_SECURITY_DIR = default_security_dir(Path(__file__))
_SNAPSHOT_FILE = "wazuh-manager-registry-reviewer-validation.snapshot.json"
_EXPECTED_SCHEMA = "wazuh_manager_registry_reviewer_validation_v1"
_REQUIRED_FALSE_BOUNDARIES = {
"agent_identity_public_display_allowed",
"host_write_authorized",
"internal_ip_public_display_allowed",
"kali_active_scan_authorized",
"raw_wazuh_payload_storage_allowed",
"runtime_execution_authorized",
"secret_value_collection_allowed",
"wazuh_active_response_authorized",
"wazuh_agent_reenroll_authorized",
"wazuh_agent_restart_authorized",
"wazuh_api_live_query_authorized",
"wazuh_manager_restart_authorized",
}
def load_latest_iwooos_wazuh_manager_registry_reviewer_validation(
security_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the public-safe Wazuh manager registry reviewer-validation contract."""
directory = security_dir or _DEFAULT_SECURITY_DIR
snapshot = _load_snapshot(directory)
_require_boundaries(snapshot)
summary = _summary(snapshot)
merged_summary = {
"expected_scope_alias_count": _int(summary.get("expected_scope_alias_count")),
"required_owner_field_count": _int(summary.get("required_owner_field_count")),
"per_host_required_field_count": _int(summary.get("per_host_required_field_count")),
"reviewer_validation_check_count": _int(summary.get("reviewer_validation_check_count")),
"outcome_lane_count": _int(summary.get("outcome_lane_count")),
"evidence_slot_count": _int(summary.get("evidence_slot_count")),
"forbidden_payload_count": _int(summary.get("forbidden_payload_count")),
"forbidden_action_count": _int(summary.get("forbidden_action_count")),
"owner_registry_export_received_count": _int(summary.get("owner_registry_export_received_count")),
"owner_registry_export_accepted_count": _int(summary.get("owner_registry_export_accepted_count")),
"reviewer_validation_ready_count": _int(summary.get("reviewer_validation_ready_count")),
"reviewer_validation_passed_count": _int(summary.get("reviewer_validation_passed_count")),
"reviewer_validation_failed_count": _int(summary.get("reviewer_validation_failed_count")),
"reviewer_validation_quarantined_count": _int(summary.get("reviewer_validation_quarantined_count")),
"manager_registry_accepted_count": _int(summary.get("manager_registry_accepted_count")),
"post_enable_readback_passed_count": _int(summary.get("post_enable_readback_passed_count")),
"runtime_gate_count": _int(summary.get("runtime_gate_count")),
"host_write_authorized_count": _int(summary.get("host_write_authorized_count")),
"active_response_authorized_count": _int(summary.get("active_response_authorized_count")),
"secret_value_collection_allowed_count": _int(summary.get("secret_value_collection_allowed_count")),
}
return {
"schema_version": "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1",
"source_schema_version": snapshot["schema_version"],
"status": snapshot.get("status", "waiting_owner_registry_export_for_reviewer_validation"),
"mode": "committed_validation_contract_readback_no_runtime_no_secret_collection",
"source_refs": [
f"docs/security/{_SNAPSHOT_FILE}",
"scripts/security/wazuh-manager-registry-reviewer-validation.py",
],
"summary": merged_summary,
"expected_scope_aliases": _strings(snapshot.get("expected_scope_aliases")),
"reviewer_validation_checks": _checks(snapshot.get("reviewer_validation_checks")),
"outcome_lanes": _strings(snapshot.get("outcome_lanes")),
"evidence_slots": _evidence_slots(snapshot.get("evidence_slots")),
"forbidden_payloads": _strings(snapshot.get("forbidden_payloads")),
"forbidden_actions": _strings(snapshot.get("forbidden_actions")),
"boundary_markers": _boundary_markers(merged_summary),
"boundaries": {
"wazuh_api_live_query_authorized": False,
"wazuh_agent_reenroll_authorized": False,
"wazuh_agent_restart_authorized": False,
"wazuh_manager_restart_authorized": False,
"wazuh_active_response_authorized": False,
"host_write_authorized": False,
"secret_value_collection_allowed": False,
"raw_wazuh_payload_storage_allowed": False,
"agent_identity_public_display_allowed": False,
"internal_ip_public_display_allowed": False,
"kali_active_scan_authorized": False,
"runtime_execution_authorized": False,
"not_authorization": True,
},
"no_false_green_rules": _strings(snapshot.get("no_false_green_rules")),
}
def _load_snapshot(directory: Path) -> dict[str, Any]:
path = directory / _SNAPSHOT_FILE
if not path.is_file():
raise FileNotFoundError(f"{path}: Wazuh manager registry reviewer validation 快照不存在")
with path.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{path}: expected JSON object")
if payload.get("schema_version") != _EXPECTED_SCHEMA:
raise ValueError(f"{path}: expected schema_version={_EXPECTED_SCHEMA}")
return payload
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 _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [item for item in value if isinstance(item, str)]
def _checks(value: Any) -> list[dict[str, str]]:
if not isinstance(value, list):
return []
checks: list[dict[str, str]] = []
for item in value:
if not isinstance(item, dict):
continue
checks.append(
{
"check_id": str(item.get("check_id", "")),
"title": str(item.get("title", "")),
"required_evidence": str(item.get("required_evidence", "")),
"failure_lane": str(item.get("failure_lane", "")),
}
)
return checks
def _evidence_slots(value: Any) -> list[dict[str, Any]]:
if not isinstance(value, list):
return []
slots: list[dict[str, Any]] = []
for item in value:
if not isinstance(item, dict):
continue
slots.append(
{
"slot_id": str(item.get("slot_id", "")),
"title": str(item.get("title", "")),
"required_fields": _strings(item.get("required_fields")),
"received": item.get("received") is True,
"accepted": item.get("accepted") is True,
"quarantined": item.get("quarantined") is True,
"next_gate": str(item.get("next_gate", "")),
}
)
return slots
def _boundary_markers(summary: dict[str, int]) -> list[str]:
return [
"wazuh_manager_registry_reviewer_validation_visible=true",
f"wazuh_manager_registry_reviewer_validation_expected_scope_alias_count={summary['expected_scope_alias_count']}",
f"wazuh_manager_registry_reviewer_validation_required_owner_field_count={summary['required_owner_field_count']}",
f"wazuh_manager_registry_reviewer_validation_per_host_required_field_count={summary['per_host_required_field_count']}",
f"wazuh_manager_registry_reviewer_validation_check_count={summary['reviewer_validation_check_count']}",
f"wazuh_manager_registry_reviewer_validation_outcome_lane_count={summary['outcome_lane_count']}",
f"wazuh_manager_registry_reviewer_validation_evidence_slot_count={summary['evidence_slot_count']}",
f"wazuh_manager_registry_reviewer_validation_forbidden_payload_count={summary['forbidden_payload_count']}",
f"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count={summary['owner_registry_export_received_count']}",
f"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count={summary['owner_registry_export_accepted_count']}",
f"wazuh_manager_registry_reviewer_validation_passed_count={summary['reviewer_validation_passed_count']}",
f"wazuh_manager_registry_reviewer_validation_quarantined_count={summary['reviewer_validation_quarantined_count']}",
f"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count={summary['manager_registry_accepted_count']}",
f"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count={summary['post_enable_readback_passed_count']}",
f"wazuh_manager_registry_reviewer_validation_runtime_gate_count={summary['runtime_gate_count']}",
"wazuh_api_live_query_authorized=false",
"wazuh_agent_reenroll_authorized=false",
"wazuh_agent_restart_authorized=false",
"wazuh_active_response_authorized=false",
"host_write_authorized=false",
"raw_wazuh_payload_storage_allowed=false",
"secret_value_collection_allowed=false",
"not_authorization=true",
]
def _require_boundaries(payload: dict[str, Any]) -> None:
summary = _summary(payload)
for key in (
"owner_registry_export_received_count",
"owner_registry_export_accepted_count",
"reviewer_validation_ready_count",
"reviewer_validation_passed_count",
"reviewer_validation_failed_count",
"reviewer_validation_quarantined_count",
"manager_registry_accepted_count",
"post_enable_readback_passed_count",
"runtime_gate_count",
"host_write_authorized_count",
"active_response_authorized_count",
"secret_value_collection_allowed_count",
):
if _int(summary.get(key)) != 0:
raise ValueError(f"Wazuh manager registry reviewer validation summary.{key} 必須維持 0")
boundaries = payload.get("execution_boundaries")
if not isinstance(boundaries, dict):
raise ValueError("Wazuh manager registry reviewer validation execution_boundaries 缺失")
for key in _REQUIRED_FALSE_BOUNDARIES:
if boundaries.get(key) is not False:
raise ValueError(f"Wazuh manager registry reviewer validation execution_boundaries.{key} 必須維持 false")
if boundaries.get("not_authorization") is not True:
raise ValueError("Wazuh manager registry reviewer validation not_authorization 必須維持 true")

View File

@@ -0,0 +1,96 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.iwooos import router
from src.services.iwooos_wazuh_manager_registry_reviewer_validation import (
load_latest_iwooos_wazuh_manager_registry_reviewer_validation,
)
def _client() -> TestClient:
app = FastAPI()
app.include_router(router)
return TestClient(app)
def test_iwooos_wazuh_manager_registry_reviewer_validation_contract_is_waiting_only() -> None:
payload = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
assert payload["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
assert payload["source_schema_version"] == "wazuh_manager_registry_reviewer_validation_v1"
assert payload["status"] == "waiting_owner_registry_export_for_reviewer_validation"
assert payload["mode"] == "committed_validation_contract_readback_no_runtime_no_secret_collection"
assert payload["summary"]["expected_scope_alias_count"] == 6
assert payload["summary"]["required_owner_field_count"] == 28
assert payload["summary"]["per_host_required_field_count"] == 9
assert payload["summary"]["reviewer_validation_check_count"] == 10
assert payload["summary"]["outcome_lane_count"] == 13
assert payload["summary"]["evidence_slot_count"] == 6
assert payload["summary"]["forbidden_payload_count"] == 27
assert payload["summary"]["owner_registry_export_received_count"] == 0
assert payload["summary"]["owner_registry_export_accepted_count"] == 0
assert payload["summary"]["reviewer_validation_passed_count"] == 0
assert payload["summary"]["reviewer_validation_quarantined_count"] == 0
assert payload["summary"]["manager_registry_accepted_count"] == 0
assert payload["summary"]["runtime_gate_count"] == 0
def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_closed() -> None:
payload = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
assert [item["slot_id"] for item in payload["evidence_slots"]] == [
"manager_registry_agent_counts",
"per_host_agent_scope_matrix",
"dashboard_api_rbac_tls_repair_readback",
"readonly_credential_metadata_without_secret",
"owner_response_and_rollback_owner",
"post_enable_iwooos_readback",
]
assert all(item["received"] is False for item in payload["evidence_slots"])
assert all(item["accepted"] is False for item in payload["evidence_slots"])
assert all(item["quarantined"] is False for item in payload["evidence_slots"])
assert "managed_core_node_a" in payload["expected_scope_aliases"]
assert "manager_registry_agent_counts" in [item["slot_id"] for item in payload["evidence_slots"]]
def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe() -> None:
response = _client().get("/api/v1/iwooos/wazuh-manager-registry-reviewer-validation")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
assert data["summary"]["owner_registry_export_received_count"] == 0
assert data["summary"]["owner_registry_export_accepted_count"] == 0
assert data["summary"]["manager_registry_accepted_count"] == 0
assert data["summary"]["runtime_gate_count"] == 0
assert len(data["reviewer_validation_checks"]) == 10
assert len(data["evidence_slots"]) == 6
assert any(
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0"
for marker in data["boundary_markers"]
)
assert any(
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0"
for marker in data["boundary_markers"]
)
assert any(
marker == "wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0"
for marker in data["boundary_markers"]
)
assert any(
marker == "wazuh_manager_registry_reviewer_validation_runtime_gate_count=0"
for marker in data["boundary_markers"]
)
assert any(rule.startswith("reviewer validation contract 可見") for rule in data["no_false_green_rules"])
assert data["boundaries"]["runtime_execution_authorized"] is False
assert data["boundaries"]["wazuh_active_response_authorized"] is False
assert data["boundaries"]["host_write_authorized"] is False
assert data["boundaries"]["not_authorization"] is True
assert "192.168.0." not in response.text
assert "工作視窗" not in response.text
assert "批准!繼續" not in response.text
assert "source_thread_id" not in response.text
assert "owenhytsai/" not in response.text
assert "WAZUH_API_PASSWORD" not in response.text