feat(smtp_relay): move probe forwarding to realism worker via bus

Attacker probe emails are now forwarded by the master (realism worker)
rather than inside the MACVLAN container, which has no internet gateway.

- New smtp.probe.pending bus topic: ingester publishes when smtp_relay
  message_stored fires; worker subscribes and does the actual delivery
- decnet/orchestrator/drivers/smtp_relay.py: pure-sync forward_probe()
  reads the .eml from disk and sends via smtplib on a thread executor
- worker.py: _run_smtp_probe_listener + _handle_probe_pending subtask;
  limit enforced via count_probe_relays() (DB-backed, restart-safe)
- bounties.py: count_probe_relays() query on probe_relay bounty type
- fleet.py: get_fleet_decky_by_name() to pull service config from DB
- services/smtp_relay.py: upstream_* and probe_limit fields defined in
  config_schema but NOT injected into container env (credentials stay
  out of docker env vars)
- ingester.py: stripped of smtplib; publishes probe.pending and exits
- tests: assert upstream keys absent from container environment
This commit is contained in:
2026-04-30 12:10:58 -04:00
parent 4c0a1309f0
commit 8ae7b9636e
8 changed files with 231 additions and 39 deletions

View File

@@ -102,18 +102,6 @@ class SMTPRelayService(BaseService):
fragment["environment"]["SMTP_BANNER"] = cfg["banner"]
if "mta" in cfg:
fragment["environment"]["SMTP_MTA"] = cfg["mta"]
if "upstream_host" in cfg:
fragment["environment"]["SMTP_UPSTREAM_HOST"] = cfg["upstream_host"]
if "upstream_port" in cfg:
fragment["environment"]["SMTP_UPSTREAM_PORT"] = str(cfg["upstream_port"])
if "upstream_user" in cfg:
fragment["environment"]["SMTP_UPSTREAM_USER"] = cfg["upstream_user"]
if "upstream_pass" in cfg:
fragment["environment"]["SMTP_UPSTREAM_PASS"] = cfg["upstream_pass"]
if "upstream_sender" in cfg:
fragment["environment"]["SMTP_UPSTREAM_SENDER"] = cfg["upstream_sender"]
if "probe_limit" in cfg:
fragment["environment"]["SMTP_PROBE_LIMIT"] = str(cfg["probe_limit"])
return fragment
def dockerfile_context(self) -> Path: