fix(security): mount canonical registry in worker
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 2m31s
CD Pipeline / build-and-deploy (push) Successful in 16m46s
CD Pipeline / post-deploy-checks (push) Successful in 2m10s

This commit is contained in:
ogt
2026-07-15 15:12:15 +08:00
parent b6240c1c12
commit 1c21c7acc8
3 changed files with 127 additions and 8 deletions

View File

@@ -842,14 +842,34 @@ async def enqueue_iwooos_wazuh_manager_posture_candidate_once(
], ],
} }
proposal_data = {
"source": _scheduler_source,
"risk_level": _risk_level,
"execution_priority": 0,
"action": _proposal_action,
}
if build_ansible_decision_audit_payload(
incident=incident,
proposal_data=proposal_data,
decision_path=_BACKFILL_DECISION_PATH,
not_used_reason=_reason,
) is None:
return {
"status": "blocked_canonical_asset_or_ansible_catalog_unresolved",
"queued": 0,
"existing": 1 if existing_row else 0,
"evidence_ready": 1,
"automation_run_id": automation_run_id,
"work_item_id": _work_item_id,
"retry_reason": retry_reason,
"active_blockers": [
"canonical_asset_or_ansible_catalog_unresolved"
],
}
queued = await recorder( queued = await recorder(
incident=incident, incident=incident,
proposal_data={ proposal_data=proposal_data,
"source": _scheduler_source,
"risk_level": _risk_level,
"execution_priority": 0,
"action": _proposal_action,
},
decision_path=_BACKFILL_DECISION_PATH, decision_path=_BACKFILL_DECISION_PATH,
not_used_reason=_reason, not_used_reason=_reason,
automation_run_id=automation_run_id, automation_run_id=automation_run_id,
@@ -863,9 +883,11 @@ async def enqueue_iwooos_wazuh_manager_posture_candidate_once(
else "queued_for_controlled_executor" else "queued_for_controlled_executor"
if queued if queued
else "candidate_race_deduplicated" else "candidate_race_deduplicated"
if existing_row
else "candidate_write_not_acknowledged"
), ),
"queued": 1 if queued else 0, "queued": 1 if queued else 0,
"existing": 0 if queued else 1, "existing": 1 if existing_row else 0,
"evidence_ready": 1 if evidence_ready else 0, "evidence_ready": 1 if evidence_ready else 0,
"automation_run_id": automation_run_id, "automation_run_id": automation_run_id,
"work_item_id": _work_item_id, "work_item_id": _work_item_id,
@@ -878,7 +900,11 @@ async def enqueue_iwooos_wazuh_manager_posture_candidate_once(
"controlled_apply_failed", "controlled_apply_failed",
"post_apply_learning_incomplete", "post_apply_learning_incomplete",
}, },
"active_blockers": [] if queued else ["candidate_write_not_acknowledged"], "active_blockers": (
[]
if queued or existing_row
else ["candidate_write_not_acknowledged"]
),
} }
except Exception as exc: except Exception as exc:
logger.warning( logger.warning(

View File

@@ -48,6 +48,7 @@ PLAYBOOK = (
BROKER_DEPLOYMENT = ( BROKER_DEPLOYMENT = (
ROOT / "k8s" / "awoooi-prod" / "08-deployment-ansible-executor-broker.yaml" 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" CATALOG_ID = "ansible:wazuh-alertmanager-integration"
@@ -325,6 +326,89 @@ async def test_wazuh_ingress_scheduler_records_one_high_risk_controlled_candidat
"scheduled-wazuh-alert-ingress:2026071500" "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: def test_wazuh_security_event_uses_secops_lifecycle() -> None:
assert classify_alert_early( assert classify_alert_early(

View File

@@ -220,6 +220,11 @@ spec:
value: "180" value: "180"
- name: AWOOOP_ANSIBLE_CHECK_MODE_STARTUP_SLEEP_SECONDS - name: AWOOOP_ANSIBLE_CHECK_MODE_STARTUP_SLEEP_SECONDS
value: "30" value: "30"
volumeMounts:
- name: service-registry
mountPath: /app/ops/config/service-registry.yaml
subPath: service-registry.yaml
readOnly: true
resources: resources:
requests: requests:
cpu: "100m" cpu: "100m"
@@ -272,3 +277,7 @@ spec:
matchLabels: matchLabels:
app: awoooi-worker app: awoooi-worker
topologyKey: kubernetes.io/hostname topologyKey: kubernetes.io/hostname
volumes:
- name: service-registry
configMap:
name: service-registry