前情提要: 我在一台机器上部署了acme.sh
脚本,申请通配符证书,确保证书在这台机器上是一直有效的。
当机器有多台的时候,我想共用申请的证书,而不是在每一台上都再来一次。
这就要有一个存放证书的地方。
本着不额外花费资源的原则,托管我域名DNS的CF本身似乎是一个挺好的选择。
上传证书
-
获取CF鉴权id和token
需要Workers KV Storage Read
和Workers KV Storage Write
权限 -
创建 KV namespace
仅需执行一次,需要获取NAMESPACE_ID
ACCOUNT_ID="aaa" ACCOUNT_TOKEN="bbb" curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces \ -H "Authorization: Bearer $ACCOUNT_TOKEN" \ -H "Content-Type:application/json" \ -d '{ "title": "SSL Certs" }'
从返回内容中获取
.result.id
示例{ "result": { "id": ">>>>>>NAMESPACE_ID<<<<<<<<", "title": "SSL Certs", "supports_url_encoding": true }, "success": true, "errors": [], "messages": [] }
-
上传证书公私钥 以上传公钥文件为例子,这个脚本在每次申请证书成功后执行。
ACCOUNT_ID="aaa" ACCOUNT_TOKEN="bbb" NAMESPACE_ID="cccc" KEY_NAME="you_domain_cert_public" KEY_DATA=$(cat /path/to/you_domain_cert_public.pem) curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/values/$KEY_NAME \ -X PUT \ -H "Authorization: Bearer $ACCOUNT_TOKEN" \ -H "Content-Type:application/json" \ -d "{ \"value\": \"$KEY_DATA\" }" KEY_NAME="you_domain_cert_private" KEY_DATA=$(cat /path/to/you_domain_cert_private.pem) curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/values/$KEY_NAME \ -X PUT \ -H "Authorization: Bearer $ACCOUNT_TOKEN" \ -H "Content-Type:application/json" \ -d "{ \"value\": \"$KEY_DATA\" }"
同步证书
- 创建 KV namespace
crontab每天执行即可。
脚本中的sed命令是针对证书内容,去掉第一个---
之前的内容,和最后一个---
之后的内容。
这样不需要额外依赖jq来解析json。
#!/bin/bash
# 修改为你的CF配置
ACCOUNT_ID="aaa"
ACCOUNT_TOKEN="bbb"
NAMESPACE_ID="cccc"
CERT_PATH="${1:-/path/to/you_domain_cert_public.pem}" # 修改为你的证书路径
KEY_PATH="${2:-/path/to/you_domain_cert_private.pem}" # 修改为你的证书路径
EXPIRY_WARNING_DAYS=7 # 过期前多少天开始同步证书
# 获取证书过期时间的时间戳
EXPIRY_DATE=$(openssl x509 -in "$CERT_PATH" -noout -enddate | sed 's/^notAfter=//')
EXPIRY_TIMESTAMP=$(date -d "$EXPIRY_DATE" +%s)
# 获取当前时间的时间戳
CURRENT_TIMESTAMP=$(date +%s)
# 计算剩余秒数
REMAINING_SECONDS=$((EXPIRY_TIMESTAMP - CURRENT_TIMESTAMP))
# 计算剩余天数
REMAINING_DAYS=$((REMAINING_SECONDS / (60 * 60 * 24)))
# 获取过期日期 (YYYY-MM-DD)
EXPIRY_DATE=$(date -d @$EXPIRY_TIMESTAMP +%Y-%m-%d)
# 大于365的判断是因为一般都是几年十几年的自签的证书
if [ "$REMAINING_DAYS" -lt "$EXPIRY_WARNING_DAYS" ] || [ "$REMAINING_DAYS" -gt 365 ]; then
echo "证书快要过期了,过期时间${EXPIRY_DATE},还剩 ${REMAINING_DAYS} 天"
KEY_NAME="you_domain_cert_public"
public_cert=$(curl -s https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/values/$KEY_NAME -H "Authorization: Bearer $ACCOUNT_TOKEN" | sed ':a;N;$!ba;s/^[^---]*---/---/;s/---[^---]*$/---/')
echo "$public_cert" > $CERT_PATH
KEY_NAME="you_domain_cert_private"
private_cert=$(curl -s https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/storage/kv/namespaces/$NAMESPACE_ID/values/$KEY_NAME -H "Authorization: Bearer $ACCOUNT_TOKEN" | sed ':a;N;$!ba;s/^[^---]*---/---/;s/---[^---]*$/---/')
echo "$private_cert" > $KEY_PATH
nginx -s reload
else
echo "证书过期还早,过期时间${EXPIRY_DATE},还剩 ${REMAINING_DAYS} 天"
fi