fix(iwooos): count alertmanager receipts in security readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m28s
CD Pipeline / build-and-deploy (push) Successful in 5m25s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
ogt
2026-07-09 22:36:09 +08:00
parent dced1be755
commit 13faee8fd4
3 changed files with 115 additions and 4 deletions

View File

@@ -74,6 +74,7 @@ from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
validate_iwooos_wazuh_runtime_gate_owner_review_packet as validate_wazuh_runtime_gate_owner_review_packet_payload,
)
from src.services.platform_operator_service import list_recent_channel_events
from src.services.public_redaction import redact_public_lan_topology
router = APIRouter(tags=["IwoooS Security"])
@@ -84,6 +85,25 @@ async def _wazuh_readonly_status() -> JSONResponse:
return JSONResponse(status_code=result.http_status, content=result.payload)
async def _alertmanager_receipt_readback() -> dict[str, Any]:
try:
payload = await list_recent_channel_events(
project_id="awoooi",
channel_type=None,
provider_prefix="alertmanager",
limit=20,
)
return {**payload, "source_status": payload.get("source_status", "ready")}
except Exception as exc:
return {
"events": [],
"total": 0,
"limit": 20,
"source_status": "source_unavailable",
"source_error": exc.__class__.__name__,
}
@router.get("/api/iwooos/wazuh")
async def get_iwooos_wazuh_readonly_status_compat() -> JSONResponse:
return await _wazuh_readonly_status()
@@ -108,7 +128,11 @@ async def get_iwooos_wazuh_readonly_status_v1() -> JSONResponse:
async def get_iwooos_security_operating_system() -> dict[str, Any]:
"""回傳 IwoooS 資安作業系統公開安全總入口。"""
try:
payload = await asyncio.to_thread(load_latest_iwooos_security_operating_system)
alert_receipt_readback = await _alertmanager_receipt_readback()
payload = await asyncio.to_thread(
load_latest_iwooos_security_operating_system,
alert_receipt_readback=alert_receipt_readback,
)
return redact_public_lan_topology(payload)
except FileNotFoundError as exc:
raise HTTPException(

View File

@@ -156,6 +156,7 @@ _RUNTIME_ACTION_KEYS = {
def load_latest_iwooos_security_operating_system(
security_dir: Path | None = None,
alert_receipt_readback: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Load the committed IwoooS security operating system contract."""
directory = security_dir or _DEFAULT_SECURITY_DIR
@@ -163,6 +164,7 @@ def load_latest_iwooos_security_operating_system(
_require_boundaries(snapshot)
summary = _summary(snapshot)
alert_receipt_summary = _alert_receipt_summary(alert_receipt_readback)
merged_summary = {
"reference_framework_count": _int(summary.get("reference_framework_count")),
"operating_role_count": _int(summary.get("operating_role_count")),
@@ -192,9 +194,13 @@ def load_latest_iwooos_security_operating_system(
"wazuh_registry_accepted_count": _int(
summary.get("wazuh_registry_accepted_count")
),
"alert_receipt_accepted_count": _int(
summary.get("alert_receipt_accepted_count")
"alert_receipt_accepted_count": max(
_int(summary.get("alert_receipt_accepted_count")),
alert_receipt_summary["accepted_count"],
),
"alert_receipt_observed_count": alert_receipt_summary["observed_count"],
"alert_receipt_source_status": alert_receipt_summary["source_status"],
"alert_receipt_source_error": alert_receipt_summary["source_error"],
"incident_case_accepted_count": _int(
summary.get("incident_case_accepted_count")
),
@@ -461,6 +467,37 @@ def _int(value: Any) -> int:
return value if isinstance(value, int) else 0
def _alert_receipt_summary(readback: dict[str, Any] | None) -> dict[str, Any]:
if not isinstance(readback, dict):
return {
"accepted_count": 0,
"observed_count": 0,
"source_status": "not_connected",
"source_error": None,
}
source_status = str(readback.get("source_status") or "ready")
source_error = readback.get("source_error")
events = readback.get("events")
total = readback.get("total")
observed_count = (
_int(total)
if total is not None
else len(events)
if isinstance(events, list)
else 0
)
accepted_count = (
observed_count if source_status == "ready" and observed_count > 0 else 0
)
return {
"accepted_count": accepted_count,
"observed_count": observed_count,
"source_status": source_status,
"source_error": str(source_error) if source_error else None,
}
def _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
@@ -612,7 +649,7 @@ def _checkpoints(value: Any) -> list[dict[str, Any]]:
return items
def _boundary_markers(summary: dict[str, int]) -> list[str]:
def _boundary_markers(summary: dict[str, Any]) -> list[str]:
return [
"iwooos_security_operating_system_api_visible=true",
"iwooos_security_operation_packet_validation_api_available=true",
@@ -627,6 +664,8 @@ def _boundary_markers(summary: dict[str, int]) -> list[str]:
f"iwooos_security_operating_system_operation_packet_required_field_count={summary['operation_packet_required_field_count']}",
f"iwooos_security_operating_system_evidence_weighted_percent={summary['evidence_weighted_security_operating_system_percent']}",
f"iwooos_security_operating_system_wazuh_registry_accepted_count={summary['wazuh_registry_accepted_count']}",
f"iwooos_security_operating_system_alert_receipt_accepted_count={summary['alert_receipt_accepted_count']}",
f"iwooos_security_operating_system_alert_receipt_observed_count={summary['alert_receipt_observed_count']}",
"iwooos_security_operating_system_operation_packet_validation_no_persist=true",
"iwooos_security_operating_system_runtime_gate_count=0",
"runtime_execution_authorized=false",

View File

@@ -71,6 +71,9 @@ def test_iwooos_security_operating_system_readback_exposes_api_validator() -> No
assert payload["summary"]["operation_packet_validator_available_count"] == 1
assert payload["summary"]["operation_packet_required_field_count"] == 24
assert payload["summary"]["wazuh_registry_accepted_count"] == 6
assert payload["summary"]["alert_receipt_accepted_count"] == 0
assert payload["summary"]["alert_receipt_observed_count"] == 0
assert payload["summary"]["alert_receipt_source_status"] == "not_connected"
assert payload["summary"]["runtime_gate_count"] == 0
assert payload["operation_packet_validation_endpoint"] == (
"/api/v1/iwooos/security-operating-system/validate-operation-packet"
@@ -87,6 +90,26 @@ def test_iwooos_security_operating_system_readback_exposes_api_validator() -> No
)
def test_iwooos_security_operating_system_accepts_alert_receipt_readback() -> None:
payload = load_latest_iwooos_security_operating_system(
alert_receipt_readback={
"events": [{"provider_event_id": "alertmanager:received:alert-a"}],
"total": 1,
"limit": 20,
"source_status": "ready",
}
)
assert payload["summary"]["alert_receipt_accepted_count"] == 1
assert payload["summary"]["alert_receipt_observed_count"] == 1
assert payload["summary"]["alert_receipt_source_status"] == "ready"
assert payload["summary"]["runtime_gate_count"] == 0
assert any(
marker == "iwooos_security_operating_system_alert_receipt_accepted_count=1"
for marker in payload["boundary_markers"]
)
def test_iwooos_security_operating_system_api_is_public_safe() -> None:
response = _client().get("/api/v1/iwooos/security-operating-system")
@@ -103,6 +126,31 @@ def test_iwooos_security_operating_system_api_is_public_safe() -> None:
assert "WAZUH_API_PASSWORD" not in response.text
def test_iwooos_security_operating_system_api_counts_alertmanager_receipts(
monkeypatch,
) -> None:
async def fake_recent_events(**_: object) -> dict[str, object]:
return {
"events": [{"provider_event_id": "alertmanager:received:alert-a"}],
"total": 1,
"limit": 20,
}
monkeypatch.setattr(
"src.api.v1.iwooos.list_recent_channel_events",
fake_recent_events,
)
response = _client().get("/api/v1/iwooos/security-operating-system")
assert response.status_code == 200
data = response.json()
assert data["summary"]["alert_receipt_accepted_count"] == 1
assert data["summary"]["alert_receipt_observed_count"] == 1
assert data["summary"]["alert_receipt_source_status"] == "ready"
assert data["summary"]["runtime_gate_count"] == 0
def test_iwooos_security_operation_packet_validator_accepts_redacted_loop() -> None:
payload = validate_iwooos_security_operation_packet(_valid_operation_packet())