☰
Maddy 出站投递安全机制全解析:MX 认证与 TLS 强制(MTA-STS / DNSSEC / DANE)
2026/10/10 5:59:31 网站建设 项目流程
  • 后端
  • 通信

【免费下载链接】maddy

✉️ Composable all-in-one mail server.

项目地址:https://gitcode.com/gh_mirrors/ma/maddy
点击查看免费下载

本文是 maddy 邮件服务器出站投递安全体系的技术指南,围绕 docs/seclevels.md 展开,系统讲解 maddy 如何在服务器到服务器(server-server)SMTP 场景下应对"MX 记录认证"与"TLS 强制"两大核心安全问题,并深入剖析其安全等级(Security Level)与策略(Policy)的实现原理。读完本文,你将掌握 maddy 的mx_auth策略组配置方法、min_tls_level/min_mx_level的取值与含义,以及 MTA-STS、DNSSEC、DANE 各自能抵御何种攻击、又有哪些已知局限。

一、安全 SMTP 面临的两类问题

maddy 将出站投递安全拆解为两个相互独立的问题:MX 记录认证(MX record authentication)与TLS 强制(TLS enforcement)。两者分别回答"该把邮件发给谁"和"如何在传输中保证机密性与真实性"。

1.1 MX 记录认证:DNS 不可信的根源

当 MTA 需要向某个远端域名的邮箱投递邮件时,它通过查询收件人域名的 DNS MX 记录来发现目标邮件服务器。问题在于:DNS 本身没有任何密码学保护,任何恶意攻击者理论上都可以篡改 DNS 响应,使其指向任意服务器,而 MTA 会毫无察觉地使用该服务器。

解决该问题的两个协议:

  • MTA-STS(SMTP MTA Strict Transport Security):要求 MTA 将所使用的记录与收件人域名通过 HTTPS 发布的一组规则进行比对校验。
  • DNSSEC:对记录本身进行密码学签名,从根源上保证记录的完整性。

1.2 TLS 强制:STARTTLS 可被隐藏

默认情况下,服务器之间的 SMTP 传输是不加密的。如果远端服务器支持 TLS,它会通过名为 STARTTLS 的 ESMTP 扩展来通告这一能力;但控制通信信道的恶意攻击者可以隐藏对 STARTTLS 的支持,迫使发件方 MTA 退回明文传输。因此必须存在一条带外(out-of-band)的、经过认证的通道,用来指示 TLS 支持的存在并强制其使用。

解决该问题的两个机制:

  • MTA-STS:当其策略处于enforce模式时,MTA 被强制要求在与远端服务器投递消息时使用 TLS。
  • DANE(DNS-based Authentication of Named Entities):作用与 MTA-STS 类似,但改用 DNSSEC 签名的 TLSA 记录来实现。

二、maddy 的两级安全模型:MX 安全等级与 TLS 安全等级

maddy 用两个取值来刻画一条出站投递"有多安全":

  • MX 安全等级(MX security level):对应 MX 记录认证问题;
  • TLS 安全等级(TLS security level):对应 TLS 强制问题。

投递时,maddy 会为与远端服务器建立的连接按这两个维度进行"评级(ranked)",再将结果与一系列策略(包括本地配置)逐一比对:如果有效等级低于要求等级,连接会被关闭并尝试下一个候选服务器;如果所有候选都因此失败,投递即告失败(若在策略检查过程中出现临时错误,则投递被推迟/重试)。

从源码结构看,这两个等级在 framework/module/mxauth.go 中被定义为有序枚举:

const ( TLSNone TLSLevel = iota TLSEncrypted TLSAuthenticated ) const ( MXNone MXLevel = iota MX_MTASTS MX_DNSSEC )

等级从低到高排列,策略通过数值比较(mxLevel < l.minMXLevel、tlsLevel < l.minTLSLevel)判定是否达标,见 internal/target/remote/security.go 中localPolicy的CheckMX/CheckConn实现。

2.1 安全等级定义与防护能力总表

maddy 文档给出了下表,汇总各级别定义及其能提供的防护:

MX/TLS levelNoneEncryptedAuthenticated
None-PP
MTA-STS-PPA (见注 1)
DNSSEC-PPA

图例:P—— 抵御被动攻击(passive attacks);A—— 抵御主动攻击(active attacks)。

各级别的精确定义如下:

  • MX 等级 None:MX 候选来自收件人域名的 DNS 查询结果,未做任何额外检查。
  • MX 等级 MTA-STS:所使用的 MX 与收件人域名发布的 MTA-STS 策略匹配(即使该策略处于 testing 模式也算数)。
  • MX 等级 DNSSEC:MX 记录已签名(DNSSEC 验证通过)。
  • TLS 等级 None:建立了明文连接,TLS 不可用或建立失败。
  • TLS 等级 Encrypted:建立了 TLS 连接,但服务器证书未通过 X.509 与 DANE 验证。
  • TLS 等级 Authenticated:建立了 TLS 连接,且服务器证书通过了 X.509或DANE 验证。

注 1:能够持续控制网络连接的持久性攻击者可以干扰策略刷新,从而将防护降级为仅能抵御被动攻击——这正是 MTA-STS 相对 DANE 的固有弱点。

2.2 为什么"Encrypted"不等于"Authenticated"

从表中可以读出 maddy 安全模型的核心理念:加密(Encrypted)只能防窃听,认证(Authenticated)才能防中间人。证书验证失败(X.509 不过、无 DANE 匹配)时,maddy 会把等级降为 Encrypted 而非直接断连——因为加密仍优于明文;但若策略要求 Authenticated,则该连接会被拒绝使用。

这一降级行为在 internal/target/remote/connect.go 的connect函数中有完整实现:先尝试带 X.509 验证的 STARTTLS;若验证失败(isVerifyError),回退到InsecureSkipVerify的"无认证 TLS"(等级降为TLSEncrypted);若 TLS 整体失败,再回退明文(等级为TLSNone)。

三、maddy 安全策略体系:mx_auth 与五种策略模块

maddy 将上述机制抽象为"MX 认证策略(MXAuthPolicy)"。策略在配置中通过remote目标的mx_auth指令块启用,未指定mx_auth时默认不启用任何机制——文档特别提醒:这会令出站 SMTP 暴露于多种降级攻击之下,因此不推荐。

策略的启用方式与配置入口详见 docs/reference/targets/remote.md,基础示例:

mx_auth { dane mtasts }

若机制支持,可为其单独开配置块,例如:

mtasts { cache ram }

3.1 策略执行顺序:policy_group 的固定编排

各策略之间存在依赖关系(某些策略依赖先前策略的结果),因此 maddy 通过mx_auth策略组模块(internal/target/remote/policy_group.go)固定了应用顺序:

  1. mtasts—— 先于其他策略,其判定出的MX_MTASTS等级会阻止后续sts_preload生效;
  2. sts_preload—— 已废弃的 STS 预加载列表模块(仅存桩,见 internal/target/remote/security.go);
  3. dane—— 通过 TLSA 记录提升 TLS 等级;
  4. dnssec—— 通过签名 MX 记录提升 MX 等级;
  5. local_policy——必须放在最后,因为它要基于前面各策略设置的等级做最终门槛校验。

对应地,每个策略模块都实现了 framework/module/mxauth.go 中定义的MXAuthPolicy接口,核心方法包括:

  • Weight():策略的相对应用权重(0–1000);
  • PrepareDomain/PrepareConn:在 MX 查询 / 建连前异步预取必要数据(如 MTA-STS 策略、TLSA 记录);
  • CheckMX:判断策略是否允许使用某个 MX,并可提升 MX 等级;
  • CheckConn:判断策略是否允许使用这条连接,并可提升 TLS 等级;
  • Reset:为下一条消息重置内部状态。

实际投递流程中,internal/target/remote/connect.go 的attemptMX会先对每个候选 MX 依次执行所有策略的CheckMX,建立连接后再执行所有策略的CheckConn,最终把两个等级写入连接对象(conn.mxLevel、conn.tlsLevel),并记录到 OpenMetrics 指标(mxLevelCnt、tlsLevelCnt)。

3.2 MTA-STS 策略

MTA-STS 会检查收件人域名的 MTA-STS 策略,为投递提供认证与 TLS 强制,但如注 1 所述,对持久性主动攻击存在部分脆弱性。只要使用的 MX 与策略匹配(即便策略未处于 enforce 模式),MX 等级就会被置为mtasts。

配置项(docs/reference/targets/remote.md):

mtasts { cache fs fs_dir StateDirectory/mtasts_cache }
  • cache:取值fs|ram,默认fs。fs使用文件系统目录缓存,ram使用内存缓存。文档建议优先用fs,否则服务器重启会丢弃缓存、导致 MTA-STS 安全保护消失;内存缓存在高负载且运行稳定的配置中才有意义。
  • fs_dir:cache为fs时使用的缓存目录,默认StateDirectory/mtasts_cache。

实现细节(internal/target/remote/security.go):

  • 缓存在Init阶段按cache取值创建(mtasts.NewFSCache/mtasts.NewRAMCache),其解析器挂载 maddy 默认 DNS 解析器;
  • StartUpdater启动后台协程,启动时立即刷新一次缓存(考虑到可能停机多时),此后每 12 小时通过cache.Refresh()刷新;
  • CheckMX中,若 MX 不匹配策略且策略为ModeEnforce,直接返回 550/5.7.0 错误(Failed to establish the MX record authenticity (MTA-STS));非 enforce 模式仅记录日志并返回MXNone;
  • CheckConn中,enforce 模式下若 TLS 握手未完成,或证书链未通过验证(VerifiedChains == nil),均返回 451/4.7.1 错误强制 TLS。

3.3 DNSSEC 策略

DNSSEC 策略检查 MX 记录是否签名,若是则把 MX 等级置为dnssec。maddy自身并不验证 DNSSEC 签名,而是依赖上游解析器:验证失败时让查询失败,验证通过且区域已签名时设置 AD 标志。作为安全措施,若解析器不是 127.0.0.1 或 ::1,AD 标志会被忽略。另注意:DNSSEC 目前不支持 Windows 等没有标准格式/etc/resolv.conf的平台。配置极其简单:

dnssec { }

实现见 internal/target/remote/security.go:CheckMX直接依据调用方传入的dnssec布尔值返回MX_DNSSEC或MXNone。该布尔值来自 internal/target/remote/connect.go 的lookupMX:当配置了扩展解析器时调用extResolver.AuthLookupMX获取签名状态,否则使用普通LookupMX(此时dnssec恒为 false)。

3.4 DANE 策略

DANE 策略检查收件人 MX 的 TLSA 记录,提供抗降级的 TLS 强制(downgrade-resistant)。若存在有效且匹配、且 usage 类型为DANE-EE(3)或 DANE-TA(2)的 TLSA 记录,TLS 等级被置为authenticated。DANE 依赖 DNSSEC 支持才能工作:

dane { }

实现细节(internal/target/remote/security.go 与 internal/target/remote/dane.go):

  • discoverTLSA使用 EDNS 扩展解析器先校验 A/AAAA 记录的 AD 标志,若 A 记录未被 DNSSEC 认证则跳过 TLSA 查询(除非是 CNAME 场景且 CNAME 本身已签名),并遵循 RFC 7672 对非认证 RRset 的处理;
  • 任何 I/O 错误(含 SERVFAIL)按 RFC 7672 视为应延迟投递的临时错误;
  • verifyDANE只采信 usage 2/3、selector 0/1、matching type 0/1/2 的记录;若无可用记录则不做强制;有记录但HandshakeComplete为 false 时返回 550/5.7.1(TLS is required but unsupported or failed (enforced by DANE));
  • DANE-EE 记录直接对服务器叶子证书做 TLSA 匹配(不校验 SAN/CN、过期证书也可接受,符合 RFC 7672 3.1.1);
  • DANE-TA 记录则把匹配的 CA 证书加入信任根池,再走标准 X.509 链验证;
  • 没有任何记录匹配时返回 550/5.7.0No matching TLSA records。

3.5 本地策略 local_policy

本地策略检查有效 TLS / MX 等级(由其他策略设定)是否满足本地配置的门槛(docs/reference/targets/remote.md):

local_policy { min_tls_level none min_mx_level none }
  • min_tls_level:none|encrypted|authenticated,默认encrypted。设定所有出站消息要求的最低 TLS 安全等级。
  • min_mx_level:none|mtasts|dnssec,默认none。设定所有出站消息要求的最低 MX 安全等级。
  • 使用local_policy off等价于两项都设为none。

实现见 internal/target/remote/security.go:Init通过cfg.Enum校验取值并映射到module包定义的枚举;CheckMX/CheckConn中一旦发现有效等级低于门槛,返回 451/4.7.x 的临时性 SMTP 错误(附带mx_level、required_mx_level等诊断字段),且错误被统一标记为临时错误,以便管理员排查问题时不丢信。

3.6 共享策略组与完整配置示例

mx_auth模块支持在顶层定义策略组、通过&引用语法在多个remote实例间共享同一套策略(docs/reference/targets/remote.md):

mx_auth outbound_policy { dane mtasts { cache ram } } # ... elsewhere ... deliver_to remote { mx_auth &outbound_policy }

仓库自带的 maddy.conf 给出了生产级完整示例——默认配置同时启用 DANE、MTA-STS(文件缓存)与本地策略:

target.remote outbound_delivery { limits { destination rate 20 1s destination concurrency 10 } mx_auth { dane mtasts { cache fs fs_dir mtasts_cache/ } local_policy { min_tls_level encrypted min_mx_level none } } }

3.7 与 REQUIRETLS 的联动

投递安全等级还与 internal/target/remote/connect.go 中 REQUIRETLS 的处理相关:当消息带有 REQUIRETLS 选项时,maddy 要求tlsLevel >= TLSAuthenticated且mxLevel >= MX_MTASTS,否则拒绝(550/5.7.30)。同时为避免"连接池投毒"(攻击者通过复用弱安全旧连接使消息无法投递),带 REQUIRETLS 的消息会绕过连接池缓存强制新建连接。relaxed_requiretls选项(默认true)则允许向未通告 REQUIRETLS 的 MX 投递,其假设是 MX 极可能就是最终目的地,只需保障到 MX 这一段的安全。

四、各机制的安全特性对比与选型建议

结合 docs/seclevels.md 的等级总表与源码实现,可将各机制总结如下:

机制提升的等级抗被动攻击抗主动攻击关键依赖局限
MTA-STSMX →mtasts;enforce 时强制 TLS是是(有限)HTTPS 策略发布、策略缓存持久性攻击者可干扰策略刷新降级防护
DNSSECMX →dnssec是是上游解析器做 DNSSEC 验证(AD 标志)依赖解析器配置;非标准 resolv.conf 平台不可用
DANETLS →authenticated是是DNSSEC + TLSA 记录要求收件方发布 TLSA;需 EDNS 扩展解析器
local_policy门槛校验(不提升等级)——前序策略结果仅设下限,本身不提供认证能力

选型建议(基于文档与默认配置推断):

  • 基础防护:至少启用local_policy { min_tls_level encrypted },确保所有出站连接加密;
  • 推荐组合:dane+mtasts+dnssec+local_policy,即 maddy.conf 的默认方案,兼顾认证与加密;
  • 对安全要求严格的场景,可将min_mx_level提至mtasts甚至dnssec,把不满足认证条件的投递直接拒绝。

五、验证与测试依据

maddy 为这套安全体系提供了较完整的单元测试,可作为理解行为边界的佐证:

  • internal/target/remote/mxauth_test.go:MX 认证策略相关测试;
  • internal/target/remote/dane_test.go 与 internal/target/remote/dane_delivery_test.go:DANE 的 TLSA 验证与投递行为测试(含verifyDANETime钩子用于覆盖 DANE-TA 验证时间);
  • internal/target/remote/remote_test.go:使用 mock DNS 区域测试完整投递链路,其中testSTSPolicy、testDANEPolicy分别以cache ram和空配置初始化策略模块。

例如 internal/target/remote/remote_test.go 展示了 MTA-STS 策略以内存缓存初始化的测试路径,印证了cache ram配置项的实际作用。

六、小结

maddy 通过"MX 安全等级 + TLS 安全等级"的二维评级模型,把出站投递安全从模糊的"尽量用 TLS"升级为可量化、可强制、可审计的工程体系:MTA-STS 与 DNSSEC 解决"发给谁"(MX 认证),MTA-STS enforce 与 DANE 解决"怎么传"(TLS 强制),而local_policy提供统一的本地兜底门槛。理解这套等级与策略编排,是正确配置 maddy 出站投递、排查 451/550 投递失败与降级攻击问题的前提。所有机制均可在remote目标的mx_auth指令中自由组合,具体参数以 docs/reference/targets/remote.md 为准,RFC 8461 第 10.2 节(Preventing Policy Discovery)是 MTA-STS 策略防探测设计的规范依据。

  • 后端
  • 通信

【免费下载链接】maddy

✉️ Composable all-in-one mail server.

项目地址:https://gitcode.com/gh_mirrors/ma/maddy
点击查看免费下载
上一篇:老游戏还能不能跑:GTA III / 罪恶都市 / 圣安地列斯在 Windows 10/11 上的兼容性修复指南
下一篇:NocoBase FlowEngine 响应式机制解析:基于 Observable 的数据流与视图同步原理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询