docker-mailserver 安全加固指南

docker-mailserver 安全加固指南:TLS 证书接入、DKIM/SPF/DMARC 三件套实测验证(Authentication-Results 头字段)、fail2ban 爆破封禁、Rspamd 调优与端口收敛,全部实测。

docker-mailserverdocker-mailserver2026/10/18

docker-mailserver 安全加固指南

‍‍​​‌​> 目标:给 docker-mailserver(DMS)建立完整的邮件安全基线——身份认证与访问控制、DKIM/SPF/DMARC 发件认证三件套、TLS 传输加密、认证爆破封禁、反垃圾调优与端口收敛。全部命令、信头字段与日志回显在 Ubuntu 22.04 + DMS 16.0.1 实测,邮件主机名 mail.example.com、邮件域 example.com,数据目录 /opt/data/docker-mailserver/。

一、环境信息

  • 操作系统:Ubuntu 22.04;docker-mailserver:16.0.1(Postfix 3.10.13 / Dovecot 2.4.1-4 / Rspamd 4.1.5 / ClamAV 1.4.3 实测版本)
  • 基线部署:《docker-mailserver Docker Compose 部署指南》完成部署,Rspamd 单栈 + ClamAV 已启用
  • 本指南完成的加固项(全部实测):TLS 证书接入(5.2)、DKIM 签名与 DNS 发布(3.1~3.2)、SPF/DMARC 记录(3.2)、Authentication-Results 头启用(3.3)、fail2ban 爆破封禁(4.1)、发件人防伪造(4.2)、端口收敛(5.1)
  • 实验辅助:一台独立测试机(地址 100.64.0.20,正反向 DNS 为 client.external.test)用于模拟外部发件方与爆破源;内部 DNS 发布 example.com 测试区记录(3.2 的记录值即实测所用)
  • 日志文件:/var/log/mail/mail.log;管理脚本:/opt/data/docker-mailserver/setup.sh

二、身份认证与访问控制

加固原则:收信放开、发信收紧——25 端口只收不报(不提供认证),提交与收信必须过认证与 TLS。

2.1 端口能力面实测(认证面收敛)

25 端口(收信)EHLO 能力面实测(无证书状态):

250-SIZE 10240000
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 CHUNKING

要点:25 端口无 AUTH 能力——认证提交只走 587/465;587 端口认证能力实测(EHLO 含 250-AUTH LOGIN PLAIN、250-STARTTLS),且认证应强制在 TLS 内进行(DMS 默认关闭明文认证)。对 25 端口发起认证的请求不受支持,杜绝收信面暴露凭据。

2.2 账号口令与访问面

  • 口令策略由账号数据文件管理(哈希存储于 config/postfix-accounts.cf,实测非明文);改密用 ./setup.sh email update <地址> <新密码>;
  • 发送权限限制:./setup.sh email restrict add send <地址> 可冻结可疑账号的发送权(setup.sh help 实测命令树中有 restrict <add|del|list> <send|receive>);
  • 管理面无 Web 入口(DMS 设计即 CLI 管理),不存在管理台弱口令暴露面。

三、邮件认证三件套:DKIM / SPF / DMARC 实测

三件套解决"这封信真是 example.com 发的吗":DKIM 是密钥签名,SPF 是 IP 授权表,DMARC 是前两者的裁决策略。DMS 16 由 Rspamd 一体承担签发与校验(双栈并存的教训见《docker-mailserver 16.0.1 配置参数详解》5.1)。

3.1 DKIM 密钥生成与签名实测

./setup.sh config dkim

实测结果:生成 RSA-2048 密钥(selector mail),文件落盘 config/rspamd/dkim/(mail.example.com.key/.pub 与 DNS 记录文本),同时打印 mail._domainkey.example.com 的 TXT 记录全文。此后本地发出的信自动附加签名,实测投递文件信头(节选):

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=mail;

签名有效性用 Rspamd 自验(实测符号输出):

cat <邮件文件> | rspamc -h 127.0.0.1:11334 symbols
R_DKIM_ALLOW[example.com:s=mail](0.00)[]

⚠️ 实测边界发现:DNS 记录发布之前,同一封信自验结果是 R_DKIM_PERMFAIL + DMARC_POLICY_REJECT——签名已存在但公网查不到公钥。这正是三件套必须配 DNS 记录的原因(见 3.2)。

3.2 DNS 记录发布(实测区记录)

在域名 DNS 处发布以下三条记录(实测区记录值,生产将 IP 换为服务器公网地址):

; SPF —— 授权 100.64.0.20 为本域合法发信 IP
example.com.           TXT  "v=spf1 ip4:100.64.0.20 ~all"

; DMARC —— 裁决策略
_dmarc.example.com.    TXT  "v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com; adkim=r; aspf=r"

; DKIM —— 3.1 生成的公钥(selector mail)
mail._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=<MIIBIjANBg...公钥串>"

💡 技术拓展:DMARC 的 p= 有三档——none(只报告不处置)、quarantine(可疑隔离)、reject(直接拒收)。对照表:| 策略 | 对 SPF/DKIM 全失败的信 | 适用阶段 | |---|---|---| | p=none | 正常投递,仅收 rua 报告 | 上线初期观察 | | p=quarantine | 改投/拒收(由评分联动) | 加固期 | | p=reject | 无条件拒收 | 信任稳定后 |

本实测环境采用 p=quarantine(加固期姿态),实测其触发了真实拒信(3.3 场景 C)。

3.3 三场景收发实测(头字段为实测照抄)

开启 Authentication-Results 头(方法见《docker-mailserver 16.0.1 配置参数详解》5.6)后,用测试机做三个场景:

场景 A:授权 IP 发本域信(应全 pass)——入站信头实测节选:

Authentication-Results: mail.example.com; dmarc=pass (policy=quarantine) header.from=example.com
X-Spamd-Result: default: False [2.90 / 15.00]; R_SPF_ALLOW(-0.20)[+ip4:100.64.0.20];
    DMARC_POLICY_ALLOW(-0.50)[example.com]; R_DKIM_NA(0.00)[example.com]

spf=pass、dmarc=pass(dkim=none 是因该测试信未签名,SPF 已足够支撑 DMARC 通过),评分 2.90 远低于 15.00 拒信线 → 正常投递。

场景 B:冒名 gmail.com(softfail → 隔离)——实测信头:

Authentication-Results: mail.example.com; dmarc=fail (policy=none) header.from=gmail.com
X-Spam: Yes
X-Spamd-Result: default: True [10.00 / 15.00]; R_SPF_SOFTFAIL(0.00)[no match: ...];
    DMARC_NA(0.00)[gmail.com]; FAKE_GMAIL(5.50)[...]

评分 10.00 触发垃圾处置,实测整封信被改投收件人 .Junk/ 目录(Sieve 联动)。

场景 C:未授权 IP 冒充本域(DMARC p=quarantine 发威)——实测会话直接被拒:

<** 554 5.7.1 Spam message rejected

Postfix 侧日志实测(NOQUEUE 拒收记录):

2026-10-18 08:33:39+08:00 mail postfix/smtpd[...]: NOQUEUE: milter-reject: END-OF-MESSAGE from other.external.test[100.64.0.30]: 554 5.7.1 Spam message rejected; from=<ceo@example.com> to=<admin@example.com>

三件套闭环结论(全部实测复现):授权来源 pass 投递、冒名第三方隔离 Junk、伪造本域当场拒信。

四、访问控制强化

4.1 fail2ban 认证爆破封禁实测

启用:mailserver.env 置 ENABLE_FAIL2BAN=1(需实例具备 NET_ADMIN 能力,见《docker-mailserver Docker Compose 部署指南》4.2),重建后 jail 就绪(实测):

fail2ban-client status
Status
|- Number of jail:      3
`- Jail list:   custom, dovecot, postfix

阈值实测:fail2ban-client get dovecot maxretry → 6;bantime → 604800(7 天)。

爆破测试:从测试机对 587 端口连续 7 次错误密码认证(实测日志行):

warning: ...[100.64.0.20]: SASL LOGIN authentication failed: (reason unavailable), sasl_username=admin@example.com

封禁生效(实测状态输出):

fail2ban-client status postfix
Status for the jail: postfix
|- Filter
|  |- Currently failed: 3
|  |- Total failed:     7
|  `- File list:        /var/log/mail.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     1
   `- Banned IP list:   100.64.0.20

封禁效果实测:该来源后续任何连接在握手前被防火墙丢弃(swaks 停在 === Trying 100.64.0.10:25... 无响应)。

解封(实测回显):

./setup.sh fail2ban                 # 查看封禁列表
./setup.sh fail2ban unban 100.64.0.20
Banned in postfix: 100.64.0.20
Unbanned IP from custom: 0
Unbanned IP from dovecot: 0
Unbanned IP from postfix: 1

解封后连接立即恢复(实测 220 握手成功)。封禁历史落库 state/lib-fail2ban/fail2ban.sqlite3(实测 73728 字节)可审计。

4.2 发件人防伪造(SPOOF_PROTECTION)

mailserver.env 置 SPOOF_PROTECTION=1 重建后,认证身份与 MAIL FROM 不匹配的提交被拒(实测):

553 5.7.1 <user@example.com>: Sender address rejected: not owned by user admin@example.com

配合第三章 DMARC 防线(外部伪造 → 554 拒信),本域地址的内外两条伪造路径均被实测封死。

五、传输加密(TLS)与网络收敛

5.1 端口收敛清单

实测收敛策略(结合《docker-mailserver Docker Compose 部署指南》4.6 端口表):

  • 只暴露必需端口:25(收信)/587(提交)/143(IMAP)三个功能口 + TLS 启用后的 465/993;995/110/4190 不对外则不映射;
  • 25 端口只收不发:无 AUTH 能力(2.1 实测),杜绝收信面凭据泄露;
  • 信任网段保持最小:PERMIT_DOCKER=none(模板默认,实测保持),非信任来源投递非本域一律 554 Relay access denied(实测日志:NOQUEUE: reject: RCPT from ...: 554 5.7.1 <nobody@dead.test>: Relay access denied);
  • 465/993 属隐式 TLS 端口,建议客户端优先使用(993 实测监听见 5.2)。

5.2 TLS 证书接入实测(manual 方式)

证书准备(自签证书实测命令,生产替换为 Let's Encrypt/商业证书,SSL_TYPE=letsencrypt 走自动路径):

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
  -keyout privkey.pem -out fullchain.pem \
  -subj '/CN=mail.example.com' -addext 'subjectAltName=DNS:mail.example.com'
chmod 600 privkey.pem

实测生成:subject=CN = mail.example.com、SAN DNS:mail.example.com、有效期 2026-10-18 至 2036-10-15;私钥权限 600。证书放入数据目录 ssl/ 并配置(SSL_TYPE=manual、SSL_CERT_PATH=/etc/dms/tls/fullchain.pem、SSL_KEY_PATH=/etc/dms/tls/privkey.pem)后重建实例。

加固前后对比(全部实测):

验证项加固前加固后
启动日志自签临时证书警告警告消失
IMAPS 993未监听LISTEN 0 100 0.0.0.0:993
25 端口 EHLO无 STARTTLS250-STARTTLS
587 握手—TLSv1.3(见下)

587 端口握手实测:

openssl s_client -starttls smtp -connect 127.0.0.1:587
subject=CN = mail.example.com
Verification error: self-signed certificate
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

(Verification error 为自签证书固有提示;生产证书经 Let's Encrypt 链验证后消失。)TLS 内认证提交端到端实测:swaks 走 --tls + AUTH 成功发信(=== TLS started with cipher TLSv1.3:TLS_AES_256_GCM_SHA384:256),投递文件带 DKIM 签名(DKIM-Signature: ... d=example.com; s=mail)。IMAP 侧实测:openssl s_client -starttls imap -connect 127.0.0.1:143 登录成功并返回文件夹列表(a1 OK Logged in、* LIST (\HasNoChildren \Junk) "." Junk 等)。

六、审计日志

邮件安全的审计面由三类日志构成(均实测可用):

  • Postfix/Dovecot 主审计:/var/log/mail/mail.log——认证失败(SASL LOGIN authentication failed)、拒信(NOQUEUE: reject/milter-reject)、投递状态(status=sent/deferred)全在这条流(回显见 3.3/4.1 与《docker-mailserver 日常运维手册》7.2);
  • Rspamd 判定审计:每封信的评分与符号明细随信存档(X-Spamd-Result 头)并可实时查询(rspamc counters 实测有计数输出);
  • 封禁审计:fail2ban 封禁历史落 state/lib-fail2ban/fail2ban.sqlite3,封禁/解封动作本身也有 setup.sh fail2ban 回显留痕(4.1)。

⚠️ 实测发现(内部网段跳过校验):Rspamd 对私有网段来源的连接默认跳过 SPF/DMARC 校验(设计使然,内网互信)。本实测为此把外部测试机放到 100.64.0.0/16(非 RFC1918 段)才复现出完整三件套判定——生产如需对内网来源也强制校验,需在 Rspamd local.d/ 调整 local_networks 或用 dmarc 模块的 ignore_local 相关项,并重建/重载生效。

七、服务降权与最小权限

进程账户实测(ps -eo user,comm 节选):

_rspamd  rspamd
clamav   clamd
postfix  pickup
postfix  qmgr
root     dovecot
root     redis-server
  • Postfix(postfix 账户)、Rspamd(_rspamd)、ClamAV(clamav)均以专用低权账户运行,符合最小权限;Dovecot 主进程与 Redis 以 root 常驻(其工作子进程降权,Dovecot 官方架构如此),属上游组件默认形态;
  • 实例能力面收敛:默认无特殊能力,仅 fail2ban 需要 NET_ADMIN(防火墙封禁用)——未启用 fail2ban 时应从部署配置中移除该能力项;
  • 私钥文件权限实测 600;config/postfix-accounts.cf(口令哈希)目录权限建议 700;
  • 各组件状态隔离在 state/ 独立子目录(lib-rspamd/、lib-postfix/、lib-clamav/ 等,实测清单见《docker-mailserver Docker Compose 部署指南》第五章),互不越界。

八、补丁与版本策略

  • 版本基线:v16.0.1(2026-09-04 发布,GitHub Releases 实查);发布节奏为年度大版本 + 周月级补丁(近三次:v15.1.0 2025-08-12 → v16.0.0 2026-08-30 → v16.0.1 2026-09-04),安全补丁跟进压力小;
  • 安全组件更新:ClamAV 病毒库自动更新(state/lib-clamav/ 实测 112M 且持续增长即库更新所致);Postfix/Dovecot/Rspamd 随镜像更新,升级即换标签重建(流程与回滚见《docker-mailserver 日常运维手册》第六章);
  • 漏洞跟踪:关注 GitHub Releases 与安全公告页;本文实测期间最新版即 v16.0.1,无未修复高危公告(2026-10-18 查询);
  • 加固自查:每季度重跑 2.1 能力面、3.3 三场景、4.1 爆破测试与 5.2 握手验证,确认安全基线未漂移。

九、常见问题

Q1:openssl s_client 提示 Verification error: self-signed certificate? 自签证书的固有现象(5.2 实测原样),传输加密本身已生效(TLSv1.3);需要链验证的生产证书请用 Let's Encrypt(SSL_TYPE=letsencrypt)或商业证书(manual 路径)。

Q2:内网测试机发信怎么测不出 SPF/DMARC 结果? Rspamd 对私有网段来源跳过校验(第六章实测发现)。测试请让发件方使用非 RFC1918 地址段(如 100.64.0.0/16 运营商级 NAT 段),或按提示调整 local_networks。

Q3:DKIM 自验 R_DKIM_PERMFAIL? DNS 记录未发布或未生效(3.1 实测边界)。核对 mail._domainkey.example.com TXT 记录内容与 config/rspamd/dkim/ 生成的记录文本一致、公钥串未被截断(记录超过 255 字符需按 TXT 规则分段)。

Q4:误把办公网封禁了怎么解? ./setup.sh fail2ban 确认封禁列表 → ./setup.sh fail2ban unban <IP> 解封(4.1 实测回显);长期误封可将办公网段加入白名单(fail2ban ignoreip,经 config/ 覆盖 fail2ban jail 配置实现)。

Q5:启动警告 Rspamd 与旧栈并存? 把 ENABLE_AMAVIS/ENABLE_OPENDKIM/ENABLE_OPENDMARC/ENABLE_POLICYD_SPF 全部置 0 重建(实测警告消失;DKIM/SPF/DMARC 由 Rspamd 统一承担,完整对比见《docker-mailserver 16.0.1 配置参数详解》5.1)。

Q6:DMARC 策略应该选哪档? 按 3.2 拓展表演进:上线观察期 p=none(只收报告)→ 确认 SPF/DKIM 记录覆盖所有合法发信源后切 p=quarantine → 运行稳定后 p=reject。本实测 p=quarantine 已能拒掉伪造本域的入站信(3.3 场景 C)。

十、参考链接

  • 官方安全相关文档:https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/
  • DKIM/SPF/DMARC 官方说明:https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/
  • fail2ban 集成说明:https://docker-mailserver.github.io/docker-mailserver/latest/config/security/fail2ban/
  • TLS 证书配置:https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl/
  • Rspamd 模块文档(milter_headers/actions/dmarc):https://www.rspamd.com/doc/modules/