今天紀錄一個自動化更新伺服器證書的流程,主要問題是在搭配 Cloudflare DNS Proxy 設定下, 呼叫 Certbot 更新證書會失敗,我們會需要額外步驟才能達成全自動化的目標。
前提 #
Cloudflare DNS 提供額外 Proxy 服務,讓伺服器可以躲在 Cloudflare 後面,提升原始伺服器的安全性。 但是在啟用 Proxy 的情況下,Certbot 是無法認證的,任何需要認證行為包括為新域名申請證書 或是更新既有網域的證書都會受影響,需要暫時關閉 Proxy 才能進行認證。
但這就有個問題,難道每次更新證書都得手動上設定頁面把 proxy 關閉在跑 renew,
這種小事還要工程師手動自己來是完全不能接受的,絕對不是因為我們喜歡射後不理。
實作 #
自動化的想法也很簡單,Cloudflare 有提供 API 接口可以使用指令方式修改 DNS Record。 首先,你要先申請一個 Access Token 來存取服務。
生成 Token #
右上角的選設定檔,左邊選擇 API 權杖
Cloudflare 有提供一些規則範本,剛好有關於設定 DNS 的範例,點選 編輯區域 DNS
權限給可以編輯 DNS,資源我會限制在目標網域。客戶端 IP 如果你家裡有固網 IP 位置可以設定上去,
限制其他 IP 的存取。
設定完就可以生成 Token。
自動化角本 #
接下來就是最重要的部分了,操作 API,第一步你先試著跟 API 溝通看看是否能正常存取。
curl -X GET "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type:application/json"
- Zone_ID 換成你自己的 ID
- YOUR_TOKEN 為剛剛申請到的 Token
Zone ID 可以在域名概觀首頁裡查到
![]()
下完指令後,你應該會得到一串 JSON 格式的資料,裡面包含你所有 DNS 紀錄。 你可以加入管線搭配 jq 以 json 輸出
curl -X GET "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type:application/json" | jq -c
接下來我們試著透過 API 更新 DNS 紀錄,你可以使用任意一個目前有的紀錄測試
curl -X PUT "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records/<RECORD_ID>" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type:application/json" \
--data '{
"type": "A",
"name": "your.domainn.com",
"content": "ipv4_address",
"proxied": false
}'
type
,name
,content
為必要項目"proxied": false
設定 proxy 關閉
設定完後重新整理 DNS 頁面,你會發現 proxy 已經關閉了!
要完成上述目的,我的想法很簡單:
- 過濾所有 A 紀錄
- 取出所有
RECORD_ID
,type
,name
,content
- 以
PUT
送出使 proxy 更新為 false
看起來很簡單,不過要過濾掉 json 並整理符合需求的格式也是花了不少時間。 這邊呈現我精煉過後的腳本。
record=$(curl -X GET "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type:application/json")
echo $record | jq -c '.result[] | select(.type == "A") | [{id, name, content}]' > record.txt
這邊我先取得所有 DNS 紀錄,接著利用 jq
指令將字串整理成 json 物件,搭配 select 選擇 A 紀錄,
最後重新封裝資料,我挑出 id, name, content,因為只要這些就夠了,然後存入 record.txt
,
每一行就剛好是一筆 A 紀錄。
while IFS= read -r line; do
RECORD_ID=$(echo $line | jq -r '.[].id')
NAME=$(echo $line | jq -r '.[].name')
CONTENT=$(echo $line | jq -r '.[].content')
curl -X PUT "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records/$RECORD_ID" \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"A\",
\"name\": \"$NAME\",
\"content\": \"$CONTENT\",
\"proxied\": false
}"
done < record.txt
存成 txt 再二次處理是因為我暫時想不到怎麼拆分 jq 的結果,存成陣列去帶, 如果你有想到更好的做法歡迎跟我縮一下
注意
jq -c/-r
的不同,-c
輸出為字串,-r
則會當成變數
這邊我就暴力解,每次讀取一行,用 jq
dump 出 id
, name
, content
,再加上 proxy 設定為 false,
迴圈跑完所有 A 紀錄的 Proxy 都關閉完成。
然後你就可以呼叫指令更新憑證
certbot renew
結束後只要把修改 "proxied": true
就可以把 proxy 重新打開。
上面所有動作可以整理成一個 shell script 執行,我自己會在關閉 Proxy 後 sleep 30 秒, 雖然 Cloudflare 生效動作速度蠻快的,不過保險起見讓他等一下。
最後搭配 Cronjob 就可以完全自動更新
crontab -e
進入編輯模式
0 3 1 * * ./path/to/your_script.sh
像我設定每個月第一天凌晨 3 點執行一次,因為 certbot 預設自動更新挑選即將過期的做更新(有效 < 30 天的), 所以每月都跑一次無所謂,這樣你也不需要擔心還要紀錄每個憑證的更新週期。
我離完全放養伺服器的日子又更近一步了~