快轉到主要內容

Cloudflare DNS Proxy 自動更新 SSL 之術

·347 字·2 分鐘

今天紀錄一個自動化更新伺服器證書的流程,主要問題是在搭配 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 已經關閉了!

要完成上述目的,我的想法很簡單:

  1. 過濾所有 A 紀錄
  2. 取出所有 RECORD_ID, type, name, content
  3. 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 天的), 所以每月都跑一次無所謂,這樣你也不需要擔心還要紀錄每個憑證的更新週期。

我離完全放養伺服器的日子又更近一步了~