Skip to content

Port ip-enrichment-worker and correlator-worker to the Rust tier (#1610) - #1615

Merged
Xore merged 2 commits into
port-foundationfrom
worktree-issue-1610-enrichment-correlator
Aug 18, 2026
Merged

Port ip-enrichment-worker and correlator-worker to the Rust tier (#1610)#1615
Xore merged 2 commits into
port-foundationfrom
worktree-issue-1610-enrichment-correlator

Conversation

@Xore

@Xore Xore commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Summary

Ports the two additional workers surfaced while surveying the codebase beyond #1610's originally-named inventory (ip-enrichment-worker, correlator-worker) — the last two of six workers total, on top of port-foundation (post-#1613/#1614).

  • ip-enrichment-worker (Go, 2,174 lines — the largest single worker in this whole migration) — architecturally distinct from every other worker ported so far: it never touches Elasticsearch at all. It's a pure host-log-file rewriter that tails ~20 sensors' raw JSON logs, resolves the real attacker IP for connections that only ever see the WireGuard tunnel-peer address (a portbridge via_port join, done at ingest time so every dashboard replica sees an already-correct src_ip), and promotes cross-sensor canonical_* fields. Writes to a parallel output file Filebeat tails instead of the raw one. New, separate compose service backend-worker-enrichment with network_mode: none — carried over unchanged from the original's own compose block, a deliberate security posture (this loop needs zero network access, incompatible with backend-worker's other ES-dependent loops).
  • correlator-worker (Go, 953 lines) — recomputes campaigns-v1 (CIDR-grouped) and attacker-clusters-v1 (fingerprint/payload/ASN/provider-grouped) from scratch every cycle, entirely via Elasticsearch-native aggregations (including a server-side Painless script for credential-pair grouping) — no raw-document paging needed. Added a delete_by_query_except primitive to es.rs for the stale-document sweep this worker's full-resync pattern needs. Added to the existing backend-worker (pure ES, no host mounts).

Both were caught by re-surveying every honeypot-* worker/importer directory in the tree rather than assuming #1610's example list was exhaustive — ip-enrichment-worker in particular is foundational (it's what produces the canonical_* fields attacker-identity-worker/agent-intrusion-worker/correlator-worker all read).

This completes every worker discovered in this migration sweep. Not done (per #1610's own remaining text, out of scope for all of #1610's PRs): the BFF scalability hard requirements (Node cluster mode, backpressure, streaming, horizontal-readiness, load-test gate).

Test plan

  • cargo build clean on every commit (no new warnings beyond one pre-existing unrelated canarytokens.rs warning)
  • cargo test — 125/125 passing (31 new across these two workers' ported test suites)
  • docker compose --profile next config resolves all new/changed services cleanly
  • Cross-checked ES aggregation query bodies, index/field names, and the delete-by-query primitive against the real elasticsearch crate source and the existing Go source throughout
  • No live ES/filesystem environment was reachable in this sandbox — live smoke testing, and the "parity tests against the current worker's observable outputs" Port follow-up: migrate every worker into the new BFF/service architecture; BFF scalability #1610 itself calls for, are still needed before either old worker retires

🤖 Generated with Claude Code

Xore and others added 2 commits August 18, 2026 22:03
Moves the portbridge via_port -> real attacker IP join from dashboard
read-time to ingest time. Unlike every other #1610 worker, this one
never touches Elasticsearch or the network at all: it tails ~19 raw
sensor JSON log files, rewrites any line whose src_ip is still the
WireGuard tunnel peer address (10.8.0.1) to the real attacker IP
(joined against portbridge's own connection log by source port), and
promotes cross-sensor canonical_* fields (creds/command/fingerprint/
payload hash/ATT&CK technique). The rewritten stream is written to
OUT_DIR for Filebeat to tail instead of the raw file -- this port
doesn't touch Filebeat's own config, only keeps producing the same
enriched-file format at the same path.

New src/ip_enrichment/ module: mod.rs (source discovery/orchestration),
tail.rs (offset-persisted incremental reads), rotate.rs (self-rotating
output writer), pending.rs (retry queue for via_port misses), viamap.rs
(the portbridge via_port map, including the incremental-refresh
ViaMapBuilder a prior production incident (#1206) made necessary --
re-parsing both portbridge generations from scratch every 2s throttled
the container's own CPU limit badly enough to push cowrie's join
success rate near zero after ~2 days uptime), tftp.rs (the separate
tftp-relay session join for dionaea's TFTP events), canonical.rs (all
13 per-persona field-promotion cases), attck.rs (the ATT&CK technique
subset derivable from promoted canonical fields), sensors.rs (the
generic src_ip join plus five bespoke per-sensor enrichers for
beelzebub/hellpot/galah/sentrypeer/wordpot, each adapting a raw log
shape the generic path can't parse).

New backend-worker-enrichment compose service, network_mode: none
(carried over unchanged from the Go worker's own compose block -- a
deliberate security posture, not an oversight) -- distinct from
backend-worker (whose other loops all need to reach Elasticsearch).

23 new unit tests, all passing (117/117 crate-wide). The old Go worker
stays running unchanged -- parity port, not cutover.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
campaigns-v1 (CIDR-grouped) and attacker-clusters-v1 (fingerprint/payload/
ASN/provider-grouped) correlation, entirely via Elasticsearch-native
aggregations -- no raw-document paging or PIT needed at all, the simplest
of the six #1610 workers ported this pass. Two sibling ip_prefix
aggregations (IPv4 /24, IPv6 /64) with a scripted cred_pairs terms
sub-aggregation, plus four cluster terms aggregations gated on a real
(not just min_doc_count) >=2-unique-IP threshold. Both indices are fully
resynced each cycle: upsert this cycle's fresh docs, then delete
everything else via a new Es::delete_by_query_except primitive
(elasticsearch::DeleteByQueryParts + Conflicts::Proceed, verified against
the vendored crate source). Runs as the `correlator` WORKER_LOOPS entry
on backend-worker -- pure ES, no host mounts, no local state, same shape
as its attacker-identity/agent-intrusion siblings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@Xore
Xore merged commit 4684243 into port-foundation Aug 18, 2026
88 checks passed
@Xore
Xore deleted the worktree-issue-1610-enrichment-correlator branch August 18, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant