在Docker容器环境中用Let's Encrypt部署HTTPS

昨天尝试了用Let's Encrypt给自己的博客部署了HTTPS,感觉这个服务真的是非常方便。网上有很多关于Let's Encrypt的文章,不过本站因为部署在docker容器中,关于这种架构的文章不多,我把自己的思路写下来吧。

先来说点基础的话题。

HTTPS可以提供全面的密码学保护,换句话说,访问HTTPS网站时,用户和网站之间传输的数据不会被第三方窃取和监听。这里的第三方包括黑客、运营商、监管部门,有你想得到的也有你想不到的。

HTTPS是基于公钥密码的,简单说就是服务器提供一个公钥,用户用这个公钥加密数据后发给服务器,然后服务器用自己的私钥解密。这里面有一个问题,即如何确定服务器的公钥是真的,如果第三方拦截并伪造服务器的公钥,用户用第三方的假公钥加密数据,那么这些数据就会被第三方解密并窃取。

为了避免这样的问题,就出现了证书。证书本身是个数字签名,就像给服务器的公钥盖个公章,用户看到公章就知道公钥是真的了。但是,这个问题没有根本解决,怎么知道公章本身是不是真的呢?因为公章的本质是数字签名,于是浏览器里面内置了一份“可信公章清单”,凡是在这份清单里的签名都认为是可信的。

因此,原则上说所有人都可以自己颁发证书,但自己颁发的证书不被浏览器信任(不在可信公章清单里),浏览器就会报错(比如12306网站遇到的错误)。这时有三种解决方法,第一是让用户强制认为自己的证书是可信的(12306的做法),第二是请在可信公章清单里的机构给自己的网站发证书,第三是让浏览器把自己列到可信清单里去(即成为可信的CA)。

第一种做法简单粗暴,风险也很大,因为用户安装的证书如果是伪造的,那在访问伪造的钓鱼网站时,就会误认为访问了可信的网站。第三种做法很难,因为成为可信的CA需要严格的条件。大部分网站用的第二种做法,即请可信CA来颁发证书。

但是CA作为盈利机构不会免费给你提供这种服务,因此请CA颁发证书是要收费的,这个费用现在越来越便宜,但还是不太便宜。此外,签发和更新证书都要通过人工完成,这对于越来越要求自动化运维的互联网行业成了一个绊脚石。

于是2015年诞生了一个叫做Let's Encrypt的项目,这个项目的目标是实现全互联网加密。先不管这个目标是否现实,从技术层面上,Let's Encrypt推出了两个革新:第一是免费签发可信的证书,第二是实现证书签发和更新的完全自动化

传统的CA在签发证书的时候必须验证申请人的身份,每个证书只能绑定特定的域名使用,换句话说,你得证明你拥有这个域名的控制权。Let's Encrypt可以采用多种方式自动完成验证,有多种客户端程序支持Let's Encrypt的方案,还提供了各种主流服务器的插件。不过本站的环境不是典型的环境,关于各种典型环境下的部署方法请参见certbot(官方推荐的客户端)的官方教程。

先介绍一下我的环境配置。服务器是Digital Ocean的一台VPS,系统是CoreOS,所以这是一台只能跑容器的服务器。服务器上运行了3个网站,每个网站都是一个单独的容器,这些容器是基于PHP官方镜像(with Apache)定制的,另外有一个反向代理容器(基于nginx官方镜像)负责根据域名将访问分配到每个网站。

由于CoreOS只能运行容器,因此无法在CoreOS中直接安装certbot,还好certbot有个官方的docker镜像,我们可以直接pull:

docker pull quay.io/letsencrypt/letsencrypt:latest

运行这个镜像就可以申请签发证书,在运行之前首先确保你要申请证书的域名能直接解析到你当前这台服务器上。

docker run -it --rm -p 80:80 -p 443:443 \
    -v /etc/letsencrypt:/etc/letsencrypt \
    quay.io/letsencrypt/letsencrypt auth

解释一下这里都做了什么事。首先-it选项表示开启交互输入,因为在申请证书的过程中需要用户输入一些信息;--rm选项表示容器运行结束后自动删除,因为这是一个一次性容器;两个-p选项表示映射主机的两个端口,因为certbot需要通过这两个端口来做验证,这里需要注意的是,我的nginx容器已经占用了这两个端口,因此在申请证书之前,需要先停止nginx容器;-v选项表示映射磁盘数据卷,因为certbot会将所有信息保存在/etc/letsencrypt目录中,我们需要让这个目录的内容持久化并可以从主机以及其他容器(主要是nginx容器)访问它。

第一次申请证书需要注册账号,过程很简单,先同意里面的协议,然后输入一个邮件地址作为ID就可以了(不需要验证这个邮箱,只是一个ID),以后运行时账号会保存在本地,就不需要再输入邮件地址了。接下来需要选择验证方式,这里选择Temporary web server方式,也就是说让certbot自己启动一个临时Web服务器(因此需要开放80和443端口)完成验证。最后输入你要签发证书的域名,程序自动完成认证之后,证书就签发好了。

如果你不喜欢这样一步一步的方式(比如我就不喜欢),可以在命令行里提供所有的参数,这样就一步搞定了:

docker run --rm -p 80:80 -p 443:443 \
    -v /etc/letsencrypt:/etc/letsencrypt \
    quay.io/letsencrypt/letsencrypt auth \
    --standalone -m someone@email.com --agree-tos \
    -d your.domain1.com -d your.domain2.com

证书签发之后,会存放到/etc/letsencrypt目录中,刚才我们映射了数据卷,因此可以直接从宿主机中看到这个目录中的内容,其中证书位于/etc/letsencrypt/live/your.domain1.com中。接下来需要让nginx容器也能够读取这些证书,方法放简单,把这个目录映射给nginx容器就可以了:

docker run --name nginx -p 80:80 -p 443:443 \
    -v /etc/nginx/conf.d:/etc/nginx/conf.d \
    -v /etc/letsencrypt:/etc/letsencrypt \
    nginx

第一个-v是存放nginx配置文件的目录,第二个-v就是存放证书的目录,接下来我们在网站的配置文件里把证书配上去:

server {
    listen 443 ssl;
    server_name your.domain1.com;
 
    ssl_certificate /etc/letsencrypt/your.domain1.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/your.domain1.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
 
    location / {
        proxy_pass http://172.17.0.1:8001;
    }
}
 
server {
    listen 80;
    server_name your.domain1.com;
    return 301 https://$host$request_uri;
}

配置文件主要就是ssl_certificatessl_certificate_key两行,第一行是提供给客户端的公钥(证书),第二行是服务器用来解密客户端消息的私钥(私钥不会,也不应该在网络上传输)。后面第二个server块是将直接用HTTP的访问重定向到HTTPS连接上。

修改好配置文件之后重启nginx容器,顺利的话网站就可以通过HTTPS访问了,可以通过浏览器看一下证书信息,颁发者是Let's Encrypt Authority X3,它的根CA是DST,即IdenTrust,这是一个为银行和金融提供证书的可信CA,通过和IdenTrust交叉验证,Let's Encrypt的证书可以在各种浏览器上确保可信。不过Let's Encrypt签发的证书是短效证书,有效期只有3个月,但没关系,我们可以通过一个简单的命令对证书进行更新,同样是通过docker容器来运行:

docker run --rm -p 80:80 -p 443:443 \
    -v /etc/letsencrypt:/etc/letsencrypt \
    quay.io/letsencrypt/letsencrypt renew \
    --standalone

运行这个命令时,certbot会自动检查确认证书有效期,如果过期时间在一个月之内,就会自动更新。在CoreOS中,由于没有Cron,我们需要通过systemd的timer来做定时调度,比如每个月运行一次这个renew任务就可以了,不过记得运行之前先停止nginx容器,运行之后再启动nginx容器。

除了standalone方式验证之外,还可以使用wwwroot方式来做验证,但在我的环境中,nginx容器只是反向代理,本身没有wwwroot,因此standalone方式比较简单,当然缺点是每次签发和更新证书都要先停止nginx容器,这会造成网站服务中断。如果需要保证服务不中断,可以为nginx容器单独配一个验证用的wwwroot。

好了,祝大家加密愉快,祝Let's Encrypt不要那么快被墙。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容