0x0 前言

一年前,总想写点什么作为生活记录。在比对众多类型博客之后,简洁高效的 hexo 脱颖而出,它适合你体会写博客的每一个阶段。本博客采用 Nginx + Hexo + Let‘s Encrypt + Cloudflare 组件,架设在 vps 上,采用了 Let‘s Encrypt 的免费证书和 Cloudflare 的 CDN 服务。如果你是利用 github pages 搭建博客,仅需阅读 1x0 章节,内容不包括上传部分。

0x1 部署 SSL 的准备工作

谷歌宣布从2018年7月即 Chrome 68 发布开始,将把所有 HTTP 站点标记为不安全。

作为信息安全工作者,我深知 SSL 存在的必要性和复杂性,但无论因为何种原因导致你需要部署 SSL ,都需要先考虑下面几个问题:

  1. 你是否基本明白 SSL 的工作原理;
  2. 网站是否计划 HTTP 和 HTTPS 并存;
  3. 新增的 SSL 开销是否影响性能或体验;
  4. 能否接受 SSL 对运维和原有组件的影响;

1x0 部署 Hexo

关于 Hexo 的介绍请前往官网了解。Hexo 博客可以像传统建站一样通过购买服务器和域名实现,也可以利用 github pages,将源码托管到 github 实现,后者需要依赖 git ,因此部分发行版的 Linux 需要更新自带的 git,无论哪种方式都需要安装 node.js。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@H0u5er ~]# yum -y install gcc zlib-devel openssl-devel perl cpio expat-devel gettext-devel curl autoconf  
[root@H0u5er ~]# yum install git-core
[root@H0u5er ~]# git --version
[root@H0u5er ~]# wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh
[root@H0u5er ~]# reboot
[root@H0u5er ~]# nvm --version
[root@H0u5er ~]# nvm install stabl // 安装稳定版,推荐执行这一条
[root@H0u5er ~]# node -v
[root@H0u5er ~]# nvm ls-remote // 安装可选版本
[root@H0u5er ~]# nvm use default
[root@H0u5er ~]# npm install -g hexo-cli // 安装必备 hexo 客户端,推荐执行这一条
[root@H0u5er ~]# npm install -g hexo-server // 安装可选
[root@H0u5er ~]# npm install -g hexo // 安装可选
[root@H0u5er ~]# pwd
[root@H0u5er ~]# hexo init blog // hexo 自动创建并设置 blog 为主文件夹
.
├── _config.yml
├── package.json
├── scaffolds
├── source
| ├── _drafts
| └── _posts
└── themes
[root@H0u5er ~]# hexo -version
[root@H0u5er ~]# cd blog // 进入主文件夹内执行 hexo 命令
[root@H0u5er blog]# hexo server // 启用 hexo 服务,默认使用 4000 端口,注意防火墙策略
[root@h0u5er blog]# hexo new new_post // 新建一篇博文
[root@h0u5er blog]# hexo help // 帮助命令

命令执行不出现问题的话,Hexo 已经安装完成。Hexo 主配置文件 _config.yml 有严格的缩进要求,通过在官方或者 github 寻找心仪的主题设计,下载主题文件到 theme 文件夹,并且将 _config.yml 的 theme 修改为对应文件夹名字,即可替换 Hexo 主题风格。

如果你是利用 github pages 搭建博客,还需要配置 git ,如上传密钥等请自行解决。

1
2
[root@h0u5er blog]# cat _config.yml | grep git
# github_username: H0u5er

2x0 申请证书

如果你是利用 github pages 搭建博客,本小节不适用。

Let‘s Encrypt 是这几年很热门的免费 SSL/TLS 证书供应商,在为我们提供便利的同时,也为许多网络犯罪提供了温床。今年 3 月开始,Let’s Encrypt 发布的ACME v2 现已正式支持通配符证书。

2x1 安装 Certbot

https://certbot.eff.org/lets-encrypt/centosrhel7-nginx

打开 Let’s Encrypt 和 Certbot 的合作网页,根据使用情况选择 Web 中间件和系统版本,页面会生成对应的安装指引。

1
2
[root@H0u5er ~]# yum install python2-certbot-nginx  // 这里我的环境是 Centos7 + Nginx
[root@H0u5er ~]# certbot --version // 这里我的环境是 Centos7 + Nginx

2x2 身份认证

1
2
[root@H0u5er ~]# certbot --help  // 查看大致使用语法
[root@H0u5er ~]# certbot --h certonly // 查看 certonly 的使用语法

申请证书方式包括Run(默认),Certonly 和 Renew,这里我建议使用 certonly 获取证书,然后通过手工配置证书的方式实现 SSL。另外 Certbot 支持 Standalone,Nginx,Webroot,Manual 等身份认证,都是为了验证域名所有者是你本人,详细区别查看这里的官方解释,使用哪种方式取决于实际网络情况和需求,下面举几个例子,更多细节问题请查阅官方资料。如果你已经使用了 CDN 服务,那么请务必采用 Webroot 的方式进行认证,因为 Let’s Encrypt 的认证服务器并不知道你的原始服务器 IP 地址。

  1. 通过 DNS 的交互验证方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@H0u5er ~]# certbot --manual --perferred-challenges dns certonly

Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): XXXX@XXXX.com

Please read the Terms of Service ....
(A)gree/(C)ancel: A

Would you be willing to share your email ....
(Y)es/(N)o: Y

Please enter in your domain name(s) (comma and/or space separated) (Enter 'c'
to cancel): example-h0u5er.com

Are you OK with your IP being logged?
(Y)es/(N)o: Y


Please deploy a DNS TXT record under the name
_acme-challenge.example-h0u5er.com with the following value:
uckdcWCoXW46RkFf630ymzjj_y5E0mE47S2VK8l3rRA
Before continuing, verify the record is deployed.
-------------------------------------------------------------------------------
Press Enter to Continue

此方式基于 DNS 的解析。在交互过程中,根据你的实际情况回答。这种方式需要你拥有对域名的解析控制权,添加一条 TXT 类型的记录,主机名为 _acme-challenge,内容为 Let’s Encrypt 在交互时产生的随机字符串,此处是 uckdcWCoXW46RkFf630ymzjj_y5E0mE47S2VK8l3rRA。在 DNS 记录添加完毕并生效之后,按 Enter 即可获得证书。

  1. 通过 Webroot 验证方式
1
[root@H0u5er ~]# certbot certonly --webroot --agree-tos -v -t --email XXXX@XXXX.com -w /var/www/ -d example-h0u5er.com

此方式基于网站目录访问。参数 -d 是需要验证的域名,系统会在 -w 参数设置的网站根目录下会创建临时文件,/网站根目录/.well-known/acme-challenge,shell 脚本会自动访问该目录,如果能访问说明你是域名的所有者,特别需要注意 DNS 的解析是否可达,可根据返回的消息进行排错。出现下述内容的时候,说明验证通过。

1
2
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/你的域名/fullchain.pem
  1. 通过 Nginx 验证方式
1
[root@H0u5er ~]# certbot --nginx --agree-tos --redirect --uir --hsts --staple-ocsp --must-staple -d www.example-h0u5er.com,example-h0u5er.com --email XXXX@XXXX.com

此方式基于 Nginx 插件。上述语句因为没有带 certonly 的参数,所以使用了默认的 run 参数代替,即在申请证书的同时自动配置好 Nginx 的 SSL 设置,还可以包括 301 重定向,CSP,HSTS,OCSP Stapling 的安全优化,但是自动配置的前提是网站并没有采用 CDN 服务。-d 参数后面如有多个子域名,可以使用逗号隔开。

  1. 通过 standalone 验证方式
1
[root@H0u5er ~]# certbot certonly --standalone --email XXXX@XXXX.com -d example-h0u5er.com

此方式基于网络。这种验证方式不依赖 Web 组件。后续可添加 –preferred-challenges http 或者–preferred-challenges tls-sni 参数,分别指定使用 80 或 443 端口进行验证,这样的话就需要保证服务器能接收互联网的请求。

2x3 自动续期

Let‘s Encrypt 默认提供的证书只有 90 天有效期,在没有 Certbot 之前,用户需要运行特定的 python 脚本使得证书可以自动续期。Let’s Encrypt 和 Certbot 合作之后,证书自动续期的事情变得非常简单,只需要利用 renew 参数,certbot 即可自动调用 /etc/letsencrypt/renewal 下的配置文件进行续期操作。如果你使用系统定时任务执行续期,请需要注意 certbot 指令在 crond 进程下的环境变量是否能够被调用。

1
2
3
[root@h0u5er ~]# certbot renew
[root@h0u5er ~]# cat /var/spool/cron/root | grep certbot
0 0 1 * * root certbot renew --quiet && systemctl reload nginx >> /var/log/letsencrypt-daily.log 2>&1

3x0 配置 Nginx

如果你是利用 github pages 搭建博客,本小节不适用。

如果你是通过购买服务器搭建博客,那么你需要 Apache 或 Nginx 等等承载 Web 服务的组件。由于 Hexo 默认工作在 4000 端口,我们可以将写好的博客转换成静态文件:

1
2
3
[root@h0u5er blog]# hexo generate      //  自动将 hexo 生成静态文件,如 HTML 
[root@h0u5er blog]# cat _config.yml | grep public
public_dir: public

静态文件会保存在 Hexo 的主目录下,文件夹的名字取决于配置文件,此处为 public 文件夹,配置 Apache 或 Nginx 使得 public 文件夹内容在 80 端口展示。还有另外一种不推荐的做法,那就是利用 Nginx 的反向代理,网站的访客如往常一样在浏览器直接输入 IP 或者域名访问默认的 80 端口,Nginx 收到请求之后将访问指向本地 4000 端口,这样做使得 Hexo 在利用 4000 端口进行本地调试的信息,也会直接暴露在 80 端口,反向代理的 nginx 配置如下:

3x1 拥有证书后的 Nginx

申请证书成功之后,默认是放在 /etc/letsencrypt/live/你的域名/ 的这个文件夹下面,在原先的配置基础上 server 部分加入 SSL 的配置就完成了基本的 SSL 支持。

1
2
3
4
5
6
7
8
9
10
11
server {  
listen 80;
listen [::]:80 ssl ipv6only=on;
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
server_name example-h0u5er.com;

ssl on;
ssl_certificate /etc/letsencrypt/live/example-h0u5er.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example-h0u5er.com/privkey.pem;
}

关于 Nginx 的配置,我部署的过程出现过许多错误,同时这样简单的配置有许多不足,如 HTTP 依然可以在网页内使用、没有启用迪菲-赫尔曼密钥交换(英语:Diffie–Hellman key exchange,缩写为D-H) 等等问题。Nginx 的 SSL 配置指南 中提及的内容很适合学习。

4x0 启用 CDN(可选)

对于不了解 CDN 加速原理的读者,建议先看看相关资料。演示采用 Cloudflare CDN 免费服务,但针对国内节点的加速效果较差,慎用。

如果你是利用 github pages 搭建博客,它同样支持 CDN 加速,但本文仅考虑且保证能在自建服务器的情况下使用该方式。

在使用 Cloudflare 的 CDN 服务之前,你需要将 DNS 的解析权交给 Cloudflare,网上有方法可以绕过,至于为什么需要把解析权交给 Cloudflare ,我推测是为了实现在不掌握用户 SSL 私钥的情况下进行 CDN 加速。首先登录到 Cloudflare 的官网, 在 DNS 菜单中选择涉及的 DNS 记录开启 CDN,同时在 Crypto 的 SSL 选项卡中开启 Full strict 模式,当 SSL 选项卡出现 Universal SSL Status Active Certificate 则完成了 CDN 加速配置,激活过程一般只需要几分钟。

关于 Full strict 模式和其他三种模式的解释对比,请翻阅官方资料,由于 Let‘s Encrypt 的有效时间较短并且属于权威机构认可的证书,因此选择 Full strict 模式。

5x0 排错与优化

错误1:浏览器提示 The plain HTTP request was sent to HTTPS port;

一般是由于中间件的配置不当导致,在我关闭 Nginx 配置中的 ssl 参数并重启 nginx 后即可解决。

1
#ssl on // 或者改为 off

可以参考此处的 Nginx 配置文件格式

错误2:www.h0u5er.com redirected you too many times;ERR_TOO_MANY_REDIRECTS;

一般是由于中间件的配置不当导致,在我调整了 Nginx 中的 301 重定向并重启 nginx 后即可解决。

错误3: Cloudflare 的Crypto SSL 一直处于 initializing Certificate 状态;

情况一,属于先部署 SSL 证书完成,再开启 CDN 的。则打开 Cloudflare CDN 功能和 SSL 的 Full strict 模式即可, initializing Certificate 一般只会持续几分钟就会转为 Active Certificate 。

情况二,属于先开启了 CDN,再从原始服务器申请 SSL 证书的。那么证书务必才用 Webroot 方式认证,同时可以使用 Nginx 的自动配置,参考如下:

1
[root@h0u5er ~]# certbot --authenticator webroot --installer nginx -w /网站主目录 --agree-tos --redirect --uir --hsts --staple-ocsp --must-staple -d www.example-h0u5er.com,example-h0u5er.com --email XXXX@XXXX.com

错误4:网站页面无法打开,error 526;

点击转跳到官方知识库页面,搞清楚 Full strict 模式和 Full 模式的应用范围与区别。

错误5: ERR_SSL_VERSION_OR_CIPHER_MISMATCH

点击转跳到medium社区,查看解决办法

优化1:部分页面在 Chrome 显示为不安全;

拿我自身的博客举例,因为之前采用 HTTP 协议的图床,所以手工替换了支持 https 的图床。目前本博客已经实现全站 HTTPS 且全站符合 Chrome 小绿锁标准,强迫症患者的福音。

优化2:Cloudflare 的 CDN 在国内效果并不好;

国内的大部分情况下 Cloudflare 是一个减速的 CDN。因为本站的服务器在国内连接速度还算不错,所以采用关闭资源自动压缩、调整缓存等级( No Query String )、开启 HTTP2、调整防火墙等级( Low ) 、关闭手机端加速等等迷之优化方式。优化后,部分地区加载网页的速度与原始 IP 访问页面的加载速度差别在毫秒级以内。

优化3:网站加固;

主机安全方面,本次采用的 CDN 虽然加速效果不怎么样,但是减少了真实 IP 暴露所带来的危害。其次建议开启服务器内的防火墙,控制端口流量,增强 RDP 或 SSH 的验证方式,合理分配系统的权限。

Web 服务方面,可以通过配置文件隐藏中间件的版本号,增加中间件的安全配置,如:

1
2
3
4
5
6
7
add_header Content-Security-Policy upgrade-insecure-requests;
add_header Strict-Transport-Security "max-age=15552000; preload" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header 'Referrer-Policy' 'origin';
add_header Feature-Policy “XXXXXXXXX”

优化4:CAA 记录配置

CAA (Certification Authority Authorization)是在 2013 年由 RFC 6844 定义的一个标准,旨在于通过控制哪些 CA 能给一个域名签发证书提升 PKI 体系的生态圈强度。 因为每一个 CA 都能给任意域名签发证书,这样会导致 CA 的证书容易被劫持,因此我们可以在 DNS 解析记录中,添加类型为 CAA 的记录,名字为 example-h0u5er.com,对应的值为 letsencrypt.org,如果是采用其他 CA 签发的证书,则替换为类似digicert.com、globalsign.com。添加一个联系邮箱,便于 CA 中心发现异常恶意签发证书时通知管理员。

1
2
3
4
[root@h0u5er conf.d]# dig h0u5er.com CAA
;; ANSWER SECTION:
h0u5er.com. 300 IN CAA 0 iodef "mailto:root@h0u5er.com"
h0u5er.com. 300 IN CAA 0 issue "letsencrypt.org"

6x0 参考资料

Ubuntu 利用Let‘s Encrypt 部署 HTTPS

测试网站的 SSL 性能

测试网站的安全头部设置

Nginx 的安全头部设置指南

7X0 更新

[2019 Feb 20] 前几天都到 Let‘s Encrypt 的邮件提醒,证书即将过期,于是到服务器执行

1
[root@h0u5er letsencrypt]# certbot renew

命令执行之后没有报错就是重新申请完成, 重启 Nginx 之后浏览器就会显示新证书了. 新证书会自动用新的数字命名,比如原本使用的是01,那么新证书就会命名成02, 并且自动以用02作为新的证书.下面这个链接文件我不知道是默认还是我自己手动改了的原因,但是也会自动指向较新的证书文件.

1
2
3
4
5
6
7
8
9
[root@h0u5er www.h0u5er.com]# pwd
/etc/letsencrypt/live/www.h0u5er.com
[root@h0u5er www.h0u5er.com]# ll
total 4
lrwxrwxrwx 1 root root 38 Dec 11 17:43 cert.pem -> ../../archive/www.h0u5er.com/cert1.pem
lrwxrwxrwx 1 root root 39 Dec 11 17:43 chain.pem -> ../../archive/www.h0u5er.com/chain1.pem
lrwxrwxrwx 1 root root 43 Dec 11 17:43 fullchain.pem -> ../../archive/www.h0u5er.com/fullchain1.pem
lrwxrwxrwx 1 root root 41 Dec 11 17:43 privkey.pem -> ../../archive/www.h0u5er.com/privkey1.pem
-rw-r--r-- 1 root root 682 Dec 11 17:43 README