Certbot+Letsencrypt+腾讯云DNS验证自动续期证书

本文基于Ubuntu

安装certbot

sudo apt-get install certbot

申请脚本

通过腾讯云域名解析的dns验证域名归属,并申请通配符域名证书

certbot certonly --manual --preferred-challenges dns -d example.com -d *.example.com \
        --email your_email \
        --agree-tos \
        --server https://acme-v02.api.letsencrypt.org/directory \
        --manual-auth-hook dnspod-add.sh \
        --manual-cleanup-hook dnspod-rm.sh \
        --deploy-hook reload-ssl.sh
  • certbot certonly​:这个子命令用于获取证书,而不是安装或配置证书。它是 Certbot 提供的最基础的证书获取方式。
  • --manual​:指定以手动模式运行 Certbot。在这种模式下,Certbot 会提示你手动完成一些操作以验证域名所有权,而不是自动处理这些操作。
  • --preferred-challenges dns​:指定优先使用 DNS 验证方式。Let's Encrypt 提供多种验证域名所有权的方式,如 HTTP 验证(通过在服务器上放置文件)和 DNS 验证(通过更新 DNS TXT 记录)。这里选择 DNS 验证。
  • -d example.com, -d *.example.com​:指定要获取证书的域名。这里不仅为 example.com 获取证书,还为 *.example.com(即所有 example.com 的子域名)获取通配符证书。
  • --email your_email​:提供电子邮件地址,用于接收证书相关的重要通知,如证书续订通知、证书吊销通知等。
  • --agree-tos​:自动同意 Let's Encrypt 的服务条款(Terms of Service)。这表示你已经阅读并同意 Let's Encrypt 的条款和条件。
  • --server https://acme-v02.api.letsencrypt.org/directory:指定Let's Encrypt的 ACME 服务器 URL。这是Let's Encrypt的生产环境服务器,用于获取正式的证书。可以先用测试环境调试。
  • --manual-auth-hook dnspod-add.sh:指定手动认证钩子(hook)脚本。当 Certbot 需要进行 DNS 验证时,它会调用这个脚本自动添加 DNS TXT 记录。这里假设脚本 dnspod-add.sh 用于与 DNS 服务提供商(如 DNSPod)交互,添加所需的 TXT 记录。
  • --manual-cleanup-hook dnspod-rm.sh​:指定手动清理钩子脚本。当验证完成后,Certbot 会调用这个脚本自动删除之前添加的 DNS TXT 记录。脚本 dnspod-rm.sh 用于从 DNS 服务提供商处删除这些记录。
  • --deploy-hook reload-ssl.sh​:指定部署钩子脚本。当证书获取或续订成功后,Certbot 会调用这个脚本。这里假设 reload-ssl.sh 脚本用于重新加载 Web 服务器的 SSL/TLS 配置,使新证书生效。

执行脚本并验证证书是否生成

ls /etc/letsencrypt/live/example.com/

更新脚本

certbot renew --manual --preferred-challenges dns -d example.com -d *.example.com \
        --email your_email \
        --agree-tos \
        --server https://acme-v02.api.letsencrypt.org/directory \
        --manual-auth-hook dnspod-add.sh \
        --manual-cleanup-hook dnspod-rm.sh \
        --deploy-hook reload-ssl.sh

和上方的申请脚本一致,将certonly换为renew

记录添加脚本

#!/bin/bash
# 填入你的 DNSPod API 密钥和域名
API_KEY="YOUR_API_KEY"
DOMAIN="example.com"
SUB_DOMAIN="_acme-challenge"
RECORD_TYPE="TXT"
DOMAIN_ID="YOUR_DOMAIN_ID"
REMOTE_URL="https://dnsapi.cn"
# 读取环境变量
# VALIDATION_DOMAIN=$CERTBOT_DOMAIN
VALIDATION_VALUE=$CERTBOT_VALIDATION

# 在这里添加你的代码,使用这些变量在你的DNS提供商处添加TXT记录
echo "Adding DNS record for $DOMAIN with $VALIDATION_VALUE"

# 使用 curl 添加 DNS 记录
curl -X POST "$REMOTE_URL/Record.Create" \
     -d "login_token=$API_KEY" \
     -d "format=json" \
     -d "domain=$DOMAIN" \
     -d "domain_id=$DOMAIN_ID" \
     -d "sub_domain=$SUB_DOMAIN" \
     -d "record_type=$RECORD_TYPE" \
     -d "record_line=默认" \
     -d "value=$VALIDATION_VALUE"
  • API_KEY​:DNSPod API 密钥,用于身份验证。
  • DOMAIN​:主域名,如 example.com
  • SUB_DOMAIN​:子域名,对于 Let's Encrypt 的 DNS 验证通常是 _acme-challenge
  • RECORD_TYPE​:DNS 记录类型,这里是 TXT,因为 Let's Encrypt 使用 TXT 记录进行验证。
  • DOMAIN_ID​:DNSPod 上域名的 ID,可以在 DNSPod 的域名列表中找到。
  • REMOTE_URL​:DNSPod API 的基础 URL。
  • CERTBOT_DOMAIN​(注释中):Certbot 提供的环境变量,表示当前要验证的域名。
  • CERTBOT_VALIDATION​:Certbot 提供的环境变量,表示用于验证的 TXT 记录值。

记录删除脚本

#!/bin/bash
# 填入你的 DNSPod API 密钥和域名
API_KEY="YOUR_API_KEY"
DOMAIN="example.com"
SUB_DOMAIN="_acme-challenge"
RECORD_TYPE="TXT"
DOMAIN_ID="YOUR_DOMAIN_ID"
REMOTE_URL="https://dnsapi.cn"

# 使用 curl 删除 DNS 记录
RESPONSE=$(curl -X POST "$REMOTE_URL/Record.List" \
     -d "login_token=$API_KEY" \
     -d "format=json" \
     -d "domain_id=$DOMAIN_ID" \
     -d "sub_domain=$SUB_DOMAIN" \
     -d "record_type=$RECORD_TYPE")
echo "Response id: $RESPONSE"

# 从返回的 JSON 中提取 RECORD_ID 并删除记录
# 这里需要解析 JSON 并提取 RECORD_ID,然后调用 Record.Delete
# 解析 JSON 响应以找到 RECORD_ID
RECORD_ID=$(echo $RESPONSE | jq -r '.["records"][].id')

echo -e "Record ID to delete: $RECORD_ID"

# 删除找到的记录
if [ ! -z "$RECORD_ID" ]; then
    readarray -t ids <<< "$RECORD_ID"  # 将 RECORD_ID 转换为数组
        for id in "${ids[@]}"; do
            curl -s -X POST "$REMOTE_URL/Record.Remove" \
                    -d "login_token=$API_KEY" \
                    -d "format=json" \
                    -d "domain=$DOMAIN" \
                    -d "domain_id=$DOMAIN_ID" \
                    -d "record_id=$id"
            done
else
    echo "No record found to delete."
fi

查找_acme-challenge记录并删除记录

总结

在以上申请脚本执行一次后,系统会自动在/etc/letsencrypt/renewal下生成example.com.conf以自动执行更新证书程序,默认每天两次,且指定了验证前、后以及完成后的钩子脚本地址,实现自动续期。

拓展

更新雷池的证书

#!/bin/bash

# 定义文件路径
LOCAL_FILE_PATH_CRT="/etc/letsencrypt/live/example.com/fullchain.pem"
LOCAL_FILE_PATH_KEY="/etc/letsencrypt/live/example.com/privkey.pem"

# API端点
API_ENDPOINT="https://example.com/api/open/cert"
SAFE_LINE_TOKEN="YOUR_TOKEN"

# 更新长亭雷池中证书
# 读取证书和密钥文件内容
CRT_CONTENT=""
KEY_CONTENT=""

# 读取文件内容,保留换行符
while IFS= read -r line; do
  CRT_CONTENT+="$line\n"
done < "$LOCAL_FILE_PATH_CRT"

while IFS= read -r line; do
  KEY_CONTENT+="$line\n"
done < "$LOCAL_FILE_PATH_KEY"

# 清除最后一个换行符
CRT_CONTENT=${CRT_CONTENT%'\n'}
KEY_CONTENT=${KEY_CONTENT%'\n'}

# 创建JSON体,id根据实际控制台请求数据来,type-2表示更新
JSON_BODY=$(cat <<EOF
{
    "id": 1,
    "manual": {
        "crt": "$CRT_CONTENT",
        "key": "$KEY_CONTENT"
    },
    "type": 2
}
EOF
)

# 发送POST请求
echo "Sending POST request to API endpoint..."
RESPONSE=$(curl -s -X POST "$API_ENDPOINT" -H "Content-Type: application/json" -H "X-SLCE-API-TOKEN: $SAFE_LINE_TOKEN" -d "$JSON_BODY")

# 检查响应
echo "POST request and response is: $RESPONSE"

发送证书到指定服务器(Nginx)

#!/bin/bash

# 定义主机地址
HOST="192.168.1.4"

# 私钥地址
PRIVATE_KEY_PATH=/root/.ssh/id_rsa

# 定义文件路径
LOCAL_FILE_PATH_CRT="/etc/letsencrypt/live/example.com/fullchain.pem"
LOCAL_FILE_PATH_KEY="/etc/letsencrypt/live/example.com/privkey.pem"
REMOTE_FILE_PATH_CRT="path_to_example.com_bundle.crt"
REMOTE_FILE_PATH_KEY="path_to_example.com.key"

# SSH 用户名和密码和端口
SSH_USER="root"
PORT=3800

# 第一步:使用sshpass和scp命令发送文件到MK-J
echo "Sending file to $HOST..."
scp -P $PORT -i $PRIVATE_KEY_PATH $LOCAL_FILE_PATH_CRT $SSH_USER@$HOST:$REMOTE_FILE_PATH_CRT
scp -P $PORT -i $PRIVATE_KEY_PATH $LOCAL_FILE_PATH_KEY $SSH_USER@$HOST:$REMOTE_FILE_PATH_KEY

注意:需要将本机的无密码公钥添加至对端服务器的authorized_keys中才能传输文件和执行命令。