├── Dockerfile ├── LICENSE ├── README.md ├── alidns.sh └── entrypoint.sh /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | # Install dependencies 4 | RUN apk --no-cache add wget tar sudo certbot bash python3 py3-pip && \ 5 | apk --no-cache add --virtual build-dependencies gcc musl-dev python3-dev libffi-dev openssl-dev make 6 | 7 | # Install aliyun-cli 8 | RUN wget https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz && \ 9 | tar xzvf aliyun-cli-linux-latest-amd64.tgz && \ 10 | mv aliyun /usr/local/bin && \ 11 | rm aliyun-cli-linux-latest-amd64.tgz 12 | 13 | # Copy and install certbot-dns-aliyun plugin 14 | RUN wget https://cdn.jsdelivr.net/gh/justjavac/certbot-dns-aliyun@main/alidns.sh && \ 15 | mv alidns.sh /usr/local/bin/alidns && \ 16 | chmod +x /usr/local/bin/alidns 17 | 18 | # Create virtual environment for Python packages 19 | RUN python3 -m venv /opt/venv 20 | ENV PATH="/opt/venv/bin:$PATH" 21 | 22 | # Install Python dependencies in virtual environment 23 | RUN pip install --upgrade pip && \ 24 | pip install aliyun-python-sdk-core aliyun-python-sdk-alidns 25 | 26 | # Copy entrypoint script 27 | COPY entrypoint.sh /usr/local/bin/entrypoint.sh 28 | RUN chmod +x /usr/local/bin/entrypoint.sh 29 | 30 | # Set environment variables (to be provided during runtime) 31 | ENV REGION="" 32 | ENV ACCESS_KEY_ID="" 33 | ENV ACCESS_KEY_SECRET="" 34 | ENV DOMAIN="" 35 | ENV EMAIL="" 36 | ENV CRON_SCHEDULE="0 0 * * *" 37 | 38 | # Setup cron job for certbot renew 39 | RUN echo "$CRON_SCHEDULE /usr/bin/certbot renew --manual --preferred-challenges dns --manual-auth-hook '/usr/local/bin/alidns' --manual-cleanup-hook '/usr/local/bin/alidns clean' --agree-tos --email $EMAIL --deploy-hook 'cp -r /etc/letsencrypt/live/$DOMAIN/* /etc/letsencrypt/certs'" > /etc/crontabs/root 40 | 41 | # Create directory for certificates 42 | RUN mkdir -p /etc/letsencrypt/certs 43 | 44 | # Make sure cron is running 45 | RUN touch /var/log/cron.log 46 | 47 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 迷渡 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # certbot-dns-aliyun 2 | 3 | > 解决阿里云 DNS 不能自动为通配符证书续期的问题 4 | 5 | ## 原理 6 | 7 | 当我们使用 certbot 申请**通配符**证书时,需要手动添加 TXT 记录。每个 certbot 8 | 申请的证书有效期为 3 个月,虽然 certbot 9 | 提供了自动续期命令,但是当我们把自动续期命令配置为定时任务时,我们无法手动添加新的 10 | TXT 记录用于 certbot 验证。 11 | 12 | 好在 certbot 提供了一个 hook,可以编写一个 Shell 脚本。在续期的时候让脚本调用 13 | DNS 服务商的 API 接口动态添加 TXT 记录,验证完成后再删除此记录。 14 | 15 | ## 安装(CommandLine) 16 | 17 | 1. 安装 aliyun cli 工具 18 | 19 | ```shell 20 | wget https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz 21 | tar xzvf aliyun-cli-linux-latest-amd64.tgz 22 | sudo cp aliyun /usr/local/bin 23 | rm aliyun 24 | ``` 25 | 26 | 安装完成后需要配置[凭证信息](https://help.aliyun.com/document_detail/110341.html) 27 | 28 | 2. 安装 certbot-dns-aliyun 插件 29 | 30 | ```shell 31 | wget https://cdn.jsdelivr.net/gh/justjavac/certbot-dns-aliyun@main/alidns.sh 32 | sudo cp alidns.sh /usr/local/bin 33 | sudo chmod +x /usr/local/bin/alidns.sh 34 | sudo ln -s /usr/local/bin/alidns.sh /usr/local/bin/alidns 35 | rm alidns.sh 36 | ``` 37 | 38 | 3. 申请证书 39 | 40 | 测试是否能正确申请: 41 | 42 | ```sh 43 | certbot certonly -d *.example.com --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --dry-run 44 | ``` 45 | 46 | 正式申请时去掉 `--dry-run` 参数: 47 | 48 | ```sh 49 | certbot certonly -d *.example.com --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" 50 | ``` 51 | 52 | 4. 证书续期 53 | 54 | ```sh 55 | certbot renew --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --dry-run 56 | ``` 57 | 58 | 如果以上命令没有错误,把 `--dry-run` 参数去掉。 59 | 60 | 5. 自动续期 61 | 62 | 添加定时任务 crontab。 63 | 64 | ```sh 65 | crontab -e 66 | ``` 67 | 68 | 输入 69 | 70 | ```txt 71 | 1 1 */1 * * root certbot renew --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --deploy-hook "nginx -s reload" 72 | ``` 73 | 74 | 上面脚本中的 `--deploy-hook "nginx -s reload"` 表示在续期成功后自动重启 75 | nginx。 76 | 77 | ## 安装(Dockerfile) 78 | 79 | 下载 Dockerfile 以及 entrypoint.sh, 确保他们在同一文件夹下。目前 Dockerfile 80 | 中默认下载 amd64 版本,其他架构请修改对应的 Aliyun CLI URL。 81 | 82 | 1. 创建 Image 83 | 84 | 进入 Dockerfile 同目录: 85 | ```sh 86 | docker build -t certbot-aliyun . 87 | ``` 88 | 89 | 使用代理(可选): 90 | ```sh 91 | docker build . \ 92 | --build-arg "HTTP_PROXY=http://127.0.0.1:7890" \ 93 | --build-arg "HTTPS_PROXY=http://127.0.0.1:7890" \ 94 | -t certbot-aliyun 95 | ``` 96 | 2. 运行容器 97 | ```sh 98 | docker run \ 99 | -e REGION=YOUR_REGEION \ 100 | -e ACCESS_KEY_ID=YOUR_ACCESS_KEY \ 101 | -e ACCESS_KEY_SECRET=YOUR_ACCESS_SECRET \ 102 | -e DOMAIN=YOUR_DOMAIN \ 103 | -e EMAIL=YOUR_NOTIFICATION_EMAIL \ // 证书刷新通知邮箱 104 | -e CRON_SCHEDULE="0 0 * * *" \ // 自定义证书刷新间隔 105 | -v /path/letsencrypt:/etc/letsencrypt \ // 将容器内的证书路径完整映射到宿主机 106 | -d certbot-aliyun 107 | ``` 108 | -------------------------------------------------------------------------------- /alidns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FLAG="(\.com\.cn|\.gov\.cn|\.net\.cn|\.org\.cn|\.ac\.cn|\.gd\.cn)$" 3 | 4 | 5 | if ! command -v aliyun >/dev/null; then 6 | echo "错误: 你需要先安装 aliyun 命令行工具 https://help.aliyun.com/document_detail/121541.html。" 1>&2 7 | exit 1 8 | fi 9 | 10 | DOMAIN=$(expr match "$CERTBOT_DOMAIN" '.*\.\(.*\..*\)') 11 | SUB_DOMAIN=$(expr match "$CERTBOT_DOMAIN" '\(.*\)\..*\..*') 12 | 13 | if echo $CERTBOT_DOMAIN |grep -E -q "$FLAG"; then 14 | 15 | DOMAIN=`echo $CERTBOT_DOMAIN |grep -oP '(?<=)[^.]+('$FLAG')'` 16 | SUB_DOMAIN=`echo $CERTBOT_DOMAIN |grep -oP '.*(?=\.[^.]+('$FLAG'))'` 17 | 18 | fi 19 | 20 | if [ -z $DOMAIN ]; then 21 | DOMAIN=$CERTBOT_DOMAIN 22 | fi 23 | if [ ! -z $SUB_DOMAIN ]; then 24 | SUB_DOMAIN=.$SUB_DOMAIN 25 | fi 26 | 27 | if [ $# -eq 0 ]; then 28 | aliyun alidns AddDomainRecord \ 29 | --DomainName $DOMAIN \ 30 | --RR "_acme-challenge"$SUB_DOMAIN \ 31 | --Type "TXT" \ 32 | --Value $CERTBOT_VALIDATION 33 | /bin/sleep 20 34 | else 35 | RecordId=$(aliyun alidns DescribeDomainRecords \ 36 | --DomainName $DOMAIN \ 37 | --RRKeyWord "_acme-challenge"$SUB_DOMAIN \ 38 | --Type "TXT" \ 39 | --ValueKeyWord $CERTBOT_VALIDATION \ 40 | | grep "RecordId" \ 41 | | grep -Eo "[0-9]+") 42 | 43 | aliyun alidns DeleteDomainRecord \ 44 | --RecordId $RecordId 45 | fi 46 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Activate the virtual environment 4 | source /opt/venv/bin/activate 5 | 6 | # Configure Aliyun CLI 7 | aliyun configure set --profile akProfile --mode AK --region $REGION --access-key-id $ACCESS_KEY_ID --access-key-secret $ACCESS_KEY_SECRET 8 | 9 | # Obtain the certificate 10 | certbot certonly -d "$DOMAIN" --manual --preferred-challenges dns --manual-auth-hook "/usr/local/bin/alidns" --manual-cleanup-hook "/usr/local/bin/alidns clean" --agree-tos --email $EMAIL --non-interactive 11 | 12 | # Start cron daemon 13 | crond -f -l 2 14 | 15 | --------------------------------------------------------------------------------