From 8a6d632ab0e0c5e7dcad2501524e9cd8e07679d2 Mon Sep 17 00:00:00 2001 From: anti Date: Sun, 26 Apr 2026 05:20:54 -0400 Subject: [PATCH] feat(deploy): systemd unit for decnet-enrich + register in worker panel Mirrors decnet-reuse-correlator.service.j2: same hardening posture (NoNewPrivileges, ProtectSystem=full, etc.), same restart policy, same log file convention. The decnet init renderer picks it up automatically via the decnet-*.service.j2 glob. Also reconciles a naming inconsistency I shipped earlier: the heartbeat name was 'intel' (the package) but the CLI command and unit are 'enrich' (the action). Renamed the heartbeat to 'enrich' so the workers panel displays the same string the operator types and the same string in the systemd unit file. Convention across the project: heartbeat name = registry key = unit basename = CLI command name. Registers 'enrich' in worker_registry.KNOWN_WORKERS and in the start-all preferred order. The decnet.target Wants= list also picks up the new unit so 'systemctl start decnet.target' brings everything up together. --- decnet/intel/worker.py | 6 +-- .../router/workers/api_start_all_workers.py | 1 + decnet/web/worker_registry.py | 1 + deploy/decnet-enrich.service.j2 | 47 +++++++++++++++++++ deploy/decnet.target | 1 + 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 deploy/decnet-enrich.service.j2 diff --git a/decnet/intel/worker.py b/decnet/intel/worker.py index 51495adb..cfb5e1b4 100644 --- a/decnet/intel/worker.py +++ b/decnet/intel/worker.py @@ -117,7 +117,7 @@ async def run_intel_loop( wake_tasks: list[asyncio.Task] = [] heartbeat_task: Optional[asyncio.Task] = None try: - candidate = get_bus(client_name="intel") + candidate = get_bus(client_name="enrich") await candidate.connect() bus = candidate wake_tasks.append(asyncio.create_task( @@ -127,10 +127,10 @@ async def run_intel_loop( _wake_on(bus, wake, _topics.attacker(_topics.ATTACKER_SCORED)), )) heartbeat_task = asyncio.create_task( - _run_health_heartbeat(bus, "intel"), + _run_health_heartbeat(bus, "enrich"), ) wake_tasks.append(asyncio.create_task( - _run_control_listener_signal(bus, "intel"), + _run_control_listener_signal(bus, "enrich"), )) except Exception as exc: # noqa: BLE001 log.warning( diff --git a/decnet/web/router/workers/api_start_all_workers.py b/decnet/web/router/workers/api_start_all_workers.py index 2177405d..eeab98a9 100644 --- a/decnet/web/router/workers/api_start_all_workers.py +++ b/decnet/web/router/workers/api_start_all_workers.py @@ -26,6 +26,7 @@ _PREFERRED_ORDER: tuple[str, ...] = ( "prober", "mutator", "reuse-correlator", + "enrich", "webhook", ) diff --git a/decnet/web/worker_registry.py b/decnet/web/worker_registry.py index e823f307..f2158ef1 100644 --- a/decnet/web/worker_registry.py +++ b/decnet/web/worker_registry.py @@ -39,6 +39,7 @@ KNOWN_WORKERS: tuple[str, ...] = ( "prober", "mutator", "reuse-correlator", # credential-reuse pass — bus-woken on credential.captured + "enrich", # threat-intel enrichment — bus-woken on attacker.observed/scored "webhook", # external SIEM/SOAR egress — bus consumer → HMAC HTTP POSTs "agent", "forwarder", diff --git a/deploy/decnet-enrich.service.j2 b/deploy/decnet-enrich.service.j2 new file mode 100644 index 00000000..344c1e4c --- /dev/null +++ b/deploy/decnet-enrich.service.j2 @@ -0,0 +1,47 @@ +[Unit] +Description=DECNET Threat-Intel Enrichment (GreyNoise + AbuseIPDB + abuse.ch) +Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#intel-enrichment +After=network-online.target decnet-bus.service +Wants=network-online.target decnet-bus.service + +[Service] +Type=simple +User={{ user }} +Group={{ group }} +WorkingDirectory={{ install_dir }} +EnvironmentFile=-{{ install_dir }}/.env.local +Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.enrich.log +# Subscribes to attacker.observed and attacker.scored; falls back to a 60s +# slow-tick poll when the bus is idle or unavailable. Per attacker IP fans +# out across the configured intel providers, writes the aggregate verdict +# to attacker_intel, and publishes attacker.intel.enriched. +# +# Free-tier API keys are read from .env.local: +# DECNET_GREYNOISE_API_KEY= (optional, lifts rate limit) +# DECNET_ABUSEIPDB_API_KEY= (required for AbuseIPDB lookups) +# DECNET_THREATFOX_API_KEY= (optional, lifts rate limit) +ExecStart={{ venv_dir }}/bin/decnet enrich +StandardOutput=append:/var/log/decnet/decnet.enrich.log +StandardError=append:/var/log/decnet/decnet.enrich.log + +CapabilityBoundingSet= +AmbientCapabilities= + +# Security Hardening +NoNewPrivileges=yes +ProtectSystem=full +ProtectHome=read-only +PrivateTmp=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +RestrictSUIDSGID=yes +LockPersonality=yes +ReadWritePaths={{ install_dir }} /var/log/decnet + +Restart=on-failure +RestartSec=5 +TimeoutStopSec=15 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/decnet.target b/deploy/decnet.target index 5b51ccbe..3fbf4f8f 100644 --- a/deploy/decnet.target +++ b/deploy/decnet.target @@ -14,6 +14,7 @@ Wants=decnet-bus.service \ decnet-prober.service \ decnet-mutator.service \ decnet-reuse-correlator.service \ + decnet-enrich.service \ decnet-webhook.service After=decnet-bus.service