fix(security): isolate allowlisted execution broker
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 1m9s
CD Pipeline / build-and-deploy (push) Successful in 4m35s
CD Pipeline / post-deploy-checks (push) Successful in 1m49s

This commit is contained in:
ogt
2026-07-11 01:04:23 +08:00
parent 40e3e281c3
commit c185d7544b
21 changed files with 1883 additions and 331 deletions

View File

@@ -0,0 +1,100 @@
"""Dedicated allowlisted Ansible execution broker process."""
from __future__ import annotations
import asyncio
import signal
from pathlib import Path
from typing import Any
import structlog
from src.core.config import settings
from src.db.base import close_db
from src.jobs.awooop_ansible_check_mode_job import (
run_awooop_ansible_check_mode_loop,
)
logger = structlog.get_logger(__name__)
_HEALTH_PATH = Path("/tmp/executor-broker-healthy")
_READY_PATH = Path("/tmp/executor-broker-ready")
async def _heartbeat_loop(shutdown_event: asyncio.Event) -> None:
while not shutdown_event.is_set():
_HEALTH_PATH.touch()
try:
await asyncio.wait_for(shutdown_event.wait(), timeout=15)
except TimeoutError:
pass
async def _main() -> None:
if not settings.ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER:
raise RuntimeError("ansible_execution_broker_disabled_by_config")
ssh_key_path = Path(settings.AWOOOP_ANSIBLE_CHECK_MODE_SSH_KEY_PATH)
known_hosts_path = Path(settings.AWOOOP_ANSIBLE_CHECK_MODE_KNOWN_HOSTS_PATH)
if not ssh_key_path.is_file() or not known_hosts_path.is_file():
raise RuntimeError("ansible_execution_broker_transport_not_mounted")
shutdown_event = asyncio.Event()
def _shutdown_handler(signum: int, _frame: Any) -> None:
logger.info("ansible_execution_broker_shutdown_requested", signal=signum)
shutdown_event.set()
signal.signal(signal.SIGTERM, _shutdown_handler)
signal.signal(signal.SIGINT, _shutdown_handler)
_HEALTH_PATH.touch()
_READY_PATH.touch()
heartbeat_task = asyncio.create_task(
_heartbeat_loop(shutdown_event),
name="ansible_execution_broker_heartbeat",
)
execution_task = asyncio.create_task(
run_awooop_ansible_check_mode_loop(),
name="run_awooop_ansible_check_mode_loop",
)
shutdown_wait_task = asyncio.create_task(
shutdown_event.wait(),
name="ansible_execution_broker_shutdown_wait",
)
logger.info(
"ansible_execution_broker_started",
interval_seconds=settings.AWOOOP_ANSIBLE_CHECK_MODE_INTERVAL_SECONDS,
batch_limit=settings.AWOOOP_ANSIBLE_CHECK_MODE_BATCH_LIMIT,
allowed_risk_levels=(
settings.AWOOOP_ANSIBLE_CONTROLLED_APPLY_ALLOWED_RISK_LEVELS
),
worker_raw_credential_access=False,
)
try:
done, _pending = await asyncio.wait(
{execution_task, shutdown_wait_task},
return_when=asyncio.FIRST_COMPLETED,
)
if execution_task in done and not shutdown_event.is_set():
_READY_PATH.unlink(missing_ok=True)
await execution_task
raise RuntimeError("ansible_execution_broker_loop_exited_unexpectedly")
finally:
shutdown_wait_task.cancel()
execution_task.cancel()
heartbeat_task.cancel()
for task in (shutdown_wait_task, execution_task, heartbeat_task):
try:
await task
except asyncio.CancelledError:
pass
await close_db()
_HEALTH_PATH.unlink(missing_ok=True)
_READY_PATH.unlink(missing_ok=True)
logger.info("ansible_execution_broker_stopped")
if __name__ == "__main__":
asyncio.run(_main())

View File

@@ -623,22 +623,6 @@ async def _main() -> None:
interval_seconds=settings.AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_INTERVAL_SECONDS,
batch_limit=settings.AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_BATCH_LIMIT,
)
ansible_check_mode_task: asyncio.Task[Any] | None = None
if settings.ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER:
from src.jobs.awooop_ansible_check_mode_job import (
run_awooop_ansible_check_mode_loop,
)
ansible_check_mode_task = asyncio.create_task(
run_awooop_ansible_check_mode_loop(),
name="run_awooop_ansible_check_mode_loop",
)
logger.info(
"signal_worker_ansible_check_mode_loop_started",
interval_seconds=settings.AWOOOP_ANSIBLE_CHECK_MODE_INTERVAL_SECONDS,
batch_limit=settings.AWOOOP_ANSIBLE_CHECK_MODE_BATCH_LIMIT,
)
# Setup graceful shutdown
shutdown_event = asyncio.Event()
@@ -670,12 +654,6 @@ async def _main() -> None:
await ansible_candidate_backfill_task
except asyncio.CancelledError:
pass
if ansible_check_mode_task is not None:
ansible_check_mode_task.cancel()
try:
await ansible_check_mode_task
except asyncio.CancelledError:
pass
await worker.stop()
await close_worker_redis_pool() # 關閉 Worker 專屬連線
await close_redis_pool()