早在去年年底和今年年初,各大证书厂商都陆续限制了免费 SSL 证书的有效期,从 12 个月缩短至 3 个月。时间将至,来看本文的兄弟们证书是否快要过期了?哈哈~

ACME: Automatic Certificate Management Environment(自动证书管理环境),是一种用于自动化管理和获取 SSL\TLS 证书的协议。

ACME 提供了一种标准化的方式,使能够自动请求、验证和获取证书,无需人工干预。完成标准化的获取证书流程需要 ACME 客户端与 ACME 服务端进行通信,常见的 ACME 的客户端有:acme.shcertbot

本文将使用 ACME 协议的 acme.sh 客户端脚本和支持 ACME 服务端的 CA Let’s Encrypt 实现自动证书管理,并自动部署至腾讯云和阿里云 CDN。

acme.sh

主要实现逻辑依靠 acme.sh 的证书申请 和 dns 验证。

相关命令

帮助理解 acme 脚本,方便后续自定义,若无需求可忽略。(actionsubuntu 环境,理论其他环境逻辑同。若需要自定义,建议先阅读文档)

安装 acme.sh

curl  https://get.acme.sh | sh -s email=$EMAIL

执行完上述命令后,会创建并复制 acme 脚本至 ~/.acme.sh/actions/home/runner/.acme.sh),后续安装的证书默认也会在此文件夹下。

email 参数为申请证书使用的邮箱,例如 Let’s EncryptACME 服务端需要邮箱注册账户、提供邮件提醒等。

生成证书 - DNSpod

  1. 使用 DNSpod api--dns dns_dp)进行自动域名 TXT 解析,验证域名所有权。
  2. DNSpod Token 中获取的 ID(对应 DP_Id) 和 Token(对应 DP_Key
  3. 使用 证书 CAletsencrypt --server letsencrypt
  4. -dksh7.com*.ksh7.com 为多个域名申请证书(或泛域名证书)
export DP_Id="1234"

export DP_Key="sADDsdasdgdsf"

acme.sh --issue --server letsencrypt --dns dns_dp -d ksh7.com -d *.ksh7.com

更换 CA

acme.sh 默认使用 ZeroSSL CA,命令中不指定 server 即可,若需要更换其他 CA 可参考 Server 描述。

CA 之间的对比

创建 DNSpod Token

进入 DNSpod Token 页面(点击链接进入即可,或者右上角点击 API 密钥),选择 创建密钥,获得的 IDToken 分别对应 DP_IdDP_Key

证书验证成功后,会自动添加和删除对应的 TXT 记录,配置了几个域名就会有几条记录,可在 DNSpod 操作记录 中查看。

生成证书 - 阿里云

export Ali_Key="<key>"
export Ali_Secret="<secret>"

acme.sh --issue --dns dns_ali -d ksh7.com -d *.ksh7.com --server letsencrypt

使用主账号的 AccessKey

进入 访问凭证管理 或右上角选择 AccessKey 管理,创建 AccessKey 或者使用之前的 AccessKey 即可,AccessKey IDAccessKey Secret 分别对应 Ali_Key Ali_Secret。简单暴力,但需要注意权限。

使用子账号的 AccessKey

子账号创建流程参考 CertCloud
更多关于 DNS API 的说明或其他平台(如 cloudflareaws 等)请参考 How to use DNS API

安装证书

申请完证书后,证书默认存放在 ~/.acme.sh/ 文件夹下(可以在 --issue 时 指定 --cert-home,或者设置环境变量 export CERT_HOME 更改路径),若你需要将证书部署至服务器的指定文件下,如 NginxApache 可使用 --installcert 命令

acme.sh --installcert -d ksh7.com -d *.ksh7.com --key-file /mycertify/ssl/ksh7.com.key --fullchain-file /mycertify/ssl/ksh7.com.cer

部署证书

使用 Actions 将证书部署至 CDN 平台

腾讯 Cdn

使用 renbaoshuo/deploy-certificate-to-tencentcloud 插件,将证书部署至 CDN 平台。

需要配置 SecretIdSecretKey,可在 API密钥管理 直接申请(权限较高),或参考 申请子账号 API 密钥

账户需要权限:QcloudCDNFullAccess、QcloudSSLFullAccess

deploy-to-tencent-cdn:
  name: Deploy certificate to Tencent Cloud CDN
  runs-on: ubuntu-latest
  # 需要在申请完证书后进行部署任务
  needs: issue

  steps:
    - name: Check out
      uses: actions/checkout@v2
      with:
        ref: ${{ github.ref }}

    - uses: renbaoshuo/deploy-certificate-to-tencentcloud@v2
      with:
        secret-id: ${{ secrets.SECRETID }}
        secret-key: ${{ secrets.SECRETKEY }}
        # cert file like ksh7.com.cer
        # 证书路径
        fullchain-file: ${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_FULLCHAIN }}
        # 私钥路径
        key-file: ${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_KEY }}
        cdn-domains: |
          ksh7.com

阿里云 CDN

AccessKey 的配置,可参考 生成证书-阿里云

deploy-to-aliyun:
  name: Deploy Certificate to Aliyun
  runs-on: ubuntu-latest
  needs: issue-ssl-certificate

  steps:
    - name: Checkout
      uses: actions/checkout@v2

    # 上传证书
    - name: Deploy certificate to aliyun
      uses: Menci/deploy-certificate-to-aliyun@beta-v1
      with:
        access-key-id: ${{ secrets.ALIYUN_ACCESS_KEY_ID }}
        access-key-secret: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}
        fullchain-file: ${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_FULLCHAIN }}
        key-file: ${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_KEY }}
        certificate-name: example.com
        cdn-domains: |
          ksh7.com

申请证书

在使用 Actions 之前,可以通过 acme.sh 脚本将相关流程本地测试一次,可有效减少未知问题。

需要注意!证书与密钥文件需要妥善保管,请勿使用 公开仓库储存

Menci/acme

使用 Actions 插件来快速开启证书申请流程。参数详解直达

jobs:
  issue-ssl-certificate:
    name: Issue SSL certificate
    runs-on: ubuntu-latest
    steps:
      - uses: Menci/acme@v2
        with:
          version: 3.0.2

          # Register your account and try issue a certificate with DNS API mode
          # Then fill with the output of `tar cz ca account.conf | base64 -w0` running in your `~/.acme.sh`
          account-tar: ${{ secrets.ACME_SH_ACCOUNT_TAR }}

          domains: example.com example.net example.org example.edu
          domains-file: ''
          append-wildcard: true

          arguments: --dns dns_cf --challenge-alias example.com
          arguments-file: ''

          output-fullchain: output/fullchain.pem
          output-key: output/key.pem
          output-pfx: output/certificate.pfx
          output-pfx-password: qwq

          # uninstall: true # Uninstall acme.sh after this action by default

自定义 acme.shActions

支持自动申请证书、以及每天检查证书是否过期,并自动重新申请证书(时间小于30天)。申请 ECCRSA泛域名证书。

使用 腾讯 CDN 的完整案例,详细文档和参数请转至:auto-ssl-cert

name: DnsPod SSL Certificates

on:
  schedule: # execute every 24 hours
    - cron: '35 6 * * *'
  workflow_dispatch:

env:
  ACME: /home/runner/.acme.sh/acme.sh
  DP_ID: ${{ secrets.DP_ID }}
  DP_KEY: ${{ secrets.DP_KEY }}
  EMAIL: ${{ secrets.EMAIL }}

jobs:
  issue-cert:
    runs-on: ubuntu-latest
    outputs:
      certificate_updated: ${{ steps.check_certificate.outputs.certificate_updated }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Install & Configure acme.sh
        run: |
          curl  https://get.acme.sh | sh -s email=$EMAIL
      - name: Issue & Deploy Certificates
        id: check_certificate
        run: |
          export DP_Id=$DP_ID
          export DP_Key=$DP_KEY

          git config --global user.email $EMAIL
          git config --global user.name acme

          # 如果想要其他证书发行机构,可以把 acme.sh 的ca目录拷贝到 repo 的 ca目录
          # mkdir -p /home/runner/.acme.sh/ca/
          # cp -r ca/* /home/runner/.acme.sh/ca/          

          certificate_updated=false

          check_certificate_validity() {
            cert_path=$1
            if [ -f "$cert_path" ]; then
              if openssl x509 -checkend $(( 30 * 86400 )) -noout -in "$cert_path"; then
                echo "Certificate at $cert_path is valid for more than 30 days, skipping..."
                return 0
              else
                return 1
              fi
            else
              return 1
            fi
          }

          issue_and_install_certificate() {
            domain=$1
            cert_type=$2 # "EC" or "RSA"
            acme_server=$3 # default choose "letsencrypt" 其他 CA 请参考 https://github.com/acmesh-official/acme.sh/wiki/CA
            keylength=$4 # empty for EC, "3072" for RSA

            cert_path="./ssl/$domain"
            [ "$cert_type" = "RSA" ] && cert_path="$cert_path/rsa"
            cert_file="$cert_path/$domain.cer"
            key_file="$cert_path/$domain.key"
            
            # Issue certificate          
            issue_status=0
            $ACME --issue --server $acme_server --debug --dns dns_dp -d "$domain" -d "*.$domain" ${keylength:+--keylength $keylength}|| issue_status=$?
            if [ $issue_status -ne 0 ]; then
              echo "Failed to issue $cert_type certificate for $domain, skipping..."
              return
            fi

            # Install certificate          
            install_status=0
            $ACME --installcert -d "$domain" --key-file "$key_file" --fullchain-file "$cert_file" || install_status=$?
            if [ $install_status -ne 0 ]; then
              echo "Failed to install $cert_type certificate for $domain, skipping..."
              return
            fi

            certificate_updated=true
            TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
            git add $cert_path/
            git commit -m "Update $cert_type certificate files for $domain at $TIMESTAMP"
          }

          while IFS= read -r domain || [ -n "$domain" ]; do
            mkdir -p "./ssl/$domain/rsa"

            # Check and issue/install EC certificate
            if ! check_certificate_validity "./ssl/$domain/$domain.cer"; then
              issue_and_install_certificate "$domain" "EC" "letsencrypt" ""
            fi

            # Check and issue/install RSA certificate
            if ! check_certificate_validity "./ssl/$domain/rsa/$domain.cer"; then
              issue_and_install_certificate "$domain" "RSA" "letsencrypt" "3072"
            fi

          done < dnspod_domains_list.txt

          echo "certificate_updated=$certificate_updated" >> $GITHUB_OUTPUT

      - name: Push changes
        if: steps.check_certificate.outputs.certificate_updated == 'true'
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

  deploy-to-tencent-cdn:
    name: Deploy certificate to Tencent Cloud CDN
    runs-on: ubuntu-latest
    needs: issue-cert
    if: needs.build.outputs.certificate_updated == 'true'

    steps:
      - name: Check out
        uses: actions/checkout@v2
        with:
          ref: ${{ github.ref }}

      - uses: renbaoshuo/deploy-certificate-to-tencentcloud@v2
        with:
          secret-id: ${{ secrets.SECRETID }}
          secret-key: ${{ secrets.SECRETKEY }}
          # cert file like ksh7.com.cer
          fullchain-file: ${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_FULLCHAIN }}
          key-file: ${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_KEY }}
          cdn-domains: |
            ksh7.com

其他推荐

httpsok

https://httpsok.com/doc/

相关链接

申请证书与部署证书参考: auto-ssl-cert
申请证书参考:auto-ssl
部署腾讯 CDN Action deploy-certificate-to-tencentcloud
部署腾讯 CDN Action 功能更完善 qcloud-ssl-cdn