Email Security Policy as Code 实践:SPF/DKIM/DMARC/MTA-STS 策略代码化
摘要:邮件安全策略(SPF、DKIM、DMARC、MTA-STS)目前仍然是大多数企业通过手动DNS管理界面逐条配置的。这种管理方式导致版本控制缺失、审核困难、变更影响评估依赖人工经验,且在多环境(开发/预发/生产)间难以保持一致性。Policy as Code(策略即代码)的理念正在改变这种局面——将邮件安全策略纳入代码仓库,通过CI/CD管道自动部署和验证,实现可审计、可回溯、可测试的策略管理流程。本文从Terraform DNS资源定义、策略代码化设计、CI/CD管道集成到持续验证的完整链路展开。
1. Policy as Code 架构总览
将邮件安全策略代码化的核心思路是:将SPF、DKIM、DMARC、MTA-STS策略定义为代码(YAML/HCL/JSON),存储在Git仓库中,通过CI/CD管道自动部署到DNS提供商和邮件服务器,并通过自动化测试确保证书有效性和策略正确性。整体架构如下:
┌────────────────────────────────────────────────────────────────┐
│ 代码仓库 (Git) │
│ │
│ ├── policies/ 邮件安全策略定义 │
│ │ ├── spf/ SPF 策略(主域+子域) │
│ │ ├── dkim/ DKIM 公钥记录 + 选择器定义 │
│ │ ├── dmarc/ DMARC 策略 + 聚合报告地址 │
│ │ └── mta-sts/ MTA-STS 策略 + TLS 报告配置 │
│ ├── terraform/ Terraform 基础设施代码 │
│ │ ├── dns/ DNS 记录(TXT, CNAME) │
│ │ └── servers/ 邮件服务器配置 │
│ ├── tests/ Policy 验证测试 │
│ └── Makefile CI/CD 编排流程 │
└───────────────────────────┬────────────────────────────────────┘
│ commit → push
▼
┌────────────────────────────────────────────────────────────────┐
│ CI/CD 管道 (GitHub Actions) │
│ │
│ Step 1: Lint — 语法检查(yamllint, jsonlint, spf-verify) │
│ Step 2: Validate — 策略语义验证(10-lookup SPF限制、DKIM位长) │
│ Step 3: Plan — Terraform plan 预览变更 │
│ Step 4: DeployDNS — 应用到DNS提供商 │
│ Step 5: Verify — 端到端验证(DNS传播+邮件投递测试) │
│ Step 6: DeployMTA — 部署到邮件服务器配置(如适用) │
└────────────────────────────────────────────────────────────────┘
此架构参考了 BCP 177 (RFC 7502) [1] 对 DNS 安全运维最佳实践的指导,以及 RFC 7489 [2](DMARC规范)和 RFC 8461 [3](MTA-STS规范)中关于策略管理的治理要求。
2. 策略代码化设计
2.1 策略定义结构(YAML格式)
# policies/config.yaml — 邮件安全策略主配置文件
email_security:
organization: "Example Corp"
domain: "example.com"
# 策略版本控制 — 用于审计和回退
version: "v2.4.1"
last_reviewed: "2026-07-15"
owner: "security-team@example.com"
spf:
# SPF 版本标识
version: "spf1"
# 授权发送服务器
include:
- "_spf.example.com" # 自有服务器
- "spf.protection.outlook.com" # Office 365
- "spf.sendgrid.net" # 第三方邮件服务
- "spf.mandrillapp.com" # 营销邮件
# 授权的IPv4地址
ip4:
- "203.0.113.0/24"
- "198.51.100.0/24"
# 授权的IPv6地址
ip6:
- "2001:db8::/32"
# 使所有其他服务器失败
all: "-all"
dkim:
selectors:
- name: "s1"
description: "主签名选择器(Dovecot)"
key_algorithm: "rsa-sha256"
key_size: 2048
key_file: "keys/s1-dkim-private.pem"
domains:
- "example.com"
- "mail.example.com"
- name: "s2"
description: "第三方邮件服务选择器(SendGrid)"
key_source: "managed"
domains:
- "example.com"
dmarc:
# DMARC 策略版本
version: "DMARC1"
# 策略(p=quarantine 过渡期,逐步提升到 p=reject)
policy: "reject"
subdomain_policy: "reject"
percentage: 100
# 聚合报告(RUA)
aggregate_report_uri:
- "mailto:dmarc-rua@example.com"
- "mailto:dmarc@rua.agari.com" # 第三方分析
# 取证报告(RUF)
forensic_report_uri:
- "mailto:dmarc-ruf@example.com"
# 报告间隔(秒)
reporting_interval: 86400
# 不对ADKIM失败的邮件进行SPF对齐检查
adkim: "r" # Relaxed
aspf: "r" # Relaxed
mta_sts:
# MTA-STS策略(RFC 8461)
policy_id: "v1.0"
# 允许的TLS发送方MTA
mx:
- "mx1.example.com"
- "mx2.example.com"
- "*.mail.protection.outlook.com" # Office 365 MX
# 模式:enforce/testing/none
mode: "enforce"
# 策略生效时长(秒)
max_age: 86400 # 1 day — 建议谨慎设置,逐步升级
tls_reporting:
# TLS-RPT (RFC 8460) 配置
rua:
- "mailto:tls-report@example.com"
- "https://tls-report.example.com/reports"
2.2 策略生成器(Python)
#!/usr/bin/env python3
"""policy_generator.py — 从YAML配置生成DNS记录"""
import yaml, dns.resolver, hashlib, base64, json
from pathlib import Path
class EmailSecurityPolicyGenerator:
"""邮件安全策略 DNS 记录生成器"""
def __init__(self, config_path: str = "policies/config.yaml"):
with open(config_path) as f:
self.config = yaml.safe_load(f)
self.domain = self.config['email_security']['domain']
self.records = []
def generate_spf_record(self) -> str:
"""生成 SPF TXT 记录"""
spf_cfg = self.config['email_security']['spf']
parts = [f"v={spf_cfg['version']}"]
for inc in spf_cfg.get('include', []):
parts.append(f"include:{inc}")
for ip4 in spf_cfg.get('ip4', []):
parts.append(f"ip4:{ip4}")
for ip6 in spf_cfg.get('ip6', []):
parts.append(f"ip6:{ip6}")
for redirect in spf_cfg.get('redirect', []):
parts.append(f"redirect={redirect}")
parts.append(spf_cfg['all'])
return ' '.join(parts)
def generate_dkim_records(self) -> list[dict]:
"""生成 DKIM DNS 记录"""
records = []
dkim_cfg = self.config['email_security']['dkim']
for selector in dkim_cfg.get('selectors', []):
if selector.get('key_source') == 'managed':
continue # 第三方管理,不生成DNS
# 读取私钥并导出公钥
key_path = Path(selector['key_file'])
if key_path.exists():
import subprocess
result = subprocess.run(
["openssl", "rsa", "-pubout", "-in", str(key_path)],
capture_output=True, text=True
)
pubkey_pem = result.stdout
# 转换为 Base64 格式并移除 PEM 头尾
pubkey_b64 = base64.b64encode(
base64.b64decode(
''.join(pubkey_pem.strip().split('\n')[1:-1])
)
).decode()
else:
# 如果是首次生成,创建密钥对
print(f"[WARN] 密钥文件 {key_path} 不存在,请先运行 ./generate_dkim_keys.sh")
pubkey_b64 = ""
dkim_record = {
"name": f"{selector['name']}._domainkey.{self.domain}.",
"type": "TXT",
"value": f"v=DKIM1; k={selector['key_algorithm'].split('-')[0]}; p={pubkey_b64}"
}
records.append(dkim_record)
return records
def generate_dmarc_record(self) -> dict:
"""生成 DMARC DNS 记录"""
dmarc_cfg = self.config['email_security']['dmarc']
parts = [f"v={dmarc_cfg['version']}"]
parts.append(f"p={dmarc_cfg['policy']}")
parts.append(f"sp={dmarc_cfg.get('subdomain_policy', dmarc_cfg['policy'])}")
parts.append(f"pct={dmarc_cfg.get('percentage', 100)}")
rua = dmarc_cfg.get('aggregate_report_uri', [])
if rua:
parts.append(f"rua={' ,'.join(rua)}")
ruf = dmarc_cfg.get('forensic_report_uri', [])
if ruf:
parts.append(f"ruf={' ,'.join(ruf)}")
parts.append(f"ri={dmarc_cfg.get('reporting_interval', 86400)}")
parts.append(f"adkim={dmarc_cfg.get('adkim', 'r')}")
parts.append(f"aspf={dmarc_cfg.get('aspf', 'r')}")
return {
"name": f"_dmarc.{self.domain}.",
"type": "TXT",
"value": '; '.join(parts)
}
def generate_mta_sts_record(self) -> dict:
"""生成 MTA-STS DNS 记录"""
sts_cfg = self.config['email_security']['mta_sts']
return {
"name": f"_mta-sts.{self.domain}.",
"type": "TXT",
"value": f"v=STSv1; id={sts_cfg['policy_id']}"
}
def generate_tls_report_record(self) -> dict:
"""生成 TLS-RPT DNS 记录"""
tls_cfg = self.config['email_security'].get('tls_reporting', {})
rua = tls_cfg.get('rua', [])
if not rua:
return None
return {
"name": f"_smtp._tls.{self.domain}.",
"type": "TXT",
"value": f"v=TLSRPTv1; rua={' ,'.join(rua)}"
}
def generate_mta_sts_policy_file(self) -> str:
"""生成 MTA-STS 策略文件(部署用于 .well-known/mta-sts.txt)"""
sts_cfg = self.config['email_security']['mta_sts']
lines = [
f"version: {sts_cfg['policy_id']}",
]
for mx in sts_cfg.get('mx', []):
lines.append(f"mx: {mx}")
lines.append(f"mode: {sts_cfg['mode']}")
lines.append(f"max_age: {sts_cfg['max_age']}")
return '\n'.join(lines) + '\n'
def generate_all(self) -> dict:
"""生成所有 DNS 记录"""
return {
"spf": {
"name": f"{self.domain}.",
"type": "TXT",
"value": self.generate_spf_record()
},
"dkim": self.generate_dkim_records(),
"dmarc": self.generate_dmarc_record(),
"mta_sts_dns": self.generate_mta_sts_record(),
"tls_report": self.generate_tls_report_record(),
"mta_sts_policy": self.generate_mta_sts_policy_file()
}
if __name__ == '__main__':
gen = EmailSecurityPolicyGenerator()
records = gen.generate_all()
# 输出所有 DNS 记录
for key, record in records.items():
if isinstance(record, list):
for r in record:
print(f"{r['name']} → {r['type']} = {r['value'][:80]}...")
elif record:
print(f"{record['name']} → {record['type']} = {record['value'][:80]}...")
# 输出 MTA-STS 策略文件
with open("mta-sts.txt", "w") as f:
f.write(records['mta_sts_policy'])
print("\nMTA-STS 策略文件已写入 mta-sts.txt")
3. Terraform DNS 资源管理
3.1 使用 Terraform 管理邮件安全 DNS 记录
Terraform 将 DNS 提供商视为数据源和目标。以下示例使用 AWS Route53 作为 DNS 提供商,原理适用于任何支持 Terraform Provider 的 DNS 服务(阿里云 DNS、腾讯云 DNSPod、Cloudflare 等):
# terraform/dns/main.tf — 邮件安全策略 DNS 记录管理
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
# 使用环境变量: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
}
data "aws_route53_zone" "main" {
name = var.domain
private_zone = false
}
# ---- SPF 记录 ----
resource "aws_route53_record" "spf" {
zone_id = data.aws_route53_zone.main.zone_id
name = var.domain
type = "TXT"
ttl = 300
records = [
var.spf_record
]
lifecycle {
# 确保 SPF 记录不超过 DNS 单条限制(255字符)
# 超过时需拆分为多个记录,但 RFC 7208 [4] 要求一次性查询
precondition {
condition = length(var.spf_record) <= 255
error_message = "SPF 记录超出 255 字符限制,需使用 include 拆分"
}
}
}
# ---- DKIM 公钥记录 ----
resource "aws_route53_record" "dkim" {
for_each = var.dkim_selectors
zone_id = data.aws_route53_zone.main.zone_id
name = "${each.key}._domainkey.${var.domain}"
type = "TXT"
ttl = 300
records = [
"v=DKIM1; k=${each.value.key_type}; p=${each.value.public_key}"
]
}
# ---- DMARC 记录 ----
resource "aws_route53_record" "dmarc" {
zone_id = data.aws_route53_zone.main.zone_id
name = "_dmarc.${var.domain}"
type = "TXT"
ttl = 300
records = [
var.dmarc_record
]
}
# ---- MTA-STS DNS 记录(策略ID指针)----
resource "aws_route53_record" "mta_sts" {
zone_id = data.aws_route53_zone.main.zone_id
name = "_mta-sts.${var.domain}"
type = "TXT"
ttl = 300
records = [
"v=STSv1; id=${var.mta_sts_policy_id}"
]
}
# ---- TLS-RPT(RFC 8460)记录 ----
resource "aws_route53_record" "tls_report" {
zone_id = data.aws_route53_zone.main.zone_id
name = "_smtp._tls.${var.domain}"
type = "TXT"
ttl = 300
records = [
"v=TLSRPTv1; rua=${var.tls_report_uri}"
]
}
# terraform/dns/variables.tf — 变量定义
variable "domain" {
description = "主域名"
type = string
}
variable "spf_record" {
description = "SPF TXT 记录值"
type = string
sensitive = false
}
variable "dkim_selectors" {
description = "DKIM 选择器配置"
type = map(object({
key_type = string
public_key = string
}))
}
variable "dmarc_record" {
description = "DMARC TXT 记录值"
type = string
}
variable "mta_sts_policy_id" {
description = "MTA-STS 策略版本ID"
type = string
default = "v1"
}
variable "tls_report_uri" {
description = "TLS-RPT 报告接收地址"
type = string
default = "mailto:tls-report@example.com"
}
3.2 环境差异化配置
# terraform/dns/prod.auto.tfvars — 生产环境特定配置
domain = "example.com"
mta_sts_policy_id = "v2"
spf_record = "v=spf1 include:_spf.example.com include:spf.protection.outlook.com -all"
dmarc_record = "v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:dmarc-rua@example.com; ri=86400"
dkim_selectors = {
"s1" = {
key_type = "rsa"
public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD..."
}
"s2" = {
key_type = "ed25519"
public_key = "MCowBQYDK2VwAyEA..."
}
}
tls_report_uri = "mailto:tls-report@example.com"
# terraform/dns/staging.auto.tfvars — 预发环境
domain = "staging.example.com"
mta_sts_policy_id = "v1"
# 预发环境的 DMARC 使用 p=none(仅监控)
spf_record = "v=spf1 include:_spf.staging.example.com -all"
dmarc_record = "v=DMARC1; p=none; sp=none; pct=100; rua=mailto:dmarc-staging@example.com"
dkim_selectors = {
"s1" = {
key_type = "rsa"
public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD..." # 测试密钥
}
}
tls_report_uri = "mailto:tls-report-staging@example.com"
4. CI/CD 管道集成
4.1 GitHub Actions 工作流
# .github/workflows/email-security-policy.yml
name: Email Security Policy CI/CD
on:
push:
branches: [main, staging]
paths:
- 'policies/**'
- 'terraform/**'
- 'tests/**'
pull_request:
paths:
- 'policies/**'
- 'tests/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install pyyaml dnspython
sudo apt-get install -y swaks jq
- name: Lint YAML configuration
run: |
yamllint policies/config.yaml
- name: Validate SPF record
run: |
python3 tests/validate_spf.py
- name: Validate DKIM key length
run: |
python3 tests/validate_dkim.py
- name: Validate DMARC policy
run: |
python3 tests/validate_dmarc.py
- name: Validate MTA-STS policy
run: |
python3 tests/validate_mta_sts.py
terraform-plan:
runs-on: ubuntu-latest
needs: validate
environment: terraform-plan
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
- name: Terraform Init
run: terraform init
working-directory: terraform/dns
- name: Terraform Plan
run: terraform plan -out=tfplan
working-directory: terraform/dns
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Upload TF plan
uses: actions/upload-artifact@v4
with:
name: tfplan
path: terraform/dns/tfplan
deploy-dns:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: terraform-plan
environment: production-dns
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
- name: Terraform Apply
run: terraform apply -auto-approve tfplan
working-directory: terraform/dns
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
verify-deployment:
runs-on: ubuntu-latest
needs: deploy-dns
steps:
- name: Wait for DNS propagation
run: sleep 120 # 等待DNS传播(TTL 300s)
- name: Verify SPF
run: |
dig TXT example.com +short | grep "v=spf1" || echo "SPF 验证失败"
- name: Verify DKIM
run: |
dig TXT s1._domainkey.example.com +short | grep "v=DKIM1" || echo "DKIM 验证失败"
- name: Verify DMARC
run: |
dig TXT _dmarc.example.com +short | grep "v=DMARC1" || echo "DMARC 验证失败"
- name: End-to-end mail delivery test
run: |
# 使用 swaks 发送测试邮件到验证邮箱
swaks --to email-test@example.com \
--from test@example.com \
--server smtp.example.com \
--auth LOGIN \
--auth-user test@example.com \
--header-X-Test-ID "CI-$(date +%s)" \
--body "Policy deployment verification test $(date)"
5. 自动化验证测试
5.1 SPF 验证
#!/usr/bin/env python3
# tests/validate_spf.py — SPF 策略验证
import dns.resolver
import sys
def validate_spf(domain: str):
"""验证 SPF 记录是否符合最佳实践"""
errors = []
warnings = []
try:
answers = dns.resolver.resolve(domain, 'TXT')
except dns.resolver.NoAnswer:
errors.append(f"未找到 {domain} 的 TXT 记录")
return errors, warnings
spf_records = []
for rdata in answers:
txt = rdata.to_text().strip('"')
if txt.startswith('v=spf1'):
spf_records.append(txt)
if len(spf_records) == 0:
errors.append("未找到 SPF 记录!")
elif len(spf_records) > 1:
# RFC 7208 [4] 规定 SPF 记录只能有一条
errors.append("存在多条 SPF 记录(超过1条)")
for record in spf_records:
# 检查 -all(必须)
if not record.endswith('-all') and not record.endswith('~all'):
warnings.append("SPF 策略未使用 -all(硬失败),建议从 ~all 升级到 -all")
# 检查 DNS 查询次数限制(RFC 7208 限制为10次)
lookup_count = record.count('include:') + record.count('a:') + record.count('mx:')
import re
ip4_count = len(re.findall(r'ip4:', record))
ip6_count = len(re.findall(r'ip6:', record))
if lookup_count > 10:
errors.append(f"SPF DNS 查询次数 {lookup_count} 超过 RFC 7208 10次限制")
# 检查单条记录长度
if len(record) > 255:
errors.append(f"SPF 记录长度 {len(record)} 超过 255 字符")
return errors, warnings
if __name__ == '__main__':
import yaml
with open('policies/config.yaml') as f:
config = yaml.safe_load(f)
domain = config['email_security']['domain']
errors, warnings = validate_spf(domain)
for e in errors:
print(f"[ERROR] {e}")
for w in warnings:
print(f"[WARN] {w}")
if errors:
sys.exit(1)
print(f"[OK] SPF 策略验证通过 ({domain})")
5.2 DMARC 验证
#!/usr/bin/env python3
# tests/validate_dmarc.py — DMARC 策略验证
import dns.resolver, yaml, sys, re
def validate_dmarc(domain: str):
errors = []
warnings = []
dmarc_domain = f"_dmarc.{domain}"
try:
answers = dns.resolver.resolve(dmarc_domain, 'TXT')
except dns.resolver.NoAnswer:
errors.append(f"未找到 DMARC 记录")
return errors, warnings
except dns.resolver.NXDOMAIN:
errors.append(f"DMARC 域名 {dmarc_domain} 不存在")
return errors, warnings
for rdata in answers:
txt = rdata.to_text().strip('"')
if not txt.startswith('v=DMARC1'):
continue
# 解析 DMARC 标记
tags = dict()
for kv in txt.split(';'):
kv = kv.strip()
if '=' not in kv:
continue
k, v = kv.split('=', 1)
tags[k.strip()] = v.strip()
# 验证策略值
policy = tags.get('p', 'none')
if policy == 'none':
warnings.append("DMARC 策略为 p=none(仅监控模式)")
elif policy == 'quarantine':
warnings.append("DMARC 策略为 p=quarantine(建议逐步升级到 p=reject)")
elif policy not in ('reject',):
errors.append(f"无效的 DMARC 策略值: {policy}")
# 验证 RUA 地址存在
if 'rua' not in tags:
warnings.append("DMARC 未配置 RUA(聚合报告)地址")
# 验证 RUA 地址格式
if 'rua' in tags:
rua = tags['rua']
# 格式必须为 mailto: 开头
if not rua.startswith('mailto:'):
errors.append(f"RUA 格式不正确(需 mailto: 前缀): {rua}")
# 验证百分比
pct = int(tags.get('pct', '100'))
if pct < 100 and policy == 'reject':
warnings.append(f"p=reject 但 pct={pct} 意味着并非100%流量强制执行")
# 验证报告间隔
ri = int(tags.get('ri', '86400'))
if ri < 3600:
warnings.append(f"报告间隔 ri={ri} 过短(建议≥3600秒)")
return errors, warnings
if __name__ == '__main__':
with open('policies/config.yaml') as f:
config = yaml.safe_load(f)
domain = config['email_security']['domain']
errors, warnings = validate_dmarc(domain)
for e in errors:
print(f"[ERROR] {e}")
for w in warnings:
print(f"[WARN] {w}")
if errors:
sys.exit(1)
print(f"[OK] DMARC 策略验证通过 ({domain})")
6. 通过DNS作为数据源的差异化配置
一种轻量级的策略代码化方法是直接将 DNS 作为"数据源"和"配置存储"——通过 TXT 记录中的结构化编码,让邮件服务器动态从 DNS 读取其安全策略,无需重新部署邮件服务器软件。RFC 8616 [5](SMTP email auto-configuration)描述了通过 DNS 自动发现配置的概念。以下是一个示例方案:
#!/bin/bash
# dns_policy_discovery.sh — 从DNS发现邮件安全策略并应用
DOMAIN="example.com"
echo "=== 从 DNS 发现邮件安全策略 ==="
# 1. 读取 MTA-STS 策略
echo ""
echo "--- MTA-STS 策略 ---"
dig _mta-sts.$DOMAIN TXT +short
# 2. 检查 SPF 记录
echo ""
echo "--- SPF 记录 ---"
dig $DOMAIN TXT +short | grep "v=spf1"
# 3. 检查 DMARC 记录
echo ""
echo "--- DMARC 记录 ---"
dig _dmarc.$DOMAIN TXT +short
# 4. 检查 TLS-RPT
echo ""
echo "--- TLS-RPT 记录 ---"
dig _smtp._tls.$DOMAIN TXT +short
# 5. 验证一致性 — DNS数据源模式
# 通过定期DNS查询,将结果与代码仓库中的期望值对比
echo ""
echo "--- 策略一致性验证 ---"
python3 <<'PYCHECK'
import dns.resolver, json
domain = "example.com"
# 期望值从策略定义文件读取
with open("tests/expected_records.json") as f:
expected = json.load(f)
# 检查 SPF
spf = dns.resolver.resolve(domain, 'TXT')
spf_value = ' '.join([r.to_text().strip('"') for r in spf if 'v=spf1' in r.to_text()])
if spf_value == expected['spf']:
print(f"[OK] SPF 与期望一致")
else:
print(f"[CHANGED] SPF 不一致")
print(f" DNS: {spf_value[:60]}...")
print(f" Git: {expected['spf'][:60]}...")
# 检查 DMARC
dmarc = dns.resolver.resolve(f"_dmarc.{domain}", 'TXT')
dmarc_value = '; '.join([r.to_text().strip('"') for r in dmarc if 'v=DMARC1' in r.to_text()])
print(f" 实际: {dmarc_value[:80]}")
PYCHECK
参考文献
- BCP 177 / RFC 7502 — DNS Best Current Practices,P. Hoffman et al.,2015,https://datatracker.ietf.org/doc/html/rfc7502
- RFC 7489 — Domain-based Message Authentication, Reporting, and Conformance (DMARC),M. Kucherawy & E. Zwicky,2015,https://datatracker.ietf.org/doc/html/rfc7489
- RFC 8461 — SMTP MTA Strict Transport Security (MTA-STS),M. Dahlberg et al.,2018,https://datatracker.ietf.org/doc/html/rfc8461
- RFC 7208 — Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1,S. Kitterman,2014,https://datatracker.ietf.org/doc/html/rfc7208
- RFC 8616 — Email Autoconfiguration with DNS,C. Holstead et al.,2019,https://datatracker.ietf.org/doc/html/rfc8616
- RFC 8460 — SMTP TLS Reporting (TLS-RPT),M. Kucherawy et al.,2018,https://datatracker.ietf.org/doc/html/rfc8460
