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
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
"""
|
|
Tests for SMTP Relay service.
|
|
"""
|
|
|
|
from decnet.services.smtp_relay import SMTPRelayService
|
|
|
|
def test_smtp_relay_compose_fragment():
|
|
svc = SMTPRelayService()
|
|
fragment = svc.compose_fragment("test-decky", log_target="log-server")
|
|
|
|
assert fragment["container_name"] == "test-decky-smtp_relay"
|
|
assert fragment["environment"]["SMTP_OPEN_RELAY"] == "1"
|
|
assert fragment["environment"]["LOG_TARGET"] == "log-server"
|
|
|
|
def test_smtp_relay_custom_cfg():
|
|
svc = SMTPRelayService()
|
|
fragment = svc.compose_fragment(
|
|
"test-decky",
|
|
service_cfg={"banner": "Welcome", "mta": "Postfix"}
|
|
)
|
|
assert fragment["environment"]["SMTP_BANNER"] == "Welcome"
|
|
assert fragment["environment"]["SMTP_MTA"] == "Postfix"
|
|
|
|
def test_smtp_relay_dockerfile_context():
|
|
svc = SMTPRelayService()
|
|
ctx = svc.dockerfile_context()
|
|
assert ctx.name == "smtp"
|
|
assert ctx.is_dir()
|
|
|
|
|
|
def test_smtp_relay_upstream_cfg_not_in_container_env():
|
|
"""Upstream relay config is stored in decky_config and consumed by the
|
|
realism worker — it must NOT be injected into the container environment
|
|
(credentials don't belong in container env vars)."""
|
|
svc = SMTPRelayService()
|
|
fragment = svc.compose_fragment(
|
|
"test-decky",
|
|
service_cfg={
|
|
"upstream_host": "smtp.sendgrid.net",
|
|
"upstream_port": 587,
|
|
"upstream_user": "apikey",
|
|
"upstream_pass": "SG.secret",
|
|
"probe_limit": 2,
|
|
},
|
|
)
|
|
env = fragment["environment"]
|
|
assert "SMTP_UPSTREAM_HOST" not in env
|
|
assert "SMTP_UPSTREAM_PORT" not in env
|
|
assert "SMTP_UPSTREAM_USER" not in env
|
|
assert "SMTP_UPSTREAM_PASS" not in env
|
|
assert "SMTP_PROBE_LIMIT" not in env
|
|
|
|
|
|
def test_smtp_relay_quarantine_bind_mount():
|
|
"""Full-message capture: each decky gets its own host quarantine dir
|
|
bind-mounted into the container, and the in-container path is exposed
|
|
via SMTP_QUARANTINE_DIR so the server can write .eml files."""
|
|
svc = SMTPRelayService()
|
|
fragment = svc.compose_fragment("test-decky")
|
|
volumes = fragment["volumes"]
|
|
assert len(volumes) == 1
|
|
host, container, mode = volumes[0].split(":")
|
|
assert host.endswith("/test-decky/smtp")
|
|
assert container == fragment["environment"]["SMTP_QUARANTINE_DIR"]
|
|
assert mode == "rw"
|