- date
- entry
- 006
- topic
- tooling
- rev
- —
把 API token 放進 SSM Parameter Store,不要放 .env
.env 的問題不是它不安全,是它會被複製。談我現在改一筆 DNS 記錄的流程:token 從加密參數拉進 shell 變數、zone id 每次動態查、關掉終端機就沒了——以及為什麼「應該沒人看到」從來不是一個判準。
一條 Cloudflare API token 放在 .env 裡,權限是「這個帳號下所有 zone 的 DNS 寫入」。這個檔案在 .gitignore 裡,權限 600,沒有進版控。聽起來守住了。
然後你回想它這半年去過哪些地方:
- 整個專案目錄被
rsync到 NAS 當備份,那份沒有加密 docker build .把整個目錄當 build context 送進 daemon,如果.dockerignore沒同步更新,它就在 image layer 裡- 交接時整包壓縮丟給同事,對方解壓在下載資料夾,到現在還在
- 某次貼設定檔進聊天視窗問問題,滑鼠選取多框了三行
這四件事沒有一件是「安全機制失效」。檔案的本質就是會被複製,而複製不會通知你。問題不在 .env 的權限位元,在於它是一個檔案。
放哪裡
我用 AWS SSM Parameter Store 的 SecureString。它由 KMS 加密、有 IAM 控管、有 CloudTrail 稽核記錄,而且對這個用途是免費的。
| Secrets Manager | SSM Parameter Store | |
|---|---|---|
| 月費 | $0.40/筆 | 標準參數免費 |
| API 費 | $0.05/萬次 | 免費 |
| 自動 rotation | 有(Lambda hook) | 沒有,要自己排 |
| 適合 | DB 密碼、需定期輪替的 | 一般 API token |
需要自動 rotation 就用 Secrets Manager,其他情況 Parameter Store 夠了。一次性設定:
aws ssm put-parameter \
--region ap-northeast-1 \
--name /cloudflare/api-token \
--type SecureString \
--value 'cfat_PASTE_HERE' \
--description "Cloudflare API token: DNS edit, all zones"
帳號 ID 這種不是機密但常要查的東西,用普通 String 存旁邊就好,不必混進加密參數裡。
每次操作的流程
拉進 shell 變數,不落盤:
CF_TOKEN=$(aws ssm get-parameter \
--region ap-northeast-1 \
--name /cloudflare/api-token \
--with-decryption \
--query Parameter.Value --output text)
先確認這條 token 還活著,再做任何會改東西的事:
curl -s -H "Authorization: Bearer $CF_TOKEN" \
https://api.cloudflare.com/client/v4/user/tokens/verify \
| jq '.success, .result.status'
# 預期 true / "active"
這一步值得做,因為 token 失效的錯誤訊息跟權限不足的錯誤訊息長得很像,先驗證可以省掉一輪猜測。
zone id 不要存
這是我覺得最容易被忽略的一點。每次操作前用域名查:
DOMAIN="example.com"
CF_ZONE_ID=$(curl -s -H "Authorization: Bearer $CF_TOKEN" \
"https://api.cloudflare.com/client/v4/zones?name=$DOMAIN" \
| jq -r '.result[0].id')
[ -z "$CF_ZONE_ID" ] || [ "$CF_ZONE_ID" = "null" ] && {
echo "ERROR: zone $DOMAIN 不在這個帳號下"; return 1
}
三個理由:
- 加新域名不用改設定。存死 zone id 的話,每接一個新域名就要多加一條,遲早有人忘記。
- 它會漂。域名轉出再轉回、或搬到另一個 Cloudflare 帳號,zone id 就換了。存下來的那份不會報錯,只會安靜地指向一個你沒有權限的 zone。
- 那個查詢順便驗證了前提。查不到就代表「這個域名不在這個帳號下」,這件事你本來就該在改 DNS 前確認。
查一次 zone 是一個 HTTP round trip。用這個成本換掉一整類「設定檔與現實不一致」的問題,很划算。
不落盤的幾條紀律
- 不
echo $CF_TOKEN——一 echo 就進了 scrollback,而 scrollback 會被截圖 - 不重導向到檔案,包括「暫時」的那種
- 自動化腳本裡只透過環境變數傳,不要 inline 成字串——inline 的會進 process list,同一台機器上的其他人
ps得到 - 操作完關掉那個終端機分頁
順帶一提兩個 Cloudflare 自己的坑:DKIM 之類的驗證用 CNAME 一定要
proxied: false(橘雲關掉),開著會驗不過;還有 token 的 rate limit 是每 5 分鐘 1200 次,批次操作要自己 sleep。
什麼算洩漏
判準很簡單:只要那串字曾經出現在檔案、git、聊天視窗、或沒清掉的終端機捲動區,就算洩漏。不需要證明有人看到,也不用評估「應該沒事吧」。
會有人想省這一步,是因為直覺上 rotate 很麻煩。實際上它是:到 dashboard 按 roll、然後
aws ssm put-parameter \
--region ap-northeast-1 \
--name /cloudflare/api-token \
--type SecureString \
--value 'cfat_NEW_VALUE' \
--overwrite
兩分鐘。既然成本這麼低,就不需要「判斷這次算不算真的洩漏」——那個判斷比 rotate 本身還貴,而且會判斷錯。
這是把要靠人判斷的地方換成規則的一種:規則會執行,判斷會偷懶。
代價
誠實講這套的缺點:
- 每次操作多一次 AWS API 呼叫,大概幾百毫秒
- 綁在 AWS 上。手邊沒有可用的 AWS 憑證時,你連 DNS 都改不了
- token scope 開成「所有 zone」很方便,但失竊的破壞範圍也是所有 zone——要配 IP 白名單和到期日一起用
第二點是真的會擋到人的。我接受它,因為「改 DNS」本來就不該是隨手能做的事——需要先有一組有效的雲端身分,這個門檻是特性不是缺陷。
如果只記得一件事
憑證的風險跟它的權限成正比,跟它被複製過幾次成正比,跟你有多小心無關。把它從檔案系統移到一個需要身分才能取用、而且每次取用都留下記錄的地方,你就不用再依賴自己記得所有複製發生過的位置。
修訂紀錄
- 首次發布