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.
This commit is contained in:
2026-04-26 05:20:54 -04:00
parent 4ec0dd75c8
commit 8a6d632ab0
5 changed files with 53 additions and 3 deletions

View File

@@ -117,7 +117,7 @@ async def run_intel_loop(
wake_tasks: list[asyncio.Task] = [] wake_tasks: list[asyncio.Task] = []
heartbeat_task: Optional[asyncio.Task] = None heartbeat_task: Optional[asyncio.Task] = None
try: try:
candidate = get_bus(client_name="intel") candidate = get_bus(client_name="enrich")
await candidate.connect() await candidate.connect()
bus = candidate bus = candidate
wake_tasks.append(asyncio.create_task( wake_tasks.append(asyncio.create_task(
@@ -127,10 +127,10 @@ async def run_intel_loop(
_wake_on(bus, wake, _topics.attacker(_topics.ATTACKER_SCORED)), _wake_on(bus, wake, _topics.attacker(_topics.ATTACKER_SCORED)),
)) ))
heartbeat_task = asyncio.create_task( heartbeat_task = asyncio.create_task(
_run_health_heartbeat(bus, "intel"), _run_health_heartbeat(bus, "enrich"),
) )
wake_tasks.append(asyncio.create_task( wake_tasks.append(asyncio.create_task(
_run_control_listener_signal(bus, "intel"), _run_control_listener_signal(bus, "enrich"),
)) ))
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
log.warning( log.warning(

View File

@@ -26,6 +26,7 @@ _PREFERRED_ORDER: tuple[str, ...] = (
"prober", "prober",
"mutator", "mutator",
"reuse-correlator", "reuse-correlator",
"enrich",
"webhook", "webhook",
) )

View File

@@ -39,6 +39,7 @@ KNOWN_WORKERS: tuple[str, ...] = (
"prober", "prober",
"mutator", "mutator",
"reuse-correlator", # credential-reuse pass — bus-woken on credential.captured "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 "webhook", # external SIEM/SOAR egress — bus consumer → HMAC HTTP POSTs
"agent", "agent",
"forwarder", "forwarder",

View File

@@ -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

View File

@@ -14,6 +14,7 @@ Wants=decnet-bus.service \
decnet-prober.service \ decnet-prober.service \
decnet-mutator.service \ decnet-mutator.service \
decnet-reuse-correlator.service \ decnet-reuse-correlator.service \
decnet-enrich.service \
decnet-webhook.service decnet-webhook.service
After=decnet-bus.service After=decnet-bus.service