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

说明:以下脚本在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 首次申请证书

  1. 创建web服务目录: mkdir -p /letsencrypt/site。这里以静态网页为例,也可以设为反向代理。
  2. 创建docker-compose文件:nano /letsencrypt/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
  1. 创建nginx配置文件:nano /letsencrypt/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;
}
  1. 启动web服务器:cd /letsencrypt && docker-compose up -d
  2. 运行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 youremail@domain.com --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

  1. 停止web服务器:cd /letsencrypt && docker-compose down
  2. 更新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目录。

  1. 更新nginx配置,启用HTTPS

处理http:

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;
    }
}

处理https:

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

  1. 重新启动web服务器:cd /letsencrypt && docker-compose up -d

4.2 更新证书

  1. 设置更新脚本
    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重新加载配置。

  1. 设置定时任务
    通过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来安装部署,也更为简单方便。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容