menu Chancel's blog
rss_feed
Chancel's blog
有善始者实繁,能克终者盖寡。

使用Let'sencrypt为所有子域名永久开通HTTPS

作者:Chancel Yang, 创建:2021-12-29, 字数:7878, 已阅:685, 最后更新:2024-03-25

1. Let's Encrypt简介

Let's Encrypt是一个由互联网安全研究集团(ISRG)运营的非营利证书颁发机构,免费提供X.509传输层安全(TLS)加密证书。

它是世界上最大的认证机构,其赞助商包括电子前沿基金会(EFF)、Mozilla基金会、OVH、思科系统、Facebook、谷歌Chrome等

来自Wikipedia的介绍

Let's Encrypt is a non-profit certificate authority run by Internet Security Research Group (ISRG) that provides X.509 certificates for Transport Layer Security (TLS) encryption at no charge. It is the world's largest certificate authority,[2] used by more than 265 million websites,[3] with the goal of all websites being secure and using HTTPS.

所以在稳定性或安全上还是有保障的

Let's Encrypt官方提供certbot程序使得签发证书自动化,无需再手动签发证书,定时执行便可无限期签发泛域名证书

2. HTTPS

2.1. HTTPS简介

2021年,如果网站还不添加为HTTPS,大部分浏览器都会非常显眼的提示“该网站不安全”了

HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)是一种在HTTP基础上加入SSL证书校验的机制,被广泛运用于交易支付等场景

简单来说,通过HTTPS便可以确认服务器的身份,在访问 www.baidu.com 的时候,HTTPS能确保没有中间人劫持,浏览器所显示的便是百度的服务器所返回的信息

注:HTTPS的中间人劫持浏览器会提示该网站HTTPS证书不可信,这种情况说明了此时服务器的证书有问题,浏览器无法确认所访问的服务器身份

HTTPS的存在能大幅提高中间人劫持的成本,降低网站被伪造的风险

所以除非有特别的需求,否则任何有用户交互的网站都应该尽量采取HTTPS

2.2. 签发HTTP证书渠道

商业付费方案较多,一般有以下几种

  • DV SSL:域名型SSL证书,一般只能确保网站具备该域名的管理权,是廉价的SSL证书(常用于个人网站、中小型企业网站)
  • OV SSL:企业型SSL证书,证书颁发机构会确保企业身份是一个合法存在的实体,其SSL证书中包含完整的公司信息,但浏览器端不会直接显示企业名称,同时价格较贵
  • EV SSL:增强型SSL证书,与OV证书类似,但会确保网站与企业身份一致,国内浏览器可以直接在地址栏处看到公司名称,价格昂贵

商业方案一般是公司用途,作为个人用途的证书,通常考虑价格低廉或免费证书,其中阿里云与腾讯云便有免费的DV证书提供

但阿里云与腾讯云的DV SSL免费证书也有不足之处

  • 一年有效期
  • 多个子域名需单独申请(一个子域名一张证书)
  • 手动部署(如果机器也在他们家则可以实现自动部署)

其中比较麻烦的是前两点,一旦超过有效期网站很容易就变得“不安全”,而众多的子域名每个单独定时去申请也很辛苦

Let's Encrypt就比较好的解决这两个痛点

3. Let's Encrypt证书签发

Let's Encrypt官方推荐certbot程序,利用这个程序我们可以做到自动签发子域名证书

由于certbot程序操作需要一定的命令行知识,降低操作难度作为替代的也可以考虑以下几个方法

  • acme.sh:用于申请Let's Encrypt证书的开源项目,操作相对简单,也支持自动更新与自动部署
  • ohttps: 可以理解为界面化的acme

下面以certbot程序为例子来申请Let's Encrypt的SSL证书

3.1. 安装certbot

首先访问 https://certbot.eff.org/instructions ,选择操作系统和HTTP服务程序,这里我选择Debian和Nginx,然后就可以看到详细的安装过程了

  1. 安装snap程序
Bash
sudo apt install snapd
  1. 使用snap安装certbot
Bash
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

3.2. certbot证书签发

证书签发分为两种,反而别是

  • 签发所有子域名(需DNS解析做校验)
  • 读取Nginx/Apache配置文件并签发证书(文件校验)

Tip:读取配置文件签发域名证书的原理是修改Nginx/Apache的配置文件,在根目录中一个验证文件,在验证成功后恢复原先配置并删除验证文件

这里以申请 chancel.me 域名作为例子

  • 签发chancel.me以下的所有子域名(3.2.1)
  • 签发chancel.me目前所有用到的子域名(3.2.2)

3.2.1. 泛域名证书签发

为整个域名签发所有子域名的SSL证书是较为常见的操作,申请通配符证书需要添加DNS的TXT字段,执行以下命令

Bash
sudo certbot certonly  -d "*.chancel.me","chancel.me" --manual --preferred-challenges dns

注意此处需要填入chancel.me,因为*.chancel.me是不包含一级域名的,所以显示填入chancel.me,这样申请下来也是一个文件

输出如下

Bash
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for *.chancel.me

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.chancel.me.

with the following value:

asdf3rfcsadfluJSDDFff3cczKNc2CplQasd3fFDF3fdsd

Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.chancel.me.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

此时需要去域名服务商的后台管理中添加一台TXT的解析,名称是_acme-challenge,值则是上面程序输出的 “asdf3rfcsadfluJSDDFff3cczKNc2CplQasd3fFDF3fdsd”

添加完成后稍待1分钟,使用dig查询域名解析信息是否已经更新

Bash
dig -t txt _acme-challenge.chancel.me @8.8.8.8

查询输出如下

TEXT
; <<>> DiG 9.11.3-1ubuntu1.8-Ubuntu <<>> -t txt _acme-challenge.chancel.me @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37425
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_acme-challenge.chancel.me.    IN      TXT

;; ANSWER SECTION:
_acme-challenge.chancel.me. 3600 IN     TXT     "asdf3rfcsadfluJSDDFff3cczKNc2CplQasd3fFDF3fdsd"

;; Query time: 29 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Aug 04 16:43:13 CST 2022
;; MSG SIZE  rcvd: 111

解析刷新后,则在certbot中点击回车,证书签发成功,输出如下

Bash
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/chancel.me/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/chancel.me/privkey.pem
This certificate expires on 2022-03-29.
These files will be updated when the certificate renews.

查看签发的证书结构,结构如下

Bash
/etc/letsencrypt/archive/chancel.me
├── cert1.pem
├── chain1.pem
├── fullchain1.pem
└── privkey1.pem

检查签发的证书信息

Bash
sudo openssl x509 -in  /etc/letsencrypt/archive/chancel.me/cert1.pem -noout

输出如下

TEXT
Certificate:
    Data:
        Version: 3 (0x2)
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
                ...
            X509v3 Subject Alternative Name: 
                DNS:*.chancel.me

此时,所有的chancel.me子域名皆可使用 /etc/letsencrypt/archive/chancel.me 中的证书文件作为SSL证书文件

3.2.2. 单独签发域名证书

以nginx为例子,使用certbot从Nginx配置中检出域名列表

Bash
sudo certbot certonly --nginx

此时输入数字可以选择签发对应证书,或者空白直接回车签发所有识别到的证书

Bash
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: chancel.me
2: api.chancel.me
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 2
Requesting a certificate for chancel.me

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/api.chancel.me/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/api.chancel.me/privkey.pem
This certificate expires on 2022-03-30.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

签发成功!查看签发的证书列表

Bash
/etc/letsencrypt/archive/api.chancel.me
├── cert1.pem
├── chain1.pem
├── fullchain1.pem
└── privkey1.pem

0 directories, 4 files

检查签发的证书信息

Bash
Certificate:
    Data:
        Version: 3 (0x2)
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
                ...
            X509v3 Subject Alternative Name: 
                DNS:chancel.me

3.3. Let's Encrypt证书到期自动更新

所签发的证书90天就过期了,自动续签可放在crontab中,半个月自动续签一次(如证书未过期是不会签发新的证书)

TEXT
0 3 15 */1 * /usr/bin/certbot renew --dry-run

3.4. Certbot删除证书

certbot本身提供证书删除功能

注:不要直接删除证书文件,这样做会导致certbot配置与证书文件不一致的错误

显示当前已签证书

Bash
sudo certbot certificates

删除指定证书

Bash
sudo certbot -d api.chancel.me delete

4. Nginx配置SSL证书

配置Nginx启用Https,主要就是ssl_certificate字段的配置,所有二级域名都是一致的

TEXT
server {
    listen 443 ssl;
    server_name www.chancel.me; 
    ssl_certificate /etc/letsencrypt/archive/chancel.me/fullchain1.pem;
    ssl_certificate_key /etc/letsencrypt/archive/chancel.me/privkey1.pem;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;

    location / {
        ...
    }
}

[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]