使用現代化的憑證更新 CLI 工具 lego 自動化 DNS 驗證
你好,我是無能。
最近想擺脫檔案驗證,或者該說感覺不太對勁的是,我目前正使用 certbot 執行憑證更新,但因為是透過檔案驗證進行,所以變成在 Nginx 端監聽了一個虛擬的 mail.example.com 網域。
雖然因為是自動執行所以還好,但 certbot 的運作方式像是暫時在檔案驗證路徑中放置檔案來進行更新。不過,郵件伺服器其實不需要 http,我實際上也不想接收它。
此外,如果 A 紀錄指定了多個來源(Origin)時,這種運作方式就會失效。
因為:
$dig soulminingrig.com +short
91.98.169.80
163.44.113.145
像這樣由兩個 IP 接收請求時,如果在其中一台伺服器執行 certbot,另一台伺服器就不會存在該檔案驗證用的檔案。雖然可能因為 HTTP 或 DNS 會將請求發送到用戶端最近的伺服器這種機制而成功,但依賴這種「魔法」在維運上是行不通的。
acme.sh 及 certbot 在規格上導入 DNS 驗證較為困難的部分
這兩者本身都可以進行 DNS 驗證。
但在 certbot 的情況下,並沒有預設自動呼叫 DNS 更新 API 的設定。因此,紀錄註冊等操作需要我們手動完成,在 DNS 驗證的情況下,自動更新實質上無法使用。
acme.sh 的情況則是預設不支援 ConoHa DNS 的 API。如果想透過指定選項來使用,選項會變得非常混亂。在這種情況下,如果更換 DNS 伺服器,情況會變得很棘手。
lego
在尋找是否有其他工具時,沒想到這款 Go 語言開發的工具竟然支援 ConoHa DNS API。
ConoHa v3 :: Let’s Encrypt client and ACME library written in Go.
GitHub - go-acme/lego: Let's Encrypt/ACME client and library written in Go · GitHub
太棒了!
安裝
連 pkg 裡都有,真是太感激了...。
# pkg search lego
lego-4.33.0 Let's Encrypt client and ACME library written in Go
pkg install lego-4.33.0
執行腳本
以我的情況,幾乎所有網域都由 Nginx 端管理,只有少部分例外,所以更新腳本我請 Chappy(AI)幫我寫了。
在我的案例中,www.example.com 和 example.com 想要核發 SAN 憑證,且不想核發萬用字元(Wildcard)憑證,所以寫法如下:
#!/bin/sh
set -eu
SITES_DIR="/usr/local/etc/nginx/sites-enabled"
export CONOHAV3_TENANT_ID=""
export CONOHAV3_API_USER_ID=""
export CONOHAV3_API_PASSWORD=""
export CONOHAV3_PROPAGATION_TIMEOUT="600"
export CONOHAV3_POLLING_INTERVAL="300"
LEGO="/usr/local/bin/lego"
SSLDIR="/usr/local/etc/ssl/lego"
EMAIL="taro@example.com"
DNS_PROVIDER="conohav3"
# 在此處添加 Nginx 管理範圍外也想更新的網域
# 以空格分隔多個網域
EXTRA_DOMAINS="
mail.example.com
"
tmp_all="$(mktemp)"
tmp_done="$(mktemp)"
trap 'rm -f "$tmp_all" "$tmp_done"' EXIT INT TERM
# 從 nginx 的 server_name 中擷取
grep -RhoE 'server_name[[:space:]]+[^;]+' "$SITES_DIR" \
| sed -E 's/^server_name[[:space:]]+//' \
| tr ' ' '\n' \
| sed 's/;$//' \
| sed '/^$/d' \
| sed '/^\*\./d' \
| sed '/^_/d' \
>> "$tmp_all"
printf '%s\n' "$EXTRA_DOMAINS" \
| tr ' ' '\n' \
| sed '/^$/d' \
>> "$tmp_all"
sort -u -o "$tmp_all" "$tmp_all"
: > "$tmp_done"
issue_cert() {
echo "==> issuing certificate for: $*"
"$LEGO" \
--accept-tos \
--path "${SSLDIR}" \
--email "$EMAIL" \
--dns "$DNS_PROVIDER" \
--dns.resolvers 1.1.1.1 \
"$@" \
run
}
already_done() {
grep -Fxq "$1" "$tmp_done"
}
mark_done() {
printf '%s\n' "$1" >> "$tmp_done"
}
while IFS= read -r host; do
[ -n "$host" ] || continue
if already_done "$host"; then
continue
fi
case "$host" in
www.*)
apex="${host#www.}"
if grep -Fxq "$apex" "$tmp_all"; then
issue_cert -d "$apex" -d "$host"
mark_done "$apex"
mark_done "$host"
else
issue_cert -d "$host"
mark_done "$host"
fi
;;
*.*.*)
issue_cert -d "$host"
mark_done "$host"
;;
*.*)
if grep -Fxq "www.$host" "$tmp_all"; then
issue_cert -d "$host" -d "www.$host"
mark_done "$host"
mark_done "www.$host"
else
issue_cert -d "$host"
mark_done "$host"
fi
;;
*)
echo "skip invalid host: $host" >&2
;;
esac
done < "$tmp_all"
更新時只需將 run 選項改為 renew 即可。
自動更新
以我的情況,因為使用的是 FreeBSD,所以寫在 /etc/periodic.conf 中
weekly_lego_enable="YES"
weekly_lego_renewscript="/usr/local/etc/lego/dns.sh"
weekly_lego_deployscript="/usr/local/etc/lego/deploy.sh"
因為有預設的 /usr/local/etc/lego/deploy.sh 和 /usr/local/etc/lego/lego.sh,所以基本上建議遵循這個設計。但是,就目前所見,預設似乎是使用檔案驗證,因此如果像這次一樣想要進行 DNS 驗證,則需要進行修改。