将宝塔证书同步到多吉云 CDN

首页 / 乱写 / 正文
TL;DR

前两个月由于腾讯云 CDN 不支持欠费立刻暂停服务换到了多吉云 CDN。

但是!

目前宝塔的免费 SSL 证书都只有 90 天的有效期,虽然可以自动续签,但是续签后还是需要频繁手动更新 CDN 的证书,非常麻烦。

好在多吉云提供了对应的 API,使得我们可以通过宝塔定时任务的方式自动实现同步宝塔的证书到多吉云 CDN。

分享卡片加载中

获取密钥 & 生成 AccessToken

多吉云的 API 具有验证机制,使用前需要在控制台的 密钥管理 获取 AccessKey 以及 SecretKey,然后根据 AccessKeySecretKey生成 AccessToken

AccessToken 的生成过程是将请求地址和请求内容拼接后,使用 SecretKey 进行 HMAC-SHA1 加密,然后将得到的加密值与 AccessKey 用冒号连接起来。具体的生成算法可以参考文档 验证机制

# 多吉云 AccessKey 和 SecretKey
ACCESS_KEY="xxxx"
SECRET_KEY="xxxxxx"

function generateAccessToken() {
   local apiPath="$1"
   local body="$2"
   local signStr=$(echo -e "${apiPath}\n${body}")
   local sign=$(echo -n "$signStr" | openssl dgst -sha1 -hmac "$SECRET_KEY" | awk '{print $NF}')
   local accessToken="$ACCESS_KEY:$sign"

   echo "$accessToken"
}

生成了 AccessToken 后,只需要在请求头中加上 Authorization: TOKEN <AccessToken> 即可通过验证。

找到宝塔的域名证书 & 上传证书

/www/server/panel/vhost/ssl/ 目录下,可以找到宝塔所有以域名命名的文件夹,文件夹内包含这个域名对应的完整证书链 fullchain.pem 以及私钥 privkey.pem

这里推荐给域名及其所有子域名使用同一个泛域名证书,可以实现一次性将域名及其所有子域名完成证书同步。

以域名 vinking.top 为例,完整证书链文件和私钥文件的目录如下:

FULLCHAIN_PATH="/www/server/panel/vhost/ssl/vinking.top/fullchain.pem"
PRIVKEY_PATH="/www/server/panel/vhost/ssl/vinking.top/privkey.pem"

获取到域名证书后,需要通过 POST 的方式向 https://api.dogecloud.com/cdn/cert/upload.json 提交证书内容。上传成功后需要获取证书 ID ,以便将刚才上传的证书绑定到域名。

参考文档 上传证书绑定证书

# 宝塔面板Let's Encrypt证书路径
FULLCHAIN_PATH="/www/server/panel/vhost/ssl/vinking.top/fullchain.pem"
PRIVKEY_PATH="/www/server/panel/vhost/ssl/vinking.top/privkey.pem"

# 证书备注名
CURRENT_DATE=$(date +"%y/%m/%d")
NOTE="Certificate $CURRENT_DATE"

# 需要绑定的域名列表
DOMAINS=("xxxxx.com" "cdn.xxxxx.com" "www.xxxxx.com")

# 上传证书到多吉云
function uploadCert() {
    local note="$1"
    local certFile="$2"
    local privateKeyFile="$3"
    local certContent=$(<"$certFile")
    local privateKeyContent=$(<"$privateKeyFile")
    local encodedCert=$(echo "$certContent" | jq -sRr @uri)
    local encodedPrivateKey=$(echo "$privateKeyContent" | jq -sRr @uri)
    local body="note=$note&cert=$encodedCert&private=$encodedPrivateKey"
    local accessToken=$(generateAccessToken "/cdn/cert/upload.json" "$body")
    local response=$(curl -s -X POST "https://api.dogecloud.com/cdn/cert/upload.json"  \
         -H "Authorization: TOKEN $accessToken" \
         -H "Content-Type: application/x-www-form-urlencoded" \
         --data "$body")

    local code=$(echo "$response" | jq -r '.code')

    if [ "$code" -eq 200 ]; then
        echo "证书上传成功!"
        local certId=$(echo "$response" | jq -r '.data.id')
        echo "证书ID:$certId"
        bindCert "$certId"
    else
        local errMsg=$(echo "$response" | jq -r '.msg')
        echo "证书上传失败,错误代码:$code,错误信息:$errMsg"
    fi
}

# 绑定证书
function bindCert() {
    local certId="$1"
    local responses=()

    for domain in "${DOMAINS[@]}"; do
        (
            local body="id=$certId&domain=$domain"
            local accessToken=$(generateAccessToken "/cdn/cert/bind.json" "$body")
            local response=$(curl -s -X POST "https://api.dogecloud.com/cdn/cert/bind.json"  \
                 -H "Authorization: TOKEN $accessToken" \
                 -H "Content-Type: application/x-www-form-urlencoded" \
                 --data "$body")
            local code=$(echo "$response" | jq -r '.code')

            if [ "$code" -eq 200 ]; then
                echo "证书已成功绑定到 $domain"
            else
                local errMsg=$(echo "$response" | jq -r '.msg')
                echo "绑定证书到 $domain 失败,错误代码:$code,错误信息:$errMsg"
            fi
        ) &
    done

    wait
}

完整代码

#!/bin/bash

# 多吉云 AccessKey 和 SecretKey
ACCESS_KEY="xxxx"
SECRET_KEY="xxxxxx"

# 宝塔面板Let's Encrypt证书路径
FULLCHAIN_PATH="/www/server/panel/vhost/ssl/xxxxxxx/fullchain.pem"
PRIVKEY_PATH="/www/server/panel/vhost/ssl/xxxxxxx/privkey.pem"

# 证书备注名
CURRENT_DATE=$(date +"%y/%m/%d")
NOTE="Certificate $CURRENT_DATE"

# 需要绑定的域名列表
DOMAINS=("xxxxx.com" "cdn.xxxxx.com" "www.xxxxx.com")

# 生成AccessToken
function generateAccessToken() {
    local apiPath="$1"
    local body="$2"
    local signStr=$(echo -e "${apiPath}\n${body}")
    local sign=$(echo -n "$signStr" | openssl dgst -sha1 -hmac "$SECRET_KEY" | awk '{print $NF}')
    local accessToken="$ACCESS_KEY:$sign"

    echo "$accessToken"
}

# 上传证书到多吉云
function uploadCert() {
    local note="$1"
    local certFile="$2"
    local privateKeyFile="$3"
    local certContent=$(<"$certFile")
    local privateKeyContent=$(<"$privateKeyFile")
    local encodedCert=$(echo "$certContent" | jq -sRr @uri)
    local encodedPrivateKey=$(echo "$privateKeyContent" | jq -sRr @uri)
    local body="note=$note&cert=$encodedCert&private=$encodedPrivateKey"
    local accessToken=$(generateAccessToken "/cdn/cert/upload.json" "$body")
    local response=$(curl -s -X POST "https://api.dogecloud.com/cdn/cert/upload.json"  \
         -H "Authorization: TOKEN $accessToken" \
         -H "Content-Type: application/x-www-form-urlencoded" \
         --data "$body")

    local code=$(echo "$response" | jq -r '.code')

    if [ "$code" -eq 200 ]; then
        echo "证书上传成功!"
        local certId=$(echo "$response" | jq -r '.data.id')
        echo "证书ID:$certId"
        bindCert "$certId"
    else
        local errMsg=$(echo "$response" | jq -r '.msg')
        echo "证书上传失败,错误代码:$code,错误信息:$errMsg"
    fi
}

# 绑定证书到域名
function bindCert() {
    local certId="$1"
    local responses=()

    for domain in "${DOMAINS[@]}"; do
        (
            local body="id=$certId&domain=$domain"
            local accessToken=$(generateAccessToken "/cdn/cert/bind.json" "$body")
            local response=$(curl -s -X POST "https://api.dogecloud.com/cdn/cert/bind.json"  \
                 -H "Authorization: TOKEN $accessToken" \
                 -H "Content-Type: application/x-www-form-urlencoded" \
                 --data "$body")
            local code=$(echo "$response" | jq -r '.code')

            if [ "$code" -eq 200 ]; then
                echo "证书已成功绑定到 $domain"
            else
                local errMsg=$(echo "$response" | jq -r '.msg')
                echo "绑定证书到 $domain 失败,错误代码:$code,错误信息:$errMsg"
            fi
        ) &
    done

    wait
}

uploadCert "$NOTE" "$FULLCHAIN_PATH" "$PRIVKEY_PATH"

使用方法

宝塔后台创建一个定时任务:

  • 任务类型:Shell 脚本
  • 任务名称:随意
  • 执行周期:每月 1 号 01:30 执行一次
  • 执行用户:root
  • 脚本内容:上面的代码

定时任务

保存后执行一次任务,如果显示下面信息即已经完成证书的同步:

证书上传成功!
证书ID:12345
证书已成功绑定到 www.vinking.top
证书已成功绑定到 vinking.top
----------------------------------------------------------------------------
★[2024-11-19 16:30:54] Successful
----------------------------------------------------------------------------

配合自动续签 Let’s Encrypt 证书定时任务 /www/server/panel/pyenv/bin/python /www/server/panel/class/acme_v2.py –renew=1 理论上可以实现放养多吉云 CDN 的证书。

无标签
评论区
头像

进行人机验证

文章目录