使用 Lego 申请 Let's Encrypt SSL 证书
TLS/SSL
HTTPS 是基于 SSL/TLS 的 HTTP 协议,在目前的场景下,应该尽可能地提供 HTTPS 访问,它可以提供:
- 流量加密: 避免第三方截取通信内容
- 身份验证: 确保用户访问你的域名时,一定是你的站点提供服务,而不是劫持站
(尽管 SSL/TLS 连接的建立可能会略微增加消耗的资源)
系统内置了一些可信的根证书公钥,根证书机构通过私钥为一些大型机构签发他们证书,这些机构通过某种方式验证你的身份后,就可以为你签发属于你得证书。这样,任何人要确保你是你,只需要用公钥解密你用自己私钥签发的内容。
证书申请
一般来说,如果服务数量不多,各大站点都提供了免费一年的证书,用户可以很方便地在云服务控制台进行证书申请、部署
但是,这种一年的证书往往只支持一个域名,如 a.example.com
,如果同时包含多个服务 a.example.com
、b.example.com
、c.example.com
时,就需要分别进行申请。
要解决这种问题,可以使用泛域名证书 *.example.com
。一般来说,免费的泛域名证书使用 Let's Encrypt 申请
Lego
Let's Encrypt 使用 acme 协议进行控制。显然,用户并不想知道具体的细节,因此有很多工具对该协议进行封装,对外只暴露必须的接口。Lego 就是一个使用 Go 开发的 Let's Encrypt 客户端 —— Le(t's Encrypt) Go
既然懒惰的用户不想知道细节,那么他也不想考虑怎么编译、安装。所以懒惰的用户决定用 Docker
按照前面提到的,证书机构需要对用户身份进行验证,在域名这里,需要验证的就是域名所有权。你需要按照要求,在域名解析内添加 TXT 记录,或是在网站根目录放置指定文件。
这里使用 DNS 记录作为验证方式,因为各大厂商都有 DNS 解析的接口以供使用。
理一下思路,需要做的任务如下:
- 使用 Lego 调用云厂商接口更新 DNS 解析,验证域名所有权
- 向 Let's Encrypt 申请证书
- 把证书部署到各个服务
下面是一个最基本的证书申请命令
首先是使用 goacme/lego
Docker 镜像,挂载对应的目录,并将云厂商的 AK&SK 以环境变量形式传入。后面则是设定需要申请的域名及验证方式(由于 example.com
并不匹配 *.example.com
,因此还需要单独申请一级域名证书)
如果是第一次申请,需要使用 run
创建用户(使用邮箱),后面直接使用 renew
即可
DNSPOD_API_KEY="123456,12341234123142341234" ALICLOUD_ACCESS_KEY="LT123412341234" ALICLOUD_SECRET_KEY="12341234123414" docker run \ --rm \ -e DNSPOD_API_KEY=\"${DNSPOD_API_KEY}\" \ -e ALICLOUD_ACCESS_KEY=\"${ALICLOUD_ACCESS_KEY}\" \ -e ALICLOUD_SECRET_KEY=\"${ALICLOUD_SECRET_KEY}\" \ -u ${UID}:${UID} \ -v $(pwd):/.lego \ goacme/lego \ --email="oyohyee@oyohyee.com" \ --domains="*.oyohyee.com" \ --domains="oyohyee.com" \ --accept-tos \ --dns="dnspod" \ --accept-tos \ --key-type rsa2048 \ run
更新脚本
目前的两个域名,分别在阿里云和腾讯云,因此需要分开两次验证,下面是一个封装好的脚本,如果需要部署到服务器,可以直接在 deploy 里添加命令(如 scp
替换掉服务器上的证书)
不过貌似各个厂商目前还不支持直接通过接口更新证书,所以还需要手动上传证书并部署到各个服务中
(看上去可能并没有比用服务商提供的一年免费证书方便,但是考虑到很多服务都是通过 Nginx 直接部署的,且服务商上也需要手动执行各个服务的部署操作,因此在 Nginx 后挂载多个服务时,还是有优势的)
#!/bin/sh DNSPOD_API_KEY="**********" ALICLOUD_ACCESS_KEY="**********" ALICLOUD_SECRET_KEY="**********" debug () { if [[ -n $DEBUG ]]; then echo $@; fi } ssl () { # concat args from 2 to end cmd=$(echo "docker run \ --rm \ -e DNSPOD_API_KEY=\"${DNSPOD_API_KEY}\" \ -e ALICLOUD_ACCESS_KEY=\"${ALICLOUD_ACCESS_KEY}\" \ -e ALICLOUD_SECRET_KEY=\"${ALICLOUD_SECRET_KEY}\" \ -u ${UID}:${UID} \ -v $(pwd):/.lego \ goacme/lego \ ${@:2} \ --accept-tos \ --key-type rsa2048 \ ${1}" | tr -s " ") debug $cmd `$cmd` } help() { echo "$0 - tool to generate ssl certification" echo " use \"$0 run\" or \"$0 renew\"" echo "" echo " run lego run" echo " renew lego renew" echo "" echo " -d --debug debug mode" exit 0 } main() { # oyohyee.com ssl $1 \ --email="oyohyee@oyohyee.com" \ --domains="*.oyohyee.com" \ --domains="oyohyee.com" \ --dns="dnspod" # ohyee.cc ssl $1 \ --email="me@ohyee.cc" \ --domains="*.ohyee.cc" \ --domains="ohyee.cc" \ --dns="alidns" } deploy() { echo "" echo "Update ssl in aliyun:" echo " https://yundun.console.aliyun.com/?&p=cas#/certExtend/upload" echo "" echo "Update ssl in tencent cloud:" echo " https://console.cloud.tencent.com/ssl" echo "" echo "Update ssl in qiniu cloud:" echo " https://portal.qiniu.com/certificate/ssl#cert" echo "" echo "Update ssl in server:" echo " scp ./certificates/_.* ubuntu@vps.oyohyee.com:/etc/nginx/ssl" echo "" } # parse flags command="" while [[ $# -gt 0 ]]; do key="$1" shift case $key in -d|--debug) DEBUG=1 ;; run) command="run" ;; renew) command="renew" ;; deploy) command="deploy" ;; *) help ;; esac done if [[ -z $command ]]; then help; fi debug "DEBUG mode" debug "command: $command" if [[ $command -ne "deploy" ]]; then deploy else main $command deploy fi
Nginx
如果有多个服务,可以考虑使用 Nginx 的 include
来统一引入根证书,每次更新证书后,只需要覆盖证书文件并重启 Nginx 即可