delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

27 Sep 2023

关于 Google Workspace 的 SPF / DMARC 设置

这里简单记一笔。我有一个域名使用的是 Google Workspace 的邮件服务。 昨天,一位用户使用该域名向中国的 QQ 邮箱用户发送邮件时被退回了。

退信中给出的线索是:

550 DMARC check failed [XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
IP: 2607:f8b0:4864:20::112b]. https://service.mail.qq.com/detail/124/61.

腾讯给出的那个链接是一篇关于 DMARC 如何配置的文章。 从上下文来看,应该是由于腾讯的服务器认为 Google Workspace 的 IP 地址不在允许发信的范围内。

退信确实是 Google Workspace 的邮件服务生成的,数据来源是和对方(腾讯) MX 之间的 SMTP 会话。

我的这个域名确实启用了 DMARC。我的DMARC记录 (域名下的 _dmarc TXT 记录) 设置如下:

v=DMARC1; p=reject; rua=mailto:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@dmarc-reports.cloudflare.net;
pct=100; adkim=s; aspf=s

此配置要求对方检查 DKIM 和 SPF,并拒绝不匹配的邮件。

其 SPF 记录 (域名的 @ 的 TXT 记录) 设置如下。这是 Google 官方文档中推荐的设置

v=spf1 include:_spf.google.com -all

主要的区别是,我使用的是 -all,即拒绝 SPF 不匹配的邮件,而不是更为常见的、表示即使发现 SPF 不匹配,也仅仅做标记但允许接收的 ~all

这里需要说一下背景:在 2004 年左右,一些业内人士认为应该推荐采用 ~all 而不是 -all,因为当时许多大型邮件系统的架构中存在多层邮件服务器分别进行不同的过滤处理, 而在 SMTP 会话阶段直接回一个永久性拒绝 (5XX) 代码可能会让一些不适任的邮件系统管理员如此配置的邮件系统将一些伪造了来源的邮件退到不希望的地方。 我并不赞同这样的观点。事实上,在会话阶段进行退信要比在收件方服务器上生成退信并将退信退给发件人或对方域名的 postmaster 要更好: 对于垃圾邮件的发送者,这意味着他们的邮件服务器会立即知道自己的行为被直接地丑拒, 接收方明确告知他们吞下垃圾邮件,而不是继续发送。对于正常的邮件服务器, 这意味着发件人可以立即收到退信从而找到自己的邮件系统管理员,而不是告诉收件人去翻垃圾箱看看是否有自己的邮件。 另一方面,回应永久性拒绝并不妨碍收件服务器对邮件内容进行进一步分析:它完全可以在 DATA 阶段结束时再回拒绝,从而将邮件完整地接收下来但不递给收件人。

当然,历史无法假设,并已经无数次地证明人类根本不配拥有美好的事物。

DomainKey 记录 (域名下的 google._domainkey TXT 记录) 由于与此问题无关,故在此处略去。

该域名启用了 DNSSEC。

首先是检查 SPF 设置的是否有问题。 _spf.google.com 是 Google 各类邮件服务采用的公共 SPF 记录,其出现问题的可能性不大。 不过,我还是手工查询了记录。在本次 SMTP 会话中 Google 使用的发送邮件地址 2607:f8b0:4864:20::112b 在此 SPF 记录引用的 _netblocks2.google.com 中以 ip6:2607:f8b0:4000::/36 的形式列出:

$ dig +short _spf.google.com TXT
"v=spf1 include:_netblocks.google.com include:_netblocks2.google.com
 include:_netblocks3.google.com ~all"
$ dig +short _netblocks.google.com  TXT
"v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20
 ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16 ip4:108.177.8.0/21
 ip4:173.194.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19
 ip4:216.239.32.0/19 ~all"
$ dig +short _netblocks2.google.com  TXT
"v=spf1 ip6:2001:4860:4000::/36 ip6:2404:6800:4000::/36
 ip6:2607:f8b0:4000::/36 ip6:2800:3f0:4000::/36
 ip6:2a00:1450:4000::/36 ip6:2c0f:fb50:4000::/36 ~all"
$ dig +short _netblocks3.google.com  TXT
"v=spf1 ip4:172.217.0.0/19 ip4:172.217.32.0/20 ip4:172.217.128.0/19
 ip4:172.217.160.0/20 ip4:172.217.192.0/19 ip4:172.253.56.0/21
 ip4:172.253.112.0/20 ip4:108.177.96.0/19 ip4:35.191.0.0/16
 ip4:130.211.0.0/22 ~all"

用 Perl 的 Mail::SPF 验证得到的结论和我认为的一致:

Mechanism 'include:_netblocks2.google.com' matched

尽管我的做法可能不太常见,但它完全符合 RFC 7208, 事实上,其 5.2 小节中描述的流程正是以 -all 作为例子的,按照 RFC 中的流程处理并没有二义性。

至于为什么腾讯的服务器认为 2607:f8b0:4864:20::112b 不是允许发信的 IP,我还是不完全理解。一个猜测是可能他们对于 -all 的处理和 ~all 有所不同。不过我离开邮件这行多年,一时也找不到腾讯的联络人(如果本blog的读者有哪位可以帮忙联系一下的话不甚感激),于是就采取了比较偷懒的做法, 也就是直接抄 gmail.com 的作业:

v=spf1 redirect=_spf.google.com

这一做法简单粗暴地告诉收件服务器:本域名直接以 _spf.google.com 的内容作为自己的 SPF 策略。

再测试时发现可以正常发信给 qq.com 了(作为副作用,这意味着最后的规则从 -all 变成了 ~all,测试显示,我的另一个域名使用 Google 推荐的 SPF 配置,即 ~all 时,其 Google Workspace 邮件可以正常发到 qq.com)。