oimi分享美好数字生活 oimi分享美好数字生活
  • 首页
  • AI
  • Lab
  • Apple
  • 生活方式
  • 硬件
  • 0
  • 0

使用 GitHub Actions 自动申请与部署 SSL 证书

OIMI
18 5 月, 2022

对于一个有很多服务器的人来说,在不同服务器上同步 SSL 证书是一件麻烦事。笔者尝试过很多种方式,最后在 Menci 的推荐下选定了使用 GitHub Actions 来自动申请、续期 SSL 证书,并自动推送到各个服务器上。

本博客的证书也是使用这种方式进行签发、部署的,可以点击浏览器地址栏上的按钮查看证书。

申请证书

前期准备

首先请在本地(或自己的服务器上)成功使用 acme.sh 的 DNS-01 验证方式成功申请一次证书,

  1. 向 CA 注册 ACME 账户(如果使用 Let’s Encrypt 则会自动进行,详细步骤请参阅 acme.sh 的 Wiki)。
  2. 通过环境变量指定 DNS 提供商的凭据,用于添加/删除 ACME DNS-01 认证所需的 TXT 记录。
  3. 确认证书申请可以成功,为后续调试排除可能的问题。

第一次申请证书后,CA 的 ACME 账户凭据将被存储到 ~/.acme.sh/ca 中,DNS 提供商的凭据将被存储到 ~/.acme.sh/account.conf 中。将它们打包并使用 Base64 编码存储,以备在 GitHub Actions 中使用:

cd ~/.acme.sh
tar cz ca account.conf | base64 -w0

将输出内容添加到 GitHub 仓库的 Secrets 中。注意不要复制输出中的多余信息。

自动化

如果没有特殊需求,可以使用 Menci/acme 来简单地申请证书:

# 全局环境变量
env:
  # Checkout 到的目录
  CERTS_OUTPUT_BASE: certs
  # 证书输出目录
  CERTS_OUTPUT_DIRECTORY: example.com
  # 证书文件名
  FILE_FULLCHAIN: fullchain.pem
  # 私钥文件名
  FILE_KEY: privatekey.key

jobs:
  issue-ssl-certificate:
    name: Issue SSL certificate
    runs-on: ubuntu-latest
    steps:
      - uses: Menci/acme@v1
        with:
          # 指定 acme.sh 的版本
          version: 3.0.2

          # 上方保存的以 Base64 编码存储的凭据
          account-tar: ${{ secrets.ACME_SH_ACCOUNT_TAR }}

          # 域名列表,以空格分隔
          domains: example.com example.net example.org example.edu
          # 是否申请通配符
          append-wildcard: true

          # 传递给 acme.sh 的额外参数
          arguments: --dns dns_cf --challenge-alias example.com

          # 导出的证书路径
          output-fullchain: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_FULLCHAIN }}
          output-key: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_KEY }}

如果需要高度自定义 acme.sh 的参数,比如为不同的域名设置不同的 DNS 提供商,可以使用下面的方式手动编写命令来执行:

# 全局环境变量
env:
  # Checkout 到的目录
  CERTS_OUTPUT_BASE: certs
  # 证书输出目录
  CERTS_OUTPUT_DIRECTORY: example.com
  # 证书文件名
  FILE_FULLCHAIN: fullchain.pem
  # 私钥文件名
  FILE_KEY: privatekey.key

jobs:
  issue-ssl-certificate:
    name: Issue SSL certificate
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: master

      - name: Checkout output branch
        uses: actions/checkout@v2
        with:
          ref: certs
          path: ${{ env.CERTS_OUTPUT_BASE }}

      # 安装 acme.sh
      - name: Install acme.sh
        shell: bash
        run: curl -s https://get.acme.sh | sh

      # 解压 acme.sh 配置信息
      - name: Extract account files for acme.sh
        shell: bash
        run: |
          echo "$ACME_SH_ACCOUNT_TAR" | base64 -d | tar -C ~/.acme.sh -xz
        env:
          # Base64 编码的 acme.sh 配置信息
          ACME_SH_ACCOUNT_TAR: ${{ secrets.ACME_SH_ACCOUNT_TAR }}

      # 申请证书
      - name: Issue SSL certificates
        shell: bash
        run: |
          ~/.acme.sh/acme.sh --issue        \
            -d "example.com"   --dns dns_cf \
            -d "*.example.com" --dns dns_cf \
            -d "example.net"   --dns dns_dp \
            -d "*.example.net" --dns dns_dp \
            --server letsencrypt

      # 导出证书
      - name: Copy certificate to output paths
        shell: bash
        run: |
          ACME_SH_TEMP_DIR="$(mktemp -d)"
          ACME_SH_TEMP_FILE_FULLCHAIN="$ACME_SH_TEMP_DIR/fullchain.pem"
          ACME_SH_TEMP_FILE_KEY="$ACME_SH_TEMP_DIR/key.pem"

          ~/.acme.sh/acme.sh --install-cert -d "$ACME_SH_FIRST_DOMAIN" --fullchain-file "$ACME_SH_TEMP_FILE_FULLCHAIN" --key-file "$ACME_SH_TEMP_FILE_KEY"

          [[ -z "$ACME_SH_OUTPUT_FULLCHAIN" ]] || (mkdir -p "$(dirname "$ACME_SH_OUTPUT_FULLCHAIN")" && cp "$ACME_SH_TEMP_FILE_FULLCHAIN" "$ACME_SH_OUTPUT_FULLCHAIN")
          [[ -z "$ACME_SH_OUTPUT_KEY" ]] || (mkdir -p "$(dirname "$ACME_SH_OUTPUT_KEY")" && cp "$ACME_SH_TEMP_FILE_KEY" "$ACME_SH_OUTPUT_KEY")

          rm -rf "$ACME_SH_TEMP_DIR"
        env:
          # 修改此处的 example.com 为申请时填写的第一个域名
          ACME_SH_FIRST_DOMAIN: example.com
          ACME_SH_OUTPUT_FULLCHAIN: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_FULLCHAIN }}
          ACME_SH_OUTPUT_KEY: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_KEY }}

上传证书至仓库

# 上传证书
- name: Push to GitHub
  run: |
    git config --global user.name "BaoshuoBot"
    git config --global user.email "79077260+BaoshuoBot@users.noreply.github.com"

    cd "$CERTS_DIRECTORY"

    git add "$FILE_FULLCHAIN" "$FILE_KEY"
    git commit -m "Upload certificates on $(date '+%Y-%m-%d %H:%M:%S')"
    git push
  env:
    TZ: Asia/Shanghai
    CERTS_DIRECTORY: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}

部署证书

在申请证书的 Job 执行完成后,可以执行一系列其他的 Job 来将证书部署到各个服务器或云服务。

服务器

可以使用 easingthemes/ssh-deploy 来使用 rsync 将证书同步到服务器上。同步完成后再使用 appleboy/ssh-action 远程执行命令重载 Nginx / Apache。

# 部署到服务器
deploy-to-server:
  name: Deploy Certificate to Server
  runs-on: ubuntu-latest
  needs: issue-ssl-certificate

  strategy:
    matrix:
      host:
        - 174.136.239.1 # Server 1
        - 174.136.239.2 # Server 2
        # ...
        - 174.136.239.254 # Server N

  steps:
    - name: Checkout
      uses: actions/checkout@v2
      with:
        ref: certs

    # 上传证书
    - name: Upload certificate to server
      uses: easingthemes/ssh-deploy@v2.1.5
      env:
        SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        ARGS: '-avz --delete'
        REMOTE_HOST: ${{ matrix.host }}
        REMOTE_USER: ${{ secrets.REMOTE_USER }}
        SOURCE: ${{ env.CERTS_OUTPUT_DIRECTORY }}/
        TARGET: /path/to/ssl/certs/${{ env.CERTS_OUTPUT_DIRECTORY }}/

    # 重载 Nginx
    - name: Force-reload nginx
      uses: appleboy/ssh-action@v0.1.4
      with:
        host: ${{ matrix.host }}
        username: ${{ secrets.REMOTE_USER }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          sudo /opt/hooks/reload-nginx.sh

需要注意的是,重载 Nginx / Apache 的命令需要 root 权限才能执行,可以采用只允许部署用户以 root 权限执行重载脚本的方式来避免出现安全问题。

在 /opt/hooks 目录下新建一个文件 reload-nginx.sh,内容如下:

#!/bin/bash
sudo systemctl force-reload nginx

然后新建一个名为 actions-cert 的用户,然后在 /etc/sudoers 文件中添加以下内容:

actions-cert ALL=(ALL) NOPASSWD: /opt/hooks/reload-nginx.sh

这个配置可以使 actions-cert 用户免密码以 root 用户的权限执行 /opt/hooks/reload-nginx.sh。

最后使用 chmod 755 /opt/hooks/reload-nginx.sh 命令将 reload-nginx.sh 文件设置为可执行,同时禁止非所有者对其进行写入操作。

如果服务器位于 NAT 后,或者禁止了 SSH 连接,还有两个方法可以将证书部署到内网服务器上:

  1. 将证书先部署到有部署条件的服务器上,然后再在内网服务器上使用 rsync 从部署好的服务器上拉取证书。
  2. 将证书上传到 Azure Key Vault 等托管服务中,再在服务器上按照 Menci 的文章 中的教程拉取即可。

阿里云

阿里云的 SSL 证书服务 支持上传自定义证书,该证书可以用于 阿里云 CDN。阿里云暂未提供将证书部署至 OSS 的 API,建议 OSS 用户使用 CDN 回源 OSS 来代替。

使用 Menci/deploy-certificate-to-aliyun 将证书部署到阿里云:

# 部署到阿里云
deploy-to-server:
  name: Deploy Certificate to Aliyun
  runs-on: ubuntu-latest
  needs: issue-ssl-certificate

  steps:
    # 拉取证书存储分支
    - name: Checkout
      uses: actions/checkout@v2
      with:
        ref: certs

    # 上传证书
    - 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: |
          example.com
          example.net

其中 certificate-name 指定上传的证书在证书服务中的名称(将自动替换旧版本),cdn-domain 指定需要将该证书部署到的 CDN 域名列表(用空白字符隔开)。

建议使用子账户 Access Key,为其赋予以下权限(并按需使用资源组隔离):

  • AliyunYundunCertFullAccess
  • AliyunCDNFullAccess
  • AliyunPCDNFullAccess
  • AliyunSCDNFullAccess
  • AliyunDCDNFullAccess

完整例子

这个 Action 完成了以下操作:

  1. 申请证书,并上传到仓库的 certs 分支。
  2. 在申请证书后将 certs 分支中的证书部署到服务器上。
# 名称
name: Issue SSL Certificates

# 触发条件
on:
  # 手动运行
  workflow_dispatch:
  # 定时运行
  schedule:
    # 每两个月运行一次
    - cron: '0 0 1 */2 *'

# 全局环境变量
env:
  # Checkout 到的目录
  CERTS_OUTPUT_BASE: certs
  # 证书输出目录
  CERTS_OUTPUT_DIRECTORY: example.com
  # 证书文件名
  FILE_FULLCHAIN: fullchain.pem
  # 私钥文件名
  FILE_KEY: privatekey.key

jobs:
  issue-ssl-certificate:
    # 申请证书并 push 到 certs 分支
    name: Issue SSL certificate
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: master

      - name: Checkout output branch
        uses: actions/checkout@v2
        with:
          ref: certs
          path: ${{ env.CERTS_OUTPUT_BASE }}

      # 安装 acme.sh
      - name: Install acme.sh
        shell: bash
        run: curl -s https://get.acme.sh | sh

      # 解压 acme.sh 配置信息
      - name: Extract account files for acme.sh
        shell: bash
        run: |
          echo "$ACME_SH_ACCOUNT_TAR" | base64 -d | tar -C ~/.acme.sh -xz
        env:
          # Base64 编码的 acme.sh 配置信息
          ACME_SH_ACCOUNT_TAR: ${{ secrets.ACME_SH_ACCOUNT_TAR }}

      # 申请证书
      - name: Issue SSL certificates
        shell: bash
        run: |
          ~/.acme.sh/acme.sh --issue            \
            -d "example.com" -d "*.example.com" \
            --dns dns_cf --server letsencrypt

      # 导出证书
      - name: Copy certificate to output paths
        shell: bash
        run: |
          ACME_SH_TEMP_DIR="$(mktemp -d)"
          ACME_SH_TEMP_FILE_FULLCHAIN="$ACME_SH_TEMP_DIR/fullchain.pem"
          ACME_SH_TEMP_FILE_KEY="$ACME_SH_TEMP_DIR/key.pem"

          # 不要忘记修改这里的 -d 参数值为上方的第一个域名
          ~/.acme.sh/acme.sh --install-cert -d "example.com" --fullchain-file "$ACME_SH_TEMP_FILE_FULLCHAIN" --key-file "$ACME_SH_TEMP_FILE_KEY"

          [[ -z "$ACME_SH_OUTPUT_FULLCHAIN" ]] || (mkdir -p "$(dirname "$ACME_SH_OUTPUT_FULLCHAIN")" && cp "$ACME_SH_TEMP_FILE_FULLCHAIN" "$ACME_SH_OUTPUT_FULLCHAIN")
          [[ -z "$ACME_SH_OUTPUT_KEY" ]] || (mkdir -p "$(dirname "$ACME_SH_OUTPUT_KEY")" && cp "$ACME_SH_TEMP_FILE_KEY" "$ACME_SH_OUTPUT_KEY")

          rm -rf "$ACME_SH_TEMP_DIR"
        env:
          ACME_SH_OUTPUT_FULLCHAIN: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_FULLCHAIN }}
          ACME_SH_OUTPUT_KEY: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}/${{ env.FILE_KEY }}

      # 上传证书
      - name: Push to GitHub
        run: |
          git config --global user.name "BaoshuoBot"
          git config --global user.email "79077260+BaoshuoBot@users.noreply.github.com"

          cd "$CERTS_DIRECTORY"

          git add "$FILE_FULLCHAIN" "$FILE_KEY"
          git commit -m "Upload certificates on $(date '+%Y-%m-%d %H:%M:%S')"
          git push
        env:
          TZ: Asia/Shanghai
          CERTS_DIRECTORY: ${{ env.CERTS_OUTPUT_BASE }}/${{ env.CERTS_OUTPUT_DIRECTORY }}

  # 部署证书到服务器
  deploy-to-server:
    name: Deploy Certificate to Server
    runs-on: ubuntu-latest
    needs: issue-ssl-certificate

    strategy:
      matrix:
        host:
          - 174.136.239.1 # Server 1
          - 174.136.239.2 # Server 2
          # ...
          - 174.136.239.254 # Server N

    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: certs

      # 上传证书
      - name: Upload certificate to server
        uses: easingthemes/ssh-deploy@v2.1.5
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          ARGS: '-avz --delete'
          REMOTE_HOST: ${{ matrix.host }}
          REMOTE_USER: ${{ secrets.REMOTE_USER }}
          SOURCE: ${{ env.CERTS_OUTPUT_DIRECTORY }}/
          TARGET: /path/to/ssl/certs/${{ env.CERTS_OUTPUT_DIRECTORY }}/

      # 重载 Nginx
      - name: Force-reload nginx
        uses: appleboy/ssh-action@v0.1.4
        with:
          host: ${{ matrix.host }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            sudo /opt/hooks/reload-nginx.sh

杂项

部分情况下,GitHub Actions 中的 GITHUB_TOKEN 只有 Read repository contents permission,而本文中的 Actions 要求这个 Token 具有 Read and write permissions,那么需要在仓库的 Settings > Actions > General 页面的底部赋予其写入权限,如图所示:

使用 GitHub Actions 自动申请与部署 SSL 证书-oimi分享美好数字生活

设置好后点击 Save 按钮即可。

0
本文系作者 @宝硕 授权发布在 oimi分享美好数字生活。未经许可,禁止转载。
Win/Mac/Linux] B站视频可视化下载器BiliDownloader 支持8K/杜比世界/交互视频等
上一篇
HEU KMS Activator v24.6.3更新~Office又一波“非正版弹窗”来袭
下一篇

评论 (0)

再想想
暂无评论

聚合文章

理想星环OS开源项目
亚马逊云科技部署DeepSeek模型
重磅发布:Windows/Office被国外大神破解,全部离线永久激活!
OOMOL Studio 免费、底层开源
DeepSeek开源周首日推出FlashMLA项目 可以显著降低内存占用和计算开销
DeepSeek-R1 发布,性能对标 OpenAI o1 正式版
HEU KMS Activator v42.3.2
Windows/Office通用激活神器MAS v2.9版发布
2024年12月,Windows11 24H2官方原版ISO的系统版本号为26100.2605
小米官方发布「米家集成」,可在 Home Assistant 中使用小米 IoT 智能设备

Android 11 Apache Apple M1 Aria2 AWS Route53 Calm CDN ChatGPT Cloudflare CloudXNS CNNIC Debian Debian 9 Debian 9 Stretch DNS DNSpod Domain Name System gdnsd Google Play HEU KMS Activator iOS 11 iOS11 Beta2 iOS11 Beta2 update1 LEMP Let’s Encrypt LNMP macOS MySQL Nginx OEM PanDownload PHP Postgres PowerDNS PowerDNS-Admin Pure DNS Spectre SQLite3 Stretch Windows 11 24H2 Youtube 公共DNS 数字许可证激活 自控力 降级iOS10

猜你喜欢

理想星环OS开源项目

理想星环OS开源项目

3 5 月, 2025
32 0 0
亚马逊云科技部署DeepSeek模型

亚马逊云科技部署DeepSeek模型

2 4 月, 2025
152 0 0
重磅发布:Windows/Office被国外大神破解,全部离线永久激活!

重磅发布:Windows/Office被国外大神破解,全部离线永久激活!

24 2 月, 2025
360 0 0
OOMOL Studio 免费、底层开源

OOMOL Studio 免费、底层开源

24 2 月, 2025
302 0 0

关于

OIMI(oimi.me)是分享美好数字生活的内容平台,同时还涉及 macOS、iOS 等知名系统的使用技巧。 科技 / 旅行 / 摄影 / 生活方式

社交媒体

Nicky

导航

Nicky
Copyright © 2016-2025 oimi分享美好数字生活. Designed by OIMI.
  • ChatTTS,HyperOS,HEU KMS Activator,Win10/11数字权利激活

OIMI

258
文章
2
评论
135
喜欢