Merge remote-tracking branch 'origin/main' into codex/sre-typed-automation-20260715
# Conflicts: # apps/api/tests/test_executor_trust_boundary_readback.py
This commit is contained in:
@@ -16,6 +16,7 @@ from src.services.ai_agent_autonomous_runtime_control import (
|
||||
_RUNTIME_TIMELINE_COUNTS_SQL,
|
||||
_runtime_stage_receipt_has_required_proof,
|
||||
build_ai_agent_autonomous_runtime_control,
|
||||
build_ai_agent_strict_runtime_completion_from_readback,
|
||||
build_runtime_receipt_readback_from_rows,
|
||||
classify_deploy_control_plane_observation,
|
||||
)
|
||||
@@ -1882,6 +1883,9 @@ def test_ai_agent_autonomous_runtime_control_uses_current_owner_directive():
|
||||
assert strict["metadata_only_counts_as_completion"] is False
|
||||
assert strict["historical_aggregate_fallback_allowed"] is False
|
||||
assert len(strict["stage_contracts"]) == len(AI_AUTOMATION_REQUIRED_LOOP_STAGES)
|
||||
assert build_ai_agent_strict_runtime_completion_from_readback(
|
||||
data["runtime_receipt_readback"]
|
||||
) == strict
|
||||
assert data["current_policy"]["low_risk_controlled_apply_allowed"] is True
|
||||
assert data["current_policy"]["medium_risk_controlled_apply_allowed"] is True
|
||||
assert data["current_policy"]["high_risk_controlled_apply_allowed"] is True
|
||||
|
||||
@@ -349,8 +349,8 @@ def test_executor_trust_boundary_requires_broker_secret_isolation() -> None:
|
||||
|
||||
def test_executor_trust_boundary_requires_live_connection_headroom() -> None:
|
||||
receipt = _verified_receipt()
|
||||
receipt["worker_active_role_connection_count"] = "7"
|
||||
receipt["worker_available_connection_headroom"] = "1"
|
||||
receipt["worker_active_role_connection_count"] = "8"
|
||||
receipt["worker_available_connection_headroom"] = "0"
|
||||
|
||||
readback = build_executor_trust_boundary_readback(
|
||||
receipt,
|
||||
@@ -382,6 +382,24 @@ def test_executor_trust_boundary_requires_api_null_pool_concurrency_cap() -> Non
|
||||
)
|
||||
|
||||
|
||||
def test_executor_trust_boundary_does_not_double_count_used_budget() -> None:
|
||||
receipt = _verified_receipt()
|
||||
receipt["worker_active_role_connection_count"] = "3"
|
||||
receipt["worker_available_connection_headroom"] = "5"
|
||||
|
||||
readback = build_executor_trust_boundary_readback(
|
||||
receipt,
|
||||
deployed_source_sha="abc123",
|
||||
)
|
||||
|
||||
database_boundary = readback["database_identity_boundary"]
|
||||
assert readback["full_workload_boundary_verified"] is True
|
||||
assert database_boundary["connection_budget_verified"] is True
|
||||
assert "worker_connection_budget_not_verified" not in (
|
||||
database_boundary["active_blockers"]
|
||||
)
|
||||
|
||||
|
||||
def test_executor_trust_boundary_binds_budget_and_verifier_contract() -> None:
|
||||
wrong_budget = _verified_receipt()
|
||||
wrong_budget["worker_required_connection_budget"] = "4"
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
# ruff: noqa: E402, I001
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
from datetime import UTC, datetime, timedelta
|
||||
@@ -20,12 +21,51 @@ from src.services.iwooos_security_asset_control_plane import (
|
||||
build_iwooos_security_asset_control_plane,
|
||||
build_unavailable_iwooos_security_asset_control_plane,
|
||||
)
|
||||
from src.services.ai_automation_runtime_contract import (
|
||||
AI_AUTOMATION_REQUIRED_LOOP_STAGES,
|
||||
AI_AUTOMATION_RUNTIME_CONTRACT_SCHEMA_VERSION,
|
||||
)
|
||||
|
||||
|
||||
def _row(**values):
|
||||
return SimpleNamespace(**values)
|
||||
|
||||
|
||||
def _closed_strict_runtime() -> dict:
|
||||
return {
|
||||
"schema_version": "ai_agent_strict_runtime_completion_v2",
|
||||
"runtime_contract_schema_version": (
|
||||
AI_AUTOMATION_RUNTIME_CONTRACT_SCHEMA_VERSION
|
||||
),
|
||||
"completion_percent": 100,
|
||||
"required_stage_count": len(AI_AUTOMATION_REQUIRED_LOOP_STAGES),
|
||||
"present_stage_count": len(AI_AUTOMATION_REQUIRED_LOOP_STAGES),
|
||||
"missing_stage_ids": [],
|
||||
"latest_flow_closed": True,
|
||||
"latest_loop_closed": True,
|
||||
"execution_loop_closed": True,
|
||||
"same_run_correlation": True,
|
||||
"full_trace_same_run_correlation_proven": True,
|
||||
"automation_run_id": "internal-run-identity",
|
||||
"run_id_mismatch_stage_ids": [],
|
||||
"uncorrelated_stage_ids": [],
|
||||
"stage_contracts": [
|
||||
{
|
||||
"stage_id": stage_id,
|
||||
"evidence_present": True,
|
||||
"same_run_correlation_proven": True,
|
||||
"completion_eligible": True,
|
||||
"evidence_source": "same_run_runtime_stage_receipt",
|
||||
}
|
||||
for stage_id in AI_AUTOMATION_REQUIRED_LOOP_STAGES
|
||||
],
|
||||
"closed": True,
|
||||
"same_run_correlation_required": True,
|
||||
"metadata_only_counts_as_completion": False,
|
||||
"historical_aggregate_fallback_allowed": False,
|
||||
}
|
||||
|
||||
|
||||
def _ready_payload(
|
||||
source_health: list[dict] | None = None,
|
||||
*,
|
||||
@@ -33,6 +73,7 @@ def _ready_payload(
|
||||
discovery_error: str | None = None,
|
||||
automation_rows: list[SimpleNamespace] | None = None,
|
||||
runtime_receipt_counts: dict[str, int] | None = None,
|
||||
strict_runtime: dict | None = None,
|
||||
) -> dict:
|
||||
now = datetime(2026, 7, 11, 4, 0, tzinfo=UTC)
|
||||
discovery = _row(
|
||||
@@ -124,6 +165,7 @@ def _ready_payload(
|
||||
learning_writeback_count_24h=runtime_counts.get("learn", 0),
|
||||
mttr_minutes_24h=14.25,
|
||||
),
|
||||
strict_runtime_row=strict_runtime,
|
||||
audit_row=_row(
|
||||
k8s_audit_count_24h=2,
|
||||
k8s_audit_failure_count_24h=0,
|
||||
@@ -249,6 +291,92 @@ def test_projects_canonical_runtime_receipts_without_false_same_run_closure() ->
|
||||
assert work_items["AIA-P0-008-01"]["gap_count"] == 1
|
||||
|
||||
|
||||
def test_projects_exact_authoritative_same_run_runtime_closure() -> None:
|
||||
payload = _ready_payload(strict_runtime=_closed_strict_runtime())
|
||||
|
||||
strict = payload["ai_automation"]["strict_runtime_completion"]
|
||||
assert strict == {
|
||||
"schema_version": "ai_agent_strict_runtime_completion_v2",
|
||||
"runtime_contract_schema_version": (
|
||||
AI_AUTOMATION_RUNTIME_CONTRACT_SCHEMA_VERSION
|
||||
),
|
||||
"contract_compatible": True,
|
||||
"completion_percent": 100,
|
||||
"required_stage_count": len(AI_AUTOMATION_REQUIRED_LOOP_STAGES),
|
||||
"present_stage_count": len(AI_AUTOMATION_REQUIRED_LOOP_STAGES),
|
||||
"missing_stage_count": 0,
|
||||
"same_run_correlation": True,
|
||||
"full_trace_same_run_correlation_proven": True,
|
||||
"execution_loop_closed": True,
|
||||
"closed": True,
|
||||
"reason_code": None,
|
||||
"cache_fallback_active": False,
|
||||
"raw_run_identity_returned": False,
|
||||
"raw_stage_receipts_returned": False,
|
||||
}
|
||||
assert payload["completion"]["strict_runtime_closure_percent"] == 100
|
||||
assert payload["ai_automation"]["same_run_closed_loop_proven"] is True
|
||||
assert payload["ai_automation"]["same_run_closed_loop_count"] == 1
|
||||
assert all(
|
||||
item["work_item_id"] != "AIA-P0-008-01"
|
||||
for item in payload["work_items"]
|
||||
)
|
||||
assert payload["completion"]["overall_percent"] == round(
|
||||
sum(
|
||||
dimension["completion_percent"]
|
||||
for dimension in payload["completion"]["dimensions"]
|
||||
)
|
||||
/ payload["completion"]["dimension_count"]
|
||||
)
|
||||
|
||||
public_text = json.dumps(payload, ensure_ascii=False)
|
||||
assert "internal-run-identity" not in public_text
|
||||
assert '"automation_run_id"' not in public_text
|
||||
assert '"stage_contracts"' not in public_text
|
||||
|
||||
|
||||
def test_strict_runtime_projection_fails_closed_on_contract_or_receipt_drift() -> None:
|
||||
variants: list[dict] = []
|
||||
|
||||
bad_schema = _closed_strict_runtime()
|
||||
bad_schema["schema_version"] = "ai_agent_strict_runtime_completion_v1"
|
||||
variants.append(bad_schema)
|
||||
|
||||
missing_stage = _closed_strict_runtime()
|
||||
missing_stage["present_stage_count"] -= 1
|
||||
missing_stage["missing_stage_ids"] = [AI_AUTOMATION_REQUIRED_LOOP_STAGES[-1]]
|
||||
variants.append(missing_stage)
|
||||
|
||||
cross_run = _closed_strict_runtime()
|
||||
cross_run["same_run_correlation"] = False
|
||||
cross_run["run_id_mismatch_stage_ids"] = ["telegram_receipt"]
|
||||
variants.append(cross_run)
|
||||
|
||||
no_run_identity = _closed_strict_runtime()
|
||||
no_run_identity["automation_run_id"] = ""
|
||||
variants.append(no_run_identity)
|
||||
|
||||
metadata_only = _closed_strict_runtime()
|
||||
metadata_only["metadata_only_counts_as_completion"] = True
|
||||
variants.append(metadata_only)
|
||||
|
||||
invalid_stage_contract = copy.deepcopy(_closed_strict_runtime())
|
||||
invalid_stage_contract["stage_contracts"][0]["completion_eligible"] = False
|
||||
variants.append(invalid_stage_contract)
|
||||
|
||||
for strict_runtime in variants:
|
||||
payload = _ready_payload(strict_runtime=strict_runtime)
|
||||
strict = payload["ai_automation"]["strict_runtime_completion"]
|
||||
assert strict["closed"] is False
|
||||
assert strict["completion_percent"] == 0
|
||||
assert payload["completion"]["strict_runtime_closure_percent"] == 0
|
||||
assert payload["ai_automation"]["same_run_closed_loop_proven"] is False
|
||||
assert any(
|
||||
item["work_item_id"] == "AIA-P0-008-01"
|
||||
for item in payload["work_items"]
|
||||
)
|
||||
|
||||
|
||||
def test_discovery_failure_exposes_only_allowlisted_collector_ids() -> None:
|
||||
raw_error = (
|
||||
"RuntimeError: asset_collector_failures=domain_tls_inventory,"
|
||||
@@ -612,6 +740,9 @@ def test_unavailable_payload_is_fixed_degraded_contract_without_raw_error() -> N
|
||||
assert payload["summary"]["overall_security_completion_percent"] == 0
|
||||
assert payload["completion"]["overall_percent"] == 0
|
||||
assert payload["completion"]["strict_runtime_closure_percent"] == 0
|
||||
assert payload["summary"]["source_count"] == 10
|
||||
assert payload["summary"]["unavailable_source_count"] == 10
|
||||
assert payload["ai_automation"]["strict_runtime_completion"]["closed"] is False
|
||||
assert payload["discovery"]["fresh"] is False
|
||||
assert payload["boundaries"]["raw_asset_identity_returned"] is False
|
||||
assert payload["boundaries"]["raw_event_payload_returned"] is False
|
||||
@@ -676,12 +807,25 @@ def test_service_uses_canonical_awoooi_project_scope(monkeypatch) -> None:
|
||||
"src.services.iwooos_security_asset_control_plane.get_db_context",
|
||||
fake_db_context,
|
||||
)
|
||||
|
||||
async def strict_runtime_unavailable():
|
||||
return {}, {
|
||||
"source_id": "autonomous_strict_runtime",
|
||||
"status": "unavailable",
|
||||
"reason_code": "autonomous_strict_runtime_query_failed",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.services.iwooos_security_asset_control_plane._read_strict_runtime_source",
|
||||
strict_runtime_unavailable,
|
||||
)
|
||||
payload = asyncio.run(service.get_snapshot())
|
||||
|
||||
assert requested_projects == ["awoooi"] * 9
|
||||
assert payload["status"] == "degraded"
|
||||
assert payload["source_status"] == "live_database_unavailable"
|
||||
assert payload["summary"]["unavailable_source_count"] == 9
|
||||
assert payload["summary"]["source_count"] == 10
|
||||
assert payload["summary"]["unavailable_source_count"] == 10
|
||||
|
||||
|
||||
def test_service_bounds_source_concurrency_for_database_budget(monkeypatch) -> None:
|
||||
@@ -719,11 +863,23 @@ def test_service_bounds_source_concurrency_for_database_budget(monkeypatch) -> N
|
||||
"src.services.iwooos_security_asset_control_plane.get_db_context",
|
||||
lambda project_id: DbContext(),
|
||||
)
|
||||
|
||||
async def strict_runtime_ready():
|
||||
return _closed_strict_runtime(), {
|
||||
"source_id": "autonomous_strict_runtime",
|
||||
"status": "ready",
|
||||
"reason_code": None,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.services.iwooos_security_asset_control_plane._read_strict_runtime_source",
|
||||
strict_runtime_ready,
|
||||
)
|
||||
payload = asyncio.run(service._load_snapshot())
|
||||
|
||||
assert concurrency["maximum"] == 3
|
||||
assert payload["summary"]["source_count"] == 9
|
||||
assert payload["summary"]["ready_source_count"] == 9
|
||||
assert payload["summary"]["source_count"] == 10
|
||||
assert payload["summary"]["ready_source_count"] == 10
|
||||
assert payload["summary"]["unavailable_source_count"] == 0
|
||||
compliance_query = next(
|
||||
statement
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# ruff: noqa: E402, I001
|
||||
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import UTC, datetime
|
||||
@@ -90,6 +92,30 @@ def test_wazuh_runtime_readback_closes_only_with_complete_same_run_receipts() ->
|
||||
assert payload["boundaries"]["command_output_returned"] is False
|
||||
|
||||
|
||||
def test_wazuh_runtime_readback_prefers_ingress_convergence_semantics() -> None:
|
||||
row = _complete_row()
|
||||
row["catalog_id"] = "ansible:wazuh-alertmanager-integration"
|
||||
row["work_item_id"] = "P0-03-WAZUH-ALERT-INGRESS"
|
||||
|
||||
payload = build_iwooos_wazuh_controlled_executor_runtime_readback(
|
||||
row,
|
||||
executor_enabled=True,
|
||||
)
|
||||
|
||||
assert payload["status"] == "wazuh_alert_ingress_same_run_runtime_closed"
|
||||
assert payload["work_item_id"] == "P0-03-WAZUH-ALERT-INGRESS"
|
||||
assert payload["summary"]["selected_catalog_id"] == (
|
||||
"ansible:wazuh-alertmanager-integration"
|
||||
)
|
||||
assert payload["summary"]["bounded_posture_execution_passed_count"] == 0
|
||||
assert payload["summary"]["bounded_ingress_convergence_passed_count"] == 1
|
||||
assert payload["summary"]["host_write_performed_count"] == 1
|
||||
assert payload["boundaries"]["bounded_no_write_posture_probe"] is False
|
||||
assert payload["boundaries"]["bounded_alert_ingress_convergence"] is True
|
||||
assert payload["boundaries"]["sanitized_alert_ingress_configured"] is True
|
||||
assert payload["boundaries"]["raw_wazuh_payload_persisted"] is False
|
||||
|
||||
|
||||
def test_wazuh_runtime_readback_keeps_partial_execution_open() -> None:
|
||||
row = _complete_row()
|
||||
row["stage_ids"] = [
|
||||
|
||||
418
apps/api/tests/test_wazuh_alertmanager_integration.py
Normal file
418
apps/api/tests/test_wazuh_alertmanager_integration.py
Normal file
@@ -0,0 +1,418 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# ruff: noqa: E402, I001
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test")
|
||||
|
||||
from src.services.awooop_ansible_audit_service import (
|
||||
_catalog_hints,
|
||||
get_ansible_catalog_item,
|
||||
)
|
||||
from src.services.awooop_ansible_post_verifier import postconditions_for_catalog
|
||||
from src.services.awooop_ansible_check_mode_service import (
|
||||
build_ansible_apply_command,
|
||||
build_ansible_check_mode_command,
|
||||
)
|
||||
from src.core.config import settings
|
||||
from src.services.controlled_alert_target_router import resolve_typed_alert_target
|
||||
from src.services.incident_service import classify_alert_early
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = (
|
||||
ROOT
|
||||
/ "infra"
|
||||
/ "ansible"
|
||||
/ "files"
|
||||
/ "wazuh"
|
||||
/ "custom-awoooi-alertmanager.py"
|
||||
)
|
||||
PLAYBOOK = (
|
||||
ROOT
|
||||
/ "infra"
|
||||
/ "ansible"
|
||||
/ "playbooks"
|
||||
/ "wazuh-alertmanager-integration.yml"
|
||||
)
|
||||
BROKER_DEPLOYMENT = (
|
||||
ROOT / "k8s" / "awoooi-prod" / "08-deployment-ansible-executor-broker.yaml"
|
||||
)
|
||||
WORKER_DEPLOYMENT = ROOT / "k8s" / "awoooi-prod" / "08-deployment-worker.yaml"
|
||||
CATALOG_ID = "ansible:wazuh-alertmanager-integration"
|
||||
|
||||
|
||||
def _load_bridge_module():
|
||||
spec = importlib.util.spec_from_file_location("wazuh_awoooi_bridge", SCRIPT)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def _alert(*, agent_id: str = "017") -> dict:
|
||||
return {
|
||||
"timestamp": "2026-07-15T05:30:00Z",
|
||||
"manager": {"name": "wazuh-manager-internal"},
|
||||
"agent": {
|
||||
"id": agent_id,
|
||||
"name": "private-endpoint-name",
|
||||
"ip": "192.168.0.55",
|
||||
},
|
||||
"rule": {
|
||||
"id": "5710",
|
||||
"level": 12,
|
||||
"description": "private security evidence",
|
||||
"groups": ["authentication_failures", "sshd"],
|
||||
"mitre": {"id": ["T1110", "not-a-technique"]},
|
||||
},
|
||||
"full_log": "raw source event must remain in Wazuh",
|
||||
"data": {"srcip": "203.0.113.7"},
|
||||
}
|
||||
|
||||
|
||||
def test_wazuh_bridge_emits_only_public_safe_stable_metadata() -> None:
|
||||
bridge = _load_bridge_module()
|
||||
|
||||
payload = bridge.build_alertmanager_payload(
|
||||
_alert(),
|
||||
now=datetime(2026, 7, 15, 5, 30, tzinfo=UTC),
|
||||
)
|
||||
encoded = json.dumps(payload, sort_keys=True)
|
||||
labels = payload["alerts"][0]["labels"]
|
||||
|
||||
assert labels["alertname"] == "WazuhSecurityEvent_5710"
|
||||
assert labels["component"] == "wazuh-manager"
|
||||
assert labels["severity"] == "critical"
|
||||
assert labels["wazuh_rule_groups"] == "authentication_failures,sshd"
|
||||
assert labels["mitre_techniques"] == "T1110"
|
||||
assert labels["deployment"].startswith("wazuh-agent-")
|
||||
assert labels["raw_payload_persisted"] == "false"
|
||||
assert labels["active_response_requested"] == "false"
|
||||
for forbidden in (
|
||||
"private-endpoint-name",
|
||||
"192.168.0.55",
|
||||
"203.0.113.7",
|
||||
"private security evidence",
|
||||
"raw source event must remain in Wazuh",
|
||||
"wazuh-manager-internal",
|
||||
):
|
||||
assert forbidden not in encoded
|
||||
|
||||
|
||||
def test_wazuh_bridge_fingerprint_clusters_same_rule_and_agent() -> None:
|
||||
bridge = _load_bridge_module()
|
||||
|
||||
first = bridge.build_alertmanager_payload(_alert(agent_id="017"))
|
||||
second = bridge.build_alertmanager_payload(_alert(agent_id="017"))
|
||||
other_agent = bridge.build_alertmanager_payload(_alert(agent_id="018"))
|
||||
|
||||
assert first["groupKey"] == second["groupKey"]
|
||||
assert first["groupKey"] != other_agent["groupKey"]
|
||||
assert (
|
||||
first["alerts"][0]["labels"]["deployment"]
|
||||
!= other_agent["alerts"][0]["labels"]["deployment"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url",
|
||||
[
|
||||
"http://awoooi.wooo.work/api/v1/webhooks/alertmanager",
|
||||
"https://example.com/api/v1/webhooks/alertmanager",
|
||||
"https://awoooi.wooo.work/api/v1/webhooks/alertmanager?raw=1",
|
||||
"https://user:pass@awoooi.wooo.work/api/v1/webhooks/alertmanager",
|
||||
],
|
||||
)
|
||||
def test_wazuh_bridge_rejects_non_allowlisted_hook_urls(url: str) -> None:
|
||||
bridge = _load_bridge_module()
|
||||
|
||||
with pytest.raises(ValueError, match="hook_url_not_allowlisted"):
|
||||
bridge._validated_hook_url(url)
|
||||
|
||||
|
||||
def test_wazuh_integration_catalog_and_rollback_contract_are_bounded() -> None:
|
||||
catalog = get_ansible_catalog_item(CATALOG_ID)
|
||||
playbook = yaml.safe_load(PLAYBOOK.read_text(encoding="utf-8"))
|
||||
text = PLAYBOOK.read_text(encoding="utf-8")
|
||||
conditions = postconditions_for_catalog(CATALOG_ID)
|
||||
|
||||
assert catalog is not None
|
||||
assert catalog["inventory_hosts"] == ["host_112"]
|
||||
assert catalog["risk_level"] == "high"
|
||||
assert catalog["auto_apply_enabled"] is True
|
||||
assert catalog["approval_required"] is False
|
||||
assert catalog["supports_check_mode"] is True
|
||||
assert playbook[0]["hosts"] == "host_112"
|
||||
assert "backup: true" in text
|
||||
assert "remote_src: true" in text
|
||||
assert "wazuh_privileged_convergence_capability_missing" in text
|
||||
assert "/var/lib/awoooi-wazuh-alert-ingress/receipt.env" in text
|
||||
assert "raw_payload_persisted=0" in text
|
||||
assert "active_response_enabled=0" in text
|
||||
assert "wazuh_alertmanager_integration_rolled_back" in text
|
||||
assert "slurp:" not in text
|
||||
assert "api_key>" not in text
|
||||
assert "ansible_become_pass" not in text
|
||||
assert "raw.githubusercontent.com" not in text
|
||||
assert {condition.condition_id for condition in conditions} == {
|
||||
"host_112_wazuh_alertmanager_script",
|
||||
"host_112_wazuh_alertmanager_config",
|
||||
"host_112_wazuh_manager_runtime_after_ingress_convergence",
|
||||
"host_112_wazuh_integrator_runtime",
|
||||
"host_112_awoooi_alertmanager_ingress_reachable",
|
||||
}
|
||||
|
||||
|
||||
def test_wazuh_become_password_projection_is_path_only_and_optional(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
password_file = tmp_path / "become-password"
|
||||
password_file.write_text("test-only-secret-value\n", encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
settings,
|
||||
"AWOOOP_ANSIBLE_BECOME_PASSWORD_FILE",
|
||||
str(password_file),
|
||||
)
|
||||
|
||||
spec = build_ansible_check_mode_command(
|
||||
playbook_path="infra/ansible/playbooks/wazuh-alertmanager-integration.yml",
|
||||
inventory_hosts=("host_112",),
|
||||
playbook_root=ROOT / "infra" / "ansible",
|
||||
check_mode_ssh_key_path=tmp_path / "ssh-key",
|
||||
check_mode_known_hosts_path=tmp_path / "known-hosts",
|
||||
)
|
||||
manifest = yaml.safe_load(BROKER_DEPLOYMENT.read_text(encoding="utf-8"))
|
||||
volume = next(
|
||||
item
|
||||
for item in manifest["spec"]["template"]["spec"]["volumes"]
|
||||
if item["name"] == "host112-become-password"
|
||||
)
|
||||
|
||||
assert spec.env["ANSIBLE_BECOME_PASSWORD_FILE"] == str(password_file)
|
||||
assert "test-only-secret-value" not in " ".join(spec.command)
|
||||
assert volume["secret"]["optional"] is True
|
||||
assert volume["secret"]["items"] == [
|
||||
{"key": "HOST112_ANSIBLE_BECOME_PASSWORD", "path": "password"}
|
||||
]
|
||||
assert "test-only-secret-value" not in BROKER_DEPLOYMENT.read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
apply_spec = build_ansible_apply_command(
|
||||
playbook_path="infra/ansible/playbooks/wazuh-alertmanager-integration.yml",
|
||||
inventory_hosts=("host_112",),
|
||||
playbook_root=ROOT / "infra" / "ansible",
|
||||
check_mode_ssh_key_path=tmp_path / "ssh-key",
|
||||
check_mode_known_hosts_path=tmp_path / "known-hosts",
|
||||
)
|
||||
assert apply_spec.env["ANSIBLE_BECOME_PASSWORD_FILE"] == str(password_file)
|
||||
assert "test-only-secret-value" not in " ".join(apply_spec.command)
|
||||
|
||||
unrelated_spec = build_ansible_check_mode_command(
|
||||
playbook_path="infra/ansible/playbooks/wazuh-manager-posture-readback.yml",
|
||||
inventory_hosts=("host_112",),
|
||||
playbook_root=ROOT / "infra" / "ansible",
|
||||
check_mode_ssh_key_path=tmp_path / "ssh-key",
|
||||
check_mode_known_hosts_path=tmp_path / "known-hosts",
|
||||
)
|
||||
assert "ANSIBLE_BECOME_PASSWORD_FILE" not in unrelated_spec.env
|
||||
|
||||
|
||||
def test_wazuh_ingress_convergence_uses_only_the_mutating_catalog() -> None:
|
||||
route = resolve_typed_alert_target(
|
||||
alertname="WazuhAlertmanagerIntegrationConvergence",
|
||||
target_resource="wazuh-manager",
|
||||
namespace="awoooi",
|
||||
labels={"service": "wazuh-manager", "tool": "wazuh"},
|
||||
)
|
||||
hints = _catalog_hints(
|
||||
{
|
||||
"incident_id": "IWZ-INGRESS-2026071500",
|
||||
"alertname": "WazuhAlertmanagerIntegrationConvergence",
|
||||
"alert_category": "security",
|
||||
"affected_services": ["wazuh-manager", "siem"],
|
||||
"signals": [
|
||||
{
|
||||
"alert_name": "WazuhAlertmanagerIntegrationConvergence",
|
||||
"labels": {
|
||||
"service": "wazuh-manager",
|
||||
"component": "wazuh-manager",
|
||||
"tool": "wazuh",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
None,
|
||||
)
|
||||
|
||||
assert route["canonical_asset_id"] == "service:wazuh-manager"
|
||||
assert route["allowed_catalog_ids"] == [CATALOG_ID]
|
||||
assert [item["catalog_id"] for item in hints["candidates"]] == [CATALOG_ID]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wazuh_ingress_scheduler_records_one_high_risk_controlled_candidate(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from src.jobs import awooop_ansible_candidate_backfill_job as job
|
||||
|
||||
class _Mappings:
|
||||
def first(self):
|
||||
return None
|
||||
|
||||
class _Result:
|
||||
def mappings(self):
|
||||
return _Mappings()
|
||||
|
||||
class _Db:
|
||||
async def execute(self, _statement, params):
|
||||
assert json.loads(params["catalog_match"]) == [
|
||||
{"catalog_id": CATALOG_ID}
|
||||
]
|
||||
return _Result()
|
||||
|
||||
@asynccontextmanager
|
||||
async def fake_db_context(project_id: str):
|
||||
assert project_id == "awoooi"
|
||||
yield _Db()
|
||||
|
||||
recorder = AsyncMock(return_value=True)
|
||||
monkeypatch.setattr(job, "get_db_context", fake_db_context)
|
||||
monkeypatch.setattr(
|
||||
job.settings,
|
||||
"ENABLE_IWOOOS_WAZUH_MANAGER_POSTURE_EXECUTOR",
|
||||
True,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
job.settings,
|
||||
"IWOOOS_WAZUH_MANAGER_POSTURE_FRESHNESS_HOURS",
|
||||
6,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
job,
|
||||
"now_taipei",
|
||||
MagicMock(return_value=datetime.fromisoformat("2026-07-15T05:59:59+08:00")),
|
||||
)
|
||||
|
||||
result = await job.enqueue_iwooos_wazuh_alertmanager_integration_candidate_once(
|
||||
recorder=recorder,
|
||||
evidence_collector=AsyncMock(return_value=MagicMock(snapshot_id="wazuh-ingress")),
|
||||
evidence_verifier=AsyncMock(return_value=True),
|
||||
)
|
||||
|
||||
assert result["status"] == "queued_for_controlled_executor"
|
||||
assert result["work_item_id"] == "P0-03-WAZUH-ALERT-INGRESS"
|
||||
recorded = recorder.await_args.kwargs
|
||||
assert recorded["incident"]["alertname"] == (
|
||||
"WazuhAlertmanagerIntegrationConvergence"
|
||||
)
|
||||
assert recorded["incident"]["incident_id"] == "IWZ-INGRESS-2026071500"
|
||||
assert len(recorded["incident"]["incident_id"]) <= 30
|
||||
assert recorded["proposal_data"]["risk_level"] == "high"
|
||||
assert recorded["proposal_data"]["execution_priority"] == 0
|
||||
assert recorded["incident"]["source_receipt_ref"] == (
|
||||
"scheduled-wazuh-alert-ingress:2026071500"
|
||||
)
|
||||
|
||||
write_not_acknowledged = (
|
||||
await job.enqueue_iwooos_wazuh_alertmanager_integration_candidate_once(
|
||||
recorder=AsyncMock(return_value=False),
|
||||
evidence_collector=AsyncMock(
|
||||
return_value=MagicMock(snapshot_id="wazuh-ingress")
|
||||
),
|
||||
evidence_verifier=AsyncMock(return_value=True),
|
||||
)
|
||||
)
|
||||
assert write_not_acknowledged["status"] == "candidate_write_not_acknowledged"
|
||||
assert write_not_acknowledged["existing"] == 0
|
||||
assert write_not_acknowledged["active_blockers"] == [
|
||||
"candidate_write_not_acknowledged"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wazuh_ingress_scheduler_reports_unresolved_payload_before_recording(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from src.jobs import awooop_ansible_candidate_backfill_job as job
|
||||
|
||||
class _Mappings:
|
||||
def first(self):
|
||||
return None
|
||||
|
||||
class _Result:
|
||||
def mappings(self):
|
||||
return _Mappings()
|
||||
|
||||
class _Db:
|
||||
async def execute(self, _statement, _params):
|
||||
return _Result()
|
||||
|
||||
@asynccontextmanager
|
||||
async def fake_db_context(_project_id: str):
|
||||
yield _Db()
|
||||
|
||||
recorder = AsyncMock(return_value=False)
|
||||
monkeypatch.setattr(job, "get_db_context", fake_db_context)
|
||||
monkeypatch.setattr(
|
||||
job.settings,
|
||||
"ENABLE_IWOOOS_WAZUH_MANAGER_POSTURE_EXECUTOR",
|
||||
True,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
job,
|
||||
"build_ansible_decision_audit_payload",
|
||||
MagicMock(return_value=None),
|
||||
)
|
||||
|
||||
result = await job.enqueue_iwooos_wazuh_alertmanager_integration_candidate_once(
|
||||
recorder=recorder,
|
||||
evidence_collector=AsyncMock(return_value=MagicMock(snapshot_id="wazuh-ingress")),
|
||||
evidence_verifier=AsyncMock(return_value=True),
|
||||
)
|
||||
|
||||
assert result["status"] == "blocked_canonical_asset_or_ansible_catalog_unresolved"
|
||||
assert result["existing"] == 0
|
||||
assert result["active_blockers"] == [
|
||||
"canonical_asset_or_ansible_catalog_unresolved"
|
||||
]
|
||||
recorder.assert_not_awaited()
|
||||
|
||||
|
||||
def test_wazuh_ingress_scheduler_worker_mounts_canonical_service_registry() -> None:
|
||||
deployment = yaml.safe_load(WORKER_DEPLOYMENT.read_text(encoding="utf-8"))
|
||||
pod_spec = deployment["spec"]["template"]["spec"]
|
||||
container = pod_spec["containers"][0]
|
||||
|
||||
mount = next(
|
||||
item for item in container["volumeMounts"] if item["name"] == "service-registry"
|
||||
)
|
||||
volume = next(item for item in pod_spec["volumes"] if item["name"] == "service-registry")
|
||||
|
||||
assert mount == {
|
||||
"name": "service-registry",
|
||||
"mountPath": "/app/ops/config/service-registry.yaml",
|
||||
"subPath": "service-registry.yaml",
|
||||
"readOnly": True,
|
||||
}
|
||||
assert volume["configMap"]["name"] == "service-registry"
|
||||
|
||||
|
||||
def test_wazuh_security_event_uses_secops_lifecycle() -> None:
|
||||
assert classify_alert_early(
|
||||
"WazuhSecurityEvent_5710",
|
||||
"critical",
|
||||
{"tool": "wazuh"},
|
||||
) == ("secops", "TYPE-5S")
|
||||
Reference in New Issue
Block a user