- 申请泛域名证书, aciton 自动更新.
- 资料来源:
https://jixun.uk/posts/2023/configure-wildcard-ssl/
https://blog.baoshuo.ren/post/actions-ssl-cert/
导语
这一篇是 homelab 系列小结的开始, 最近彻底重构了网络和服务, 还有很多尚未成型, 因此以一些小篇节为主.
homelab 的内网服务最近开始先 docker 迁移, 遇到了一个很大问题: 自托管的 docker 仓库 http 需要在每个 client 配置允许不安全连接….简直要疯…
因此优先项成了: 完成内网 https
正文
需求
方案实际很容易确定:
- 证书: 内网目前没有公开服务, 因此只需要一个泛域名证书, 这样维护成本也比较低
- acme.sh 申请, Let’s Encrypt, cloudflare DNS 验证
- 自动化托管到 gitea aciton(兼容 github action)
- HTTPS 目前已有服务都支持反向代理, 那么 Traefik 就几乎唯一的选择了.
证书
背景参考: 使用 acme.sh 配置自动续签 SSL 证书 - 烧饼博客
首次申请
申请 Cloudflare API: 参考 使用 DNS 验证签发证书 (非常详细)
- 不要申请成 Global API Key 杜绝额外安全性问题.
申请的 key 复制到当前 shell 下
1 2 3
| export CF_Token="复制下来的 Token" export CF_Account_ID="复制下来的 Account ID" export CF_Zone_ID="复制下来的 Zone ID"
|
acme: 文档非常全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
curl https://get.acme.sh | sh -s [email protected]
./acme.sh --set-default-ca --server letsencrypt
./acme.sh --issue --dns dns_cf -d lan.jasper1024.com -d "*.lan.jasper1024.com"
./acme.sh --install-cert -d lan.jasper1024.com -d "*.lan.jasper1024.com" \ --cert-file ~/cert.pem \ --key-file ~/key.pem \ --fullchain-file ~/fullchain.pem
tar cz ca account.conf | base64 -w0
|
Action
保存到 action 的密钥
- base64 -> ACME_SH_ACCOUNT_TAR
- CF_TOKEN
- CF_ACCOUNT_ID
- CF_ZONE_ID
主要配置文件参考自 使用 GitHub Actions 自动申请与部署 SSL 证书 - 宝硕博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| name: Issue SSL Certificates
on: workflow_dispatch: schedule: - cron: "0 0 1 */2 *" push: branches: - master
env: CERTS_OUTPUT_BASE: certs CERTS_OUTPUT_DIRECTORY: lan.jasper1024.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 }}
- name: Install acme.sh shell: bash run: | curl https://get.acme.sh | sh -s [email protected] --force # 检查 acme.sh 是否存在 if [ ! -f ~/.acme.sh/acme.sh ]; then echo "acme.sh installation failed." exit 1 fi
- name: Extract account files for acme.sh shell: bash run: | echo "$ACME_SH_ACCOUNT_TAR" | base64 -d | tar -C ~/.acme.sh -xz cat ~/.acme.sh/account.conf env: ACME_SH_ACCOUNT_TAR: ${{ secrets.ACME_SH_ACCOUNT_TAR }}
- name: Issue SSL certificates shell: bash run: | ~/.acme.sh/acme.sh --issue \ -d "lan.jasper1024.com" -d *.lan.jasper1024.com \ --dns dns_cf --server letsencrypt --log env: CF_Token: ${{ secrets.CF_Token }} CF_Account_ID: ${{ secrets.CF_Account_ID }} CF_Zone_ID: ${{ secrets.CF_Zone_ID }}
- 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 "lan.jasper1024.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 "AcmeBot" git config --global user.email "[email protected]"
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 }}
|
这样任何需要证书的地方 clone 这个仓库;
更好的方式: 交给自动化工具, 每次只传输更新后的证书;
Traefik
Traefik 配合容器简直起飞!!
- 前排警告, 初次上手, 仅配置完反向代理就没再深究, 大概率目前配置并不是最佳实践.
首先创建一个 traefik 的网络 proxy
docker install traefik 的最简单示例
- https://doc.traefik.io/traefik/getting-started/quick-start/
docker compose 下这两行可以让 http://lan.jasper1024.com/
-> http://lan.jasper1024.com/dashboard/
- https://doc.traefik.io/traefik/operations/api/#configuration
1 2 3
| labels: - "traefik.http.routers.api.rule=Host(`lan.jasper1024.com`)" - "traefik.http.routers.api.service=api@internal"
|
下面的配置可以正常 https 访问 https://lan.jasper1024.com/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ports: - "443:443" volumes: - /opt/traefik/config:/etc/traefik - /opt/traefik/acme:/opt/traefik/acme command: - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - "--providers.file.directory=/etc/traefik" - "--providers.file.watch=true" labels: - "traefik.enable=true" - "traefik.http.routers.api.rule=Host(`lan.jasper1024.com`)" - "traefik.http.routers.api.service=api@internal" - "traefik.http.routers.api-secure.rule=Host(`lan.jasper1024.com`)" - "traefik.http.routers.api-secure.entrypoints=websecure" - "traefik.http.routers.api-secure.service=api@internal" - "traefik.http.routers.api-secure.tls=true"
|
至此 不再需要修改 traefik, 只需要在其他容器内声明即可 以 gitea 为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| services: gitea: xxx labels: - "traefik.enable=true" - "traefik.http.routers.gitea.rule=Host(`git.lan.jasper1024.com`)" - "traefik.http.routers.gitea.entrypoints=web" - "traefik.http.services.gitea.loadbalancer.server.port=3333" - "traefik.http.routers.gitea-secure.rule=Host(`git.lan.jasper1024.com`)" - "traefik.http.routers.gitea-secure.entrypoints=websecure" - "traefik.http.routers.gitea-secure.tls=true" networks: - proxy
networks: proxy: external: true
|