运维 Docker 环境下自动更新 Let’ s Encrypt SSL 证书

freefishz · January 29, 2019 · Last by shiny replied at February 01, 2019 · 13280 hits

说明:以下脚本在 Ubuntu 18.04 运行通过,大部分脚本执行需要管理员权限。

1. 准备 docker 环境

# 安装必备工具包
apt-get -y install apt-transport-https ca-certificates curl software-properties-common

# 添加docker阿里云源,相对官方源速度更快
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"

# 安装最新版社区版docker
apt update -y 
apt install -y docker-ce

# 如果想要安装指定版本的docker-ce,如下:
# 查看有哪些版本
apt-cache madison docker-ce
# 安装指定版本
apt install -y docker-ce=[版本]

#  设置阿里云docker源
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://uon07it7.mirror.aliyuncs.com"]
}
EOF

systemctl daemon-reload
systemctl restart docker

# 下载安装docker-compose
curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

2. Let's Encrypt 免费证书签发过程简介

Let's Encrypt 免费证书签发过程包含以下三个阶段:

  1. 在本地服务器上安装CertbotCertbot是签发/更新证书的客户端程序;
  2. 运行Certbot获取SSL/TLS证书,证书有效期为3个月
  3. 设置定时脚本每周运行一次Certbot更新证书。如果证书有效期小于 30 天,Certbot会更新证书;

3. Certbot 工作原理简介

不论是第一次申请证书,还是更新证书,Certbot都会发起一次ACME请求,来验证你是否拥有该域名。如果验证通过,Certbot就会将新证书安装到本地服务,其实就是将证书保存在一个目录中。证书一般包含两个文件(包含公钥、私钥以及证书等信息),web 服务器需要配置使用这两个证书文件,来实现 HTTPS 访问。

ACME验证过程如下:

  1. 假设你有一个域名:example.com,和一个公网 IP:xxx.xxx.xxx.xxx,并设置好了 DNS 解析;
  2. 配置好一台 web 服务器,在 80 端口和 443 端口接受 examle.com 的请求;
  3. CertbotLet's Encrypt发起证书申请;
  4. Let's Encrypt返回Certbot一个唯一的token
  5. Certbot配置 web 服务器,使token可以通过 url:http://example.com/.well-known/acme-challenge/{token}访问;
  6. Let's Encrypt CA访问上述 url,如果获取到的token和它发送给Certbottoken一致,就可以证明你拥有该域名;

注意:Certbot需要配置 web 服务器的相应权限。以 nginx 为例,Certbot需要权限将token写入.well-known/acme-challenge目录。

4. 通过 docker 来运行 Certbot

为了方便维护、升级,推荐使用 docker 来运行Certbot。整个过程可以分为两部分:首次申请证书和更新证书。

4.1 首次申请证书

创建 web 服务目录:

mkdir -p /letsencrypt/site #这里以静态网页为例,也可以设为反向代理。

创建 docker-compose 文件:

nano /letsencrypt/docker-compose.yml

docker-compose.yml 内容如下:

version: '3.1'

services:
  demo-site:
    container_name: 'demo-site'
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./site:/usr/share/nginx/html
    networks:
      - docker-network

networks:
  docker-network:
    driver: bridge

创建 nginx 配置文件:

nano /letsencrypt/nginx.conf

nginx.conf 内容如下:

server {
    listen 80;
    server_name example.com www.example.com;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /usr/share/nginx/html;
    }

    root /usr/share/nginx/html;
    index index.html;
}

启动 web 服务器:

cd /letsencrypt && docker-compose up -d

运行Certbot申请证书:

docker run --rm -it \
-v /letsencrypt/certbot/etc/letsencrypt:/etc/letsencrypt \             # 证书申请工作目录
-v /letsencrypt/certbot/var/log/letsencrypt:/var/log/letsencrypt \     # 日志记录
-v /letsencrypt/site:/data/letsencrypt \                               # ACME验证token目录,与nginx服务器共享
certbot/certbot \
certonly --webroot \                                                   # 指定ACME验证方式:token文件验证
--email [email protected] --agree-tos --no-eff-email \              # 申请者邮件
--webroot-path=/data/letsencrypt \                                     # ACME验证token文件放置目录
-d example.com -d www.example.com                                      # 指定要申请证书的域名列表

如果脚本正常运行,可以在/letsencrypt/certbot/etc/letsencrypt/live下找到example.com文件夹,其中包含申请成功的证书文件:fullchain.pemprivkey.pem

停止 web 服务器:

cd /letsencrypt && docker-compose down

更新 docker-compose 配置:

version: '3.1'

services:
  demo-site:
    container_name: 'demo-site'
    image: nginx:alpine
    ports:
      - "80:80"     # 保留80端口,用于证书更新
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./site:/usr/share/nginx/html
      - ./certbot/etc/letsencrypt/live:/letsencrypt/live        # 当前证书目录
      - ./certbot/etc/letsencrypt/archive:/letsencrypt/archive  # 历史证书目录
      - ./dhparam-2048.pem:/letsencrypt/dhparam-2048.pem        # 使用2048位DH(Diffie-Hellman)参数
    networks:
      - docker-network

networks:
  docker-network:
    driver: bridge

生成 2048 位的 DH 参数文件命令如下:

openssl dhparam -out /letsencrypt/dhparam-2048.pem 2048

live 目录的证书会 soft link 到 archive 目录,而 docker 对 soft link 支持不好,因此需要同时映射 live 和 archive 目录。

更新 nginx 配置,启用 HTTPS:

server {
    listen      80;
    server_name example.com www.example.com;

    # 重定向到https
    location / {
        rewrite ^ https://$host$request_uri? permanent;
    }

    # 高优先级,仅用于更新证书
    location ~ /.well-known/acme-challenge {
        allow all;
        root /data/letsencrypt;
    }
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    server_tokens off;

    ssl on;

    ssl_certificate /letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /letsencrypt/live/example.com/privkey.pem;

    ssl_buffer_size 8k;

    ssl_dhparam /letsencrypt/dhparam-2048.pem; # 使用2048位DH参数,加强安全

    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    ssl_ecdh_curve secp384r1;
    ssl_session_tickets off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8;

    root /usr/share/nginx/html;
    index index.html;
}

DH 以及 OCSP 内容请参考:Strong SSL Security On nginx

重新启动 web 服务器:

cd /letsencrypt && docker-compose up -d

4.2 更新证书

设置更新脚本:

touch /letsencrypt/renew.sh 
chmod +x /letsencrypt/renew.sh
nano /letsencrypt/renew.sh

renew.sh 脚本内容如下:

#!/bin/bash

docker run -it --rm \
-v /letsencrypt/certbot/etc/letsencrypt:/etc/letsencrypt \
-v /letsencrypt/certbot/var/lib/letsencrypt:/var/lib/letsencrypt \
-v /letsencrypt/certbot/var/log/letsencrypt:/var/log/letsencrypt \
-v /letsencrypt/site:/data/letsencrypt \
certbot/certbot \
renew --webroot -w /data/letsencrypt --quiet && docker kill --signal=HUP demo-site

最后一行脚本说明:在更新完证书后,通知 nginx 重新加载配置。

通过 crontab 设置定时任务:

crontab -e

添加一行,每周执行一次更新脚本:

0 1 * * 0 /letsencrypt/renew.sh

5. 安全加强

可以通过ssllabs.com验证证书,如果按照上述配置,应该可以获得A+评价。 当然,还可以进一步通过securityheaders.io校验网站安全性,对于 nginx 可以添加以下配置:

server {
    # ....

    location / {
        #security headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        #CSP
        add_header Content-Security-Policy "frame-src 'self'; default-src 'self'; script-src 'self' 'unsafe-inline' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com; img-src 'self'; style-src 'self' https://maxcdn.bootstrapcdn.com; font-src 'self' data: https://maxcdn.bootstrapcdn.com; form-action 'self'; upgrade-insecure-requests;" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    }

    # ....
}

6. 小结

本文参考How to Set Up Free SSL Certificates from Let's Encrypt using Docker and Nginx,对 Docker 环境下如何使用 Let's Encrypt 自动获取/更新 SSL 证书做了一个简明攻略。

如果你正在使用 K8S,ingress nginxtraefik都对let‘s encrypt提供了很好的支持,配合helm来安装部署,也更为简单方便。

最最爽是还可以泛域。而且支持阿里云 接口访问,一句就够了

docker run --rm  -it  \
  -v "$(pwd)/out":/acme.sh  \
  -e Ali_Key="xxxx" \
  -e Ali_Secret="xxxx" \
  neilpang/acme.sh --issue --log --dns dns_ali -d *.youdomain.com
Reply to alvin2ye

上次看的时候还不支持阿里云接口,更新真快。牛 x

我部署的方式: acme.sh 更新证书后,通过 docker-compose.yml 映射证书到容器里,然后 nginx -s reload。 遇到偶发性的问题:文件已经更新了,但容器里的文件还未更新,需要重启 nginx 容器。

所以做了证书的过期监控。

You need to Sign in before reply, if you don't have an account, please Sign up first.