背景
Let’s Encrypt提供了3类验证(Challenge)方式,用于颁发证书:
HTTP-01
:通过HTTP访问服务器80端口的.well-known/acme-challenge
验证。DNS-01
:在DNS中添加_acme-challenge
开头的TXT记录,这种方式因为能签发通配符证书(Wildcard)而被大范围使用。TLS-SNI-01
、TLS-ALPN-01
:通过TLS的方式对443端口访问进行验证。
DNS-01
和HTTP-01
能在极少改动服务器配置的情况下,完成验证。然而,对应某些特殊环境下,80端口难以开放或DNS记录难以更改,只能通过443端口验证。
TLS-SNI-01的漏洞
TLS-SNI-01,顾名思义,是使用SNI进行验证。通过配置特定域名的SNI(例如773c7d.13445a.acme.invalid
),生成临时证书进行验证。
然而,对于共享同一个IP的虚拟主机,一旦没有上传证书的SNI验证,攻击者就能轻而易举通过指向同一IP的域名的证书颁发验证。
因此,Let’s Encrypt在2018.1.9收到报告后停止了新证书颁发。2019.2.13,TLS-SNI-01验证将被终止。
替代
TLS-SNI-02和TLS-SNI-01具有同样的问题,TLS-SNI-03还在开发中,TLS-SNI验证短期内难以再次使用。
TLS-ALPN-01给出了一种替代。ALPN(应用层协议协商),是在HTTP/2中被引入、通过HTTPS进行协议协商的机制。TLS-ALPN-01利用了这个机制,将协议设为acme-tls
进行验证。这避免了SNI的问题。2018.7.13,TLS-ALPN-01可用于Let’s Encrypt生产环境验证。
然而,根据讨论可以看出,TLS-ALPN-01支持的客户端非常少。对于提供自动HTTPS加密的Caddy,TLS-ALPN-01支持依旧未能合并。因此,下面我将谈谈的Nginx方案。
原理
Nginx不仅是HTTP/HTTPS服务器,跟提供了全面的TLS、UDP甚至是邮件协议支持。
Nginx的ngx_stream_ssl_preread_module
模块提供了ClientHello
访问。通过$ssl_preread_alpn_protocols
变量,即可实现不同协议的分流。
配置
使用包含ngx_stream_ssl_preread_module
的Nginx官方源安装Nginx。
参考dehydrated的TLS-ALPN-01配置。
- 配置
nginx.conf
1 | stream { |
- 安装dehydrated并配置
1 | wget -O /usr/sbin/dehydrated -c https://raw.githubusercontent.com/lukas2511/dehydrated/master/dehydrated |
在config
修改如下参数1
2CHALLENGETYPE="tls-alpn-01"
HOOK="${BASEDIR}/hook.sh"
修改domains.txt中为所需域名。
- 配置Python验证服务
将页面上的Example responder保存到/etc/dehydrated/tls.py
值得注意的是,FALLBACK
的两个参数中的ssl-snakeoil
证书是由ssl-cert
这一包生成的,可以安装其来生成或自行处理。
接着,将其配置为服务tls-alpn.service
。
1 | [Unit] |
- 配置钩子(hook)实现自动化
修改hook.sh
中的以下函数
1 | deploy_challenge() { |
将/usr/sbin/dehydrated -c
放入cron中定时运行即可实现自动更新证书。
- 进行证书获取
1 | dehydrated --register --accept-terms |
- 配置证书
1 | ssl_certificate /etc/nginx/ssl/fullchain.pem; |
- 配置
PROXY protocol
以实现客户端IP获取
Haproxy提出的PROXY protocol能实现TLS的客户端IP等信息的安全传递。
在http的server中,修改下列配置:
1 | # 启用proxy_protocol监听 |
结语
certbot从v0.26.0开始,进行了不完整支持,同上面类似,需要搭配cme-alpn-proxy等服务端食用。
但是这种使用ssl_preread分流的做法,需要PROXY protocol
才能接近原效果,也只有为数不多的Web服务器支持。希望Caddy能早日集成TLS-ALPN-01支持。