📌 前言
在上一章介紹 DANE 時,我們提到 DANE(DNS-based Authentication of Named Entities)透過 DNSSEC 加簽的 TLSA 記錄,讓寄件端 MTA 在進行 SMTP over TLS(port 25)時:
- 驗證 MX 主機憑證是否與 DNS 宣告相符合
- 直接在 DNSSEC 保護的 zone 裡「鎖定」你要使用的公鑰
- 防止 MITM、SSL 降級攻擊、憑證替換攻擊
若使用 TLSA(3 1 1)模式,就表示綁定的是「伺服器公鑰指紋」。
因此只要 你的憑證更新,公鑰可能跟著變動 → TLSA 記錄也必須同步更新。
Let’s Encrypt 憑證 90 天更新一次。
若 TLSA 沒同步更新,就會造成使用 DANE 的收件端拒收郵件,例如:
TLSA mismatch — certificate does not match DNSSEC TLSA
因此:
🎯 憑證自動更新 + TLSA 自動更新 + BIND DNSSEC 自動簽署 = 必須全自動化
這篇文章將示範我在 mail.it.nuface.tw 伺服器上的完整自動化流程。
🧩 系統架構與前置條件
本文章以你的實際環境為例:
| 項目 | 設定 |
|---|---|
| 郵件網域 | it.nuface.tw |
| MX | mail.it.nuface.tw |
| IP | x.x.x.x |
| DNS | 自建 BIND9(DNSSEC 已啟動),上層註冊商為中華電信 |
| DNSSEC | 已啟用,子域與 A 記錄皆有 RRSIG |
| TLSA 記錄 | _25._tcp.mail.it.nuface.tw |
| Postfix | Docker Container |
| 憑證 | Let’s Encrypt(容器內 /etc/letsencrypt/live/...) |
| 自動更新模式 | 不寄 email、寫 log 到 /var/log/dane-update.log |
🧩 TLSA 使用的模式:3 1 1
本教學使用最常見、最安全的模式:
Usage = 3 (DANE-EE: End Entity Cert)
Selector = 1 (SubjectPublicKeyInfo)
Match Type = 1 (SHA-256)
因此 TLSA 格式為:
_25._tcp.mail.it.nuface.tw IN TLSA 3 1 1 <hash>
🛠 第一步:從憑證產生 TLSA 指紋(hash)
在 mail container(或 host mount)執行:
CERT=/etc/letsencrypt/live/mail.it.nuface.tw/fullchain.pem
openssl x509 -in "$CERT" -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl sha256 -binary \
| hexdump -ve '32/1 "%02x" "\n"'
輸出例:
3a5b7c9d2f4e8a0123456789abcdef0123456789abcdef0123456789abcdef0
這一串就是 TLSA 使用的 <hash>。
🛠 第二步:BIND9 zone 檔加入 TLSA 記錄(單行格式)
zone db.nuface.tw 前段如下:
mail.it IN A x.x.x.x
在附近加上:
_25._tcp.mail.it IN TLSA 3 1 1 3a5b7c9d2f4e8a0123456789abcdef0123456789abcdef0123456789abcdef0
若之前有舊的 TLSA 記錄,請用腳本自動取代,不要手動更新。
🛠 第三步:建立自動化腳本 /usr/local/sbin/update-tlsa.sh
此腳本功能:
✔ 自動重新計算 TLSA hash
✔ zone 檔自動更新(單行格式)
✔ SOA serial 自動 +1
✔ rndc reload nuface.tw
✔ BIND9 自動 DNSSEC 重簽(你有 inline-signing)
✔ 寫 log 到 /var/log/dane-update.log
以下即為可直接使用的版本:
📄 /usr/local/sbin/update-tlsa.sh
#!/bin/bash
set -euo pipefail
ZONE="/etc/bind/zones/ext/db.nuface.tw"
ORIGIN="nuface.tw."
TLSA_NAME="_25._tcp.mail.it"
CERT="/etc/letsencrypt/live/mail.it.nuface.tw/fullchain.pem"
LOG="/var/log/dane-update.log"
timestamp() { date "+%Y-%m-%d %H:%M:%S"; }
echo "[$(timestamp)] Starting TLSA update..." >> "$LOG"
# 1. 產生新 hash
NEW_HASH=$(openssl x509 -in "$CERT" -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl sha256 -binary \
| hexdump -ve '32/1 "%02x"')
if [[ -z "$NEW_HASH" ]]; then
echo "[$(timestamp)] ERROR: failed to calculate TLSA hash" >> "$LOG"
exit 1
fi
echo "[$(timestamp)] New TLSA hash: $NEW_HASH" >> "$LOG"
# 2. 更新 zone:取代舊 TLSA 行
sed -i \
"s/^${TLSA_NAME}[[:space:]]\+IN[[:space:]]\+TLSA.*/${TLSA_NAME} IN TLSA 3 1 1 ${NEW_HASH}/" \
"$ZONE"
echo "[$(timestamp)] TLSA record updated in zone file." >> "$LOG"
# 3. 更新 SOA serial
SERIAL=$(grep -A1 "SOA" "$ZONE" | awk 'NR==2 {print $1}')
NEXT=$((SERIAL + 1))
sed -i "s/$SERIAL ; Serial/$NEXT ; Serial/" "$ZONE"
echo "[$(timestamp)] SOA serial updated: $SERIAL -> $NEXT" >> "$LOG"
# 4. reload zone
rndc reload nuface.tw
echo "[$(timestamp)] rndc reload executed." >> "$LOG"
echo "[$(timestamp)] TLSA update completed." >> "$LOG"
🛠 第四步:整合到 renewssl.sh
在你原本的 script 中:
if [ "$after" -gt "$before" ]; then
...
docker exec "$MAIL_CTN" postfix reload || true
docker restart "$DOVECOT_CTN" || true
下面加一句(主機端執行):
/usr/local/sbin/update-tlsa.sh
完整範例:
if [ "$after" -gt "$before" ]; then
echo "$after" > "$STAMP_FILE"
echo "[renewssl] Certificates changed, applying reloads..."
docker restart "$WEB_CTN" || true
docker exec "$MAIL_CTN" postfix reload || true
docker restart "$DOVECOT_CTN" || true
# 新增:TLSA 自動更新
/usr/local/sbin/update-tlsa.sh
fi
完成後 → 你的憑證連帶 TLSA 會跟著自動更新。
🧪 第五步:驗證 TLSA + DNSSEC
查 TLSA 是否正確:
dig +dnssec _25._tcp.mail.it.nuface.tw TLSA
你應該看到:
- 你的新 hash
- RRSIG(DNSSEC 簽章)
- 回應 flags 中有
ad(Authenticated Data)
🧪 第六步:測試 DANE 邏輯(Outbound)
測試你對外寄信時 DANE 行為:
posttls-finger dane-test.somerandomdomain
或者寄到已部署 DANE 的網域,查看 log:
grep -i dane /var/log/mail.log
成功範例:
Verified TLS connection established using DANE
🧪 第七步:常見錯誤排查
| 問題 | 原因 | 解法 |
|---|---|---|
| TLSA mismatch | hash 未更新 | 手動執行 update-tlsa.sh |
| zone serial 未更新 | 所以 slave 不同步 | 檢查 sed / serial |
| DNSSEC RRSIG 過期 | BIND9 未重簽 | 確認 inline-signing 是否正常 |
| hash 長度錯誤 | TLSA 格式錯誤或空值 | 檢查 openssl 管線 |
| Postfix 不使用 DANE | smtp_dns_support_level 未設 | 設為 dnssec |
🎯 結語
完成以上步驟後,你現在擁有:
✔ DNSSEC 保護的 MX
✔ 自動更新的 TLSA(3 1 1)
✔ BIND9 inline-signing 自動簽署
✔ Postfix 出站 DANE 驗證
✔ 全自動化 Let’s Encrypt + TLSA + DNSSEC pipeline
你的 mail.it.nuface.tw 已經是高強度郵件安全架構,可抵抗:
- MITM
- DNS spoofing
- TLS downgrading
- certificate replacement attack