之前写过一篇使用 acme.sh 申请 SSL 证书的帖子,最近把这个流程放到了 GitHub Action 上自动化完成,在这儿记录一下要点。
GitHub Action 是托管在 GitHub 上的自动化服务,免费账户的私有仓库每月有 2000 分钟的运行额度,可以用来做很多运维、CI 相关的工作。
要使用 GitHub Action,只需在项目的根目录下的 .github/workflows 中创建一个 .yml 文件即可,比如新建一个 .github/workflows/renew-ssl.yml 。
基本设置
这个 action 的基本结构类似下面这样:
name: Renew SSL Certificate
on:
schedule:
- cron: '0 20 * * 0' # 每周一 UTC 20:00(北京时间 04:00)
workflow_dispatch: # 支持手动触发
permissions:
contents: read
jobs:
renew-cert:
runs-on: ubuntu-latest
steps:
- name: Task name
run: |
YOUR SCRIPT代码很直白,这儿就不多解释了,下面继续看最主要的任务步骤(steps)。
第一步:安装 acme.sh
我们要使用 acme.sh 来申请 SSL 证书,因此第一步需要安装 acme.sh。在以上 YML 文件的 steps 部分,添加以下代码:
- name: Install acme.sh
run: |
curl https://get.acme.sh | sh -s email=${{ secrets.ACME_EMAIL }}第二步:申请证书
接下来,则是使用 acme.sh 申请域名证书。这儿我们申请了一个泛域名证书:
- name: Issue or Renew Certificate
id: issue-cert
env:
Ali_Key: ${{ secrets.ALI_KEY }}
Ali_Secret: ${{ secrets.ALI_SECRET }}
run: |
~/.acme.sh/acme.sh --issue \
-d oldj.net \
-d '*.oldj.net' \
--dns dns_ali \
--keylength ec-256你需要将上面代码中的 oldj.net 替换为你自己的域名。申请泛域名证书时,需要同时传入 your-domain.com 和 *.your-domain.com。
我的域名是在阿里云上,因此 --dns 参数设置的是 dns_ali 。如果你的域名在其他注册商那里,需要把这个参数改为对应的值,具体可查看 acme.sh 的文档。
注意,这儿用到了环境变量 secrets.ALI_KEY 和 secrets.ALI_SECRET ,这两个值需要你去阿里云后台生成,然后在 GitHub 项目仓库的后台添加。
阿里云后台具体位置是 AccessKey 管理页面,建议创建一个子账号,只授予 AliyunDNSFullAccess 权限即可。
GitHub 添加 Secrets 的位置是:仓库页面 → Settings → Secrets and variables → Actions → New repository secret。
第三步:上传到服务器
证书需要上传到服务器才能发挥作用,在这儿我们使用 SSH 的方式连接服务器。为了能顺利连接服务器,需要你准备一对 SSH 密钥,将公钥上传到服务器的 ~./ssh/authorized_keys 下,同时像上面那样将密钥添加到 GitHub Secrets 中,名称是 SSH_PRIVATE_KEY 。
接下来,再在 renew-ssl.yml 中继续添加以下代码:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
echo "StrictHostKeyChecking no" > ~/.ssh/config再接下来是正式的上传步骤:
- name: Deploy to Server-001
if: steps.issue-cert.outcome == 'success'
continue-on-error: true
run: |
scp ~/.acme.sh/oldj.net_ecc/fullchain.cer \
~/.acme.sh/oldj.net_ecc/oldj.net.key \
${{ secrets.SERVER_USER_GATEWAY }}@${{ secrets.SERVER_HOST_GATEWAY }}:${{ secrets.CERT_REMOTE_DIR }}/
ssh ${{ secrets.SERVER_USER_GATEWAY }}@${{ secrets.SERVER_HOST_GATEWAY }} "sudo nginx -s reload"注意 SERVER_USER_GATEWAY 和 SERVER_HOST_GATEWAY 分别是你的服务器的用户名和地址(IP 或域名),CERT_REMOTE_DIR 是你准备把证书上传到的服务器文件夹,需要先创建对应的文件夹。这三个变量也像上面一样添加到 GitHub Secrets 中。
步骤的第一行 if: steps.issue-cert.outcome == 'success' 是要确保前面申请证书成功才会执行后续的操作。
最后一行的 sudo nginx -s reload 作用是让服务器上的 Nginx 重新加载配置以及证书,没有这一句,即使证书文件已经上传了,Nginx 仍会继续使用老证书,直到下一次重载或重启。
到这儿,证书的申请 → 上传 → 应用流程就基本完整了,如果你有多台服务器在使用这个证书,可以继续添加上传任务。
更新腾讯云上的证书
我在使用腾讯云 CDN,所以也研究了一下如何自动更新腾讯云的上传证书。其他云服务商应该也有类似的接口。
另外,腾讯云的 EdgeOne 已经可以自动申请和更新免费证书了,无需再自己上传,不过传统的 CDN 服务目前似乎还需要自己管理免费证书。
相关代码如下:
- name: Upload to Tencent Cloud
if: steps.issue-cert.outcome == 'success'
continue-on-error: true
run: |
pip install tccli
# 配置腾讯云 CLI
tccli configure set secretId ${{ secrets.TENCENT_SECRET_ID }}
tccli configure set secretKey ${{ secrets.TENCENT_SECRET_KEY }}
tccli configure set region ap-guangzhou
CERT=$(cat ~/.acme.sh/oldj.net_ecc/fullchain.cer | base64 -w 0)
KEY=$(cat ~/.acme.sh/oldj.net_ecc/oldj.net.key | base64 -w 0)
# 上传新证书
RESULT=$(tccli ssl UploadCertificate \
--CertificatePublicKey "$(cat ~/.acme.sh/oldj.net_ecc/fullchain.cer)" \
--CertificatePrivateKey "$(cat ~/.acme.sh/oldj.net_ecc/oldj.net.key)" \
--Alias "oldj.net-wildcard-$(date +%Y%m%d)")
NEW_CERT_ID=$(echo "$RESULT" | jq -r '.CertificateId')
echo "New certificate ID: $NEW_CERT_ID"
# 如果有旧证书 ID,自动替换关联资源
if [ -n "${{ secrets.TENCENT_OLD_CERT_ID }}" ]; then
tccli ssl UpdateCertificateInstance \
--OldCertificateId ${{ secrets.TENCENT_OLD_CERT_ID }} \
--ResourceTypes '["cdn"]' \
--CertificateId "$NEW_CERT_ID"
echo "Associated resources updated from ${{ secrets.TENCENT_OLD_CERT_ID }} to $NEW_CERT_ID"
fi
# 自动更新 Secret 中的证书 ID,供下次续期使用
echo "$NEW_CERT_ID" | gh secret set TENCENT_OLD_CERT_ID
echo "TENCENT_OLD_CERT_ID updated to: $NEW_CERT_ID"
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
GH_REPO: ${{ github.repository }}请注意将代码中的证书路径(本例中是 oldj.net_ecc)替换为你的实际路径。
这段代码中使用了腾讯云的 tccli 来实现证书上传和更新工作,和上面类似,你需要先设置一些 Secrets:
TENCENT_SECRET_ID腾讯云 API SecretIdTENCENT_SECRET_KEY腾讯云 API SecretKeyTENCENT_OLD_CERT_ID当前正在使用的证书 ID(如果仅上传,不需要自动替换老证书,可不填此项)
其中 TENCENT_SECRET_*可以前往腾讯云 API 密钥管理页面生成,建议创建子账号,授予 QcloudSSLFullAccess 权限,以及你需要更新的资源的权限,比如 QcloudCDNFullAccess 。
上面的 --ResourceTypes 用于指定要更新的资源,这儿我只写了 CDN,你可以根据需要调整。
首次运行前,需要你先设置一下要更新的证书 ID。由于每次自动更新证书之后,证书 ID 都会变化,为了实现完全自动化,这儿添加了一个 GH_PAT 参数,用于记录你的 GitHub Personal Access Token 。
这个 GitHub Personal Access Token 的创建方式如下:
进入 GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens
选择对应的组织以及仓库,权限勾选 Secrets → Read and Write
生成后,将 Token 存入仓库 Secret 中,名称是
GH_PAT
这样自动更新腾讯云上的证书的流程便也自动化了。
评论: