fix(iwooos): repair wazuh host trust and retry
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m27s
CD Pipeline / build-and-deploy (push) Successful in 6m53s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 1m4s
CD Pipeline / post-deploy-checks (push) Successful in 1m51s

This commit is contained in:
ogt
2026-07-11 10:13:02 +08:00
parent 707846474f
commit 4a4b95e513
11 changed files with 401 additions and 28 deletions

View File

@@ -103,3 +103,12 @@ def test_cd_applies_rolls_back_and_verifies_network_boundary() -> None:
assert "legacy_namespace_egress_allow_absent=true" in workflow
assert "192.168.0.110/32 192.168.0.112/32 192.168.0.120/32" in workflow
assert 'wc -l)" = "5"' in workflow
expected_hosts = (
"192.168.0.110 192.168.0.112 192.168.0.120 "
"192.168.0.121 192.168.0.188"
)
assert f"ssh-keyscan {expected_hosts}" in workflow
assert "EXPECTED_HOSTS=5" in workflow
assert workflow.count(expected_hosts) >= 4
assert "len(reachable) == len(sys.argv) - 1" in workflow
assert "ssh-mcp-key known_hosts 更新失敗,停止部署" in workflow

View File

@@ -113,6 +113,37 @@ def test_wazuh_runtime_readback_keeps_partial_execution_open() -> None:
assert "same_run_mcp_context_missing" in payload["active_blockers"]
def test_wazuh_runtime_readback_classifies_failed_check_for_bounded_retry() -> None:
row = {
"candidate_op_id": "00000000-0000-0000-0000-000000000311",
"automation_run_id": "00000000-0000-0000-0000-000000000311",
"trace_id": "00000000-0000-0000-0000-000000000311",
"run_id": "00000000-0000-0000-0000-000000000311",
"work_item_id": "P0-03-WAZUH-MANAGER-POSTURE",
"check_status": "failed",
"check_returncode": "4",
"check_timed_out": "false",
"stage_ids": [],
}
payload = build_iwooos_wazuh_controlled_executor_runtime_readback(
row,
executor_enabled=True,
)
assert payload["status"] == (
"wazuh_manager_posture_check_failed_waiting_bounded_retry"
)
assert payload["summary"]["check_mode_failed_count"] == 1
assert payload["summary"]["runtime_execution_performed_count"] == 0
assert payload["summary"]["runtime_closed_count"] == 0
assert payload["check_failure_class"] == "ansible_target_unreachable"
assert payload["next_safe_action"] == (
"repair_and_enqueue_bounded_retry:ansible_target_unreachable"
)
assert "ansible_target_unreachable" in payload["active_blockers"]
def test_wazuh_posture_catalog_and_playbook_are_bounded_no_write() -> None:
catalog = get_ansible_catalog_item("ansible:wazuh-manager-posture-readback")
assert catalog is not None
@@ -224,6 +255,192 @@ async def test_wazuh_posture_scheduler_enqueues_after_mcp_and_log_evidence(
posture_clock.assert_called_once_with()
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_retries_failed_check_once_per_deploy(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.jobs import awooop_ansible_candidate_backfill_job as job
existing_row = {
"op_id": "00000000-0000-0000-0000-000000000321",
"candidate_status": "dry_run",
"automation_run_id": "00000000-0000-0000-0000-000000000321",
"latest_check_status": "failed",
}
class _Mappings:
def first(self):
return existing_row
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):
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-11T05:59:59+08:00")),
)
monkeypatch.setenv(
"AWOOOI_BUILD_COMMIT_SHA",
"abcdef1234567890abcdef1234567890abcdef12",
)
result = await job.enqueue_iwooos_wazuh_manager_posture_candidate_once(
recorder=recorder,
evidence_collector=AsyncMock(return_value=MagicMock(snapshot_id="wazuh-2")),
evidence_verifier=AsyncMock(return_value=True),
)
assert result["status"] == "queued_for_controlled_executor_retry"
assert result["queued"] == 1
assert result["retry_of_failed_check_mode"] is True
recorded = recorder.await_args.kwargs
assert recorded["incident"]["incident_id"] == (
"IWZ-POSTURE-2026071100-ABCDEF123456"
)
assert recorded["incident"]["source_receipt_ref"] == (
"scheduled-wazuh-manager-posture:2026071100-ABCDEF123456"
)
assert recorded["automation_run_id"] == result["automation_run_id"]
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_does_not_duplicate_active_candidate(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.jobs import awooop_ansible_candidate_backfill_job as job
class _Mappings:
def first(self):
return {
"op_id": "00000000-0000-0000-0000-000000000331",
"candidate_status": "dry_run",
"automation_run_id": "00000000-0000-0000-0000-000000000331",
"latest_check_status": "pending",
}
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=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,
)
result = await job.enqueue_iwooos_wazuh_manager_posture_candidate_once(
recorder=recorder,
)
assert result["status"] == "fresh_candidate_already_present"
assert result["queued"] == 0
recorder.assert_not_awaited()
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_limits_failed_retry_to_once_per_deploy(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.jobs import awooop_ansible_candidate_backfill_job as job
class _Mappings:
def first(self):
return {
"op_id": "00000000-0000-0000-0000-000000000341",
"candidate_status": "failed",
"automation_run_id": "00000000-0000-0000-0000-000000000341",
"source_receipt_ref": (
"scheduled-wazuh-manager-posture:"
"2026071100-ABCDEF123456"
),
"latest_check_status": "failed",
}
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=True)
collector = AsyncMock()
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.setenv(
"AWOOOI_BUILD_COMMIT_SHA",
"abcdef1234567890abcdef1234567890abcdef12",
)
result = await job.enqueue_iwooos_wazuh_manager_posture_candidate_once(
recorder=recorder,
evidence_collector=collector,
)
assert result["status"] == (
"failed_check_retry_already_attempted_for_deploy"
)
assert result["queued"] == 0
assert result["retry_of_failed_check_mode"] is True
assert result["active_blockers"] == [
"bounded_retry_already_attempted_for_deploy"
]
recorder.assert_not_awaited()
collector.assert_not_awaited()
@pytest.mark.asyncio
async def test_wazuh_posture_scheduler_queues_bounded_probe_with_degraded_context(
monkeypatch: pytest.MonkeyPatch,

View File

@@ -91,6 +91,7 @@ def test_api_manifest_has_read_only_identity_and_no_ssh_executor_mounts() -> Non
assert env["DATABASE_NULL_POOL"] == "true"
assert env["ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER"] == "false"
assert env["ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER"] == "false"
assert env["ENABLE_IWOOOS_WAZUH_MANAGER_POSTURE_EXECUTOR"] == "true"
assert {"repair-ssh-key", "repair-known-hosts", "ssh-mcp-key"}.isdisjoint(
mount_names | volume_names
)