docker-mailserver 安全加固指南
docker-mailserver 安全加固指南:TLS 证书接入、DKIM/SPF/DMARC 三件套实测验证(Authentication-Results 头字段)、fail2ban 爆破封禁、Rspamd 调优与端口收敛,全部实测。
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 | 无 STARTTLS | 250-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 段)才复现出完整三件套判定——生产如需对内网来源也强制校验,需在 Rspamdlocal.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/