送走了 8102 年,大量活化石网站依然不启用 TLS。好在 9102 年已经扑面而来,无论这些技术领域的封建老僵尸是否关注,新技术总会不断发展。
本文旨在提供一个渐进式指南,让人能够从最基础的正确监听端口开始,逐步增添一些离博物馆比较遥远的配置,形成一个在 9102 年看上去不太老土的范式。
当然,你应该以实际需求为导向,适时停下来。我们并非试图把网站变成新玩意的试验田,只是希望 9102 年的新人在搜索教程时,不要再复制到 SSLv3。
注:
- 为了操作简便,本文所涉及软件使用主流软件包管理器中默认源包含的较新版本,而非需要自行编译的最新版本。
- 为了内容集中,本文尽量避免一些功能性的内容,例如 PHP、Python 等脚本语言或 404 页面、访问日志的配置。
- 文章中的网站使用本站域名
yvesx.com
作为示例,但本站实际使用的配置并非文中所述。
跑起来再说
如果你的 Nginx 没有在安装完成后自启动,要把服务启动一下。用浏览器访问 IP 地址,能看到 Nginx 的默认页面。
把域名解析指向服务器(可能需要一点时间生效),然后配置最基本的信息:监听端口、域名、网站目录。
server {
listen 80;
root /your_website_root_directory/;
server_name www.your_domain.com;
}
建议使用 nginx -t
检查一下明显的语法错误。(它不是万无一失的,尤其对于爱用 if
语法的人——记住,你正在编辑的只是一份配置文档,它不是一个编程语言。)
正确保存配置文件以后,软重启 Nginx 服务即可使其生效。如:systemctl reload nginx
在浏览器中输入域名访问你的网站,如果成功,那么它已经是一个看上去还算正常的网站了。
简单配置 TLS
启用 TLS 根本没有很多人想像的那样麻烦,在得到证书与私钥后,只需两行指定。
server {
listen 80;
listen 443 ssl;
root /your_website_root_directory/;
server_name www.your_domain.com;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
# 显然,证书与私钥的路径是任意的。它们既不一定在同一目录下,也不一定是 *.cer 与 *.key
}
依然要检测和软重启生效 Nginx 配置,后文不再就此赘述。
你可以在浏览器中访问 https://www.your_domain.com/
观察 TLS 部署是否成功。
统一入口
第一,除了 www.your_domain.com
,我们还应该正确解析 your_domain.com
。第二,我们应该对这两个域名同时支持 http://
与 https://
的正常访问。第三,这样一来任何页面或资源都有了 4 种可用的 URL,所以我们要让它们统一到同一个上面。
一般的思路是在监听 80 端口、监听 443 端口并分别配置好 TLS 的基础上,使用 301 跳转到你想要的 URL。例如:
# 对于裸域,监听 80 端口与 443 端口,一律跳转。
server {
listen 80;
listen 443 ssl;
server_name your_domain.com;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
# 注意这里的证书与私钥都是签给裸域的。
return 301 https://$host$request_uri;
}
# 对于 www 的 80 端口也要跳转。
server {
listen 80;
server_name www.your_domain.com;
return 301 https://$host$request_uri;
}
# www 的 TLS 是我们要的“正式”访问方式。
server {
listen 443 ssl;
root /your_website_root_directory/;
server_name www.your_domain.com;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
# 注意这里的证书与私钥都是签给 www 主机名的。
}
比较容易出错的地方是,对于带 www 与无 www 的两个域名,你需要正确配置两张匹配的证书。
更快更安全更现代的 TLS
很多人启用 TLS 是为了安全(还有一些人是认为绿锁好看),但不幸的是,旧的协议并不安全。(其实不止如此,OpenSSl 历史上的几次安全事件严重动摇了人们对开源软件的看法,以往大家都天真地以为开源软件意味着人人审计、共同维护,这会带来更好的安全性,但残酷的现实是,一个人人都在用的开源组件漏洞百出,被利用很长时间才在社区中有反响。)
很多人不用 TLS 是为了性能,但欣慰的是,我们有缓存机制。
在客户端,可能存在使用 OCSP 实时查询证书有效性的情况。这无疑相当影响性能。我们启用 OCSP Stapling,让服务器获取查询结果并缓存起来,以直接提供给客户端,绕过它自行查询的过程,而保留了安全性。
Session Ticket 是一种加速 TLS 握手的技术,它将服务端加密的会话信息保存在客户端,于是客户端将该信息发送到服务端时,服务端能够解密并认可客户端,从而跳过证书交换过程。它的问题在于,多服务器需要使用相同的 Ticket Key 才能实现复用,而为了安全性它们又应该定时更换,一些人会忽略这一点,引入额外的安全隐患。这里我们已经有了缓存,证书交换频率不高,索性关闭这一特性。
让我们回顾一下要做的事情:
- 使用会话缓存提高性能
- 指定较新的 TLS 实现版本提高安全性
- 启用 OCSP Stapling 提高性能
- 关闭 Session Ticket 减小潜在风险(这不是必要的)
配置如下:
server {
listen 80;
listen 443 ssl;
server_name your_domain.com;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
return 301 https://$host$request_uri;
}
server {
listen 80;
server_name www.your_domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
root /your_website_root_directory/;
server_name www.your_domain.com;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
}
我们将相同的两段配置给了两处 TLS,这其中刚好没有包含路径、域名之类需要替换的东西,你也可以直接复制这一段,粘贴到对应位置。
注意:旧的浏览器或操作系统并不支持新的协议。使用 TLSv1.2,意味着网站最低只能支持到:Firefox 27、Chrome 30、IE 11(Windows 7)、Opera 17、Safari 9、Android 5.0、Java 8(还有 Edge)
在检查 Nginx 配置时,你可能会遇到这样一条警告:nginx: [warn] "ssl_stapling" ignored, issuer certificate not found
这是你的证书没有正确配置。一般来说,你应该指定完整证书链(包括 CA 证书),或者另行指定上级信任链(使用 ssl_trusted_certificate
字段)。但你可能对证书机制了解得不多,而证书与证书看上去都是一样的,很容易在此出错,所以建议直接给 ssl_certificate
字段指定完整证书链文件。
为你的 TLS 设置 HSTS
HSTS(HTTP Strict Transport Security)策略约定,你可以设置一个 HTTP 响应头消息,告诉浏览器在一定时长内强制 HTTPS 方式访问。“强制”的意思是将所有 HTTP 方式的访问改换成 HTTPS,如果在此期间你的 TLS 配置错误,会导致无法访问网站。
显而易见,HSTS 可以减小中间人攻击的可能性,并降低从 HTTP 301 跳转到 HTTPS 的频率。
HSTS 的设置非常简单,只需增加一个 HTTP 类似这样的响应头消息:strict-transport-security: max-age=15768000; includeSubDomains
。此处的 15768000 可以自定义(秒),我们选的这个数刚好是六个月。includeSubDomains
不是必须的,如果加上它,HSTS 策略也会启用在子域上。
注意:上面给出的是 HTTP 响应头消息,并不是 Nginx 配置。要在 Nginx 里设置这个响应头,应该增加下述行:add_header Strict-Transport-Security max-age=15768000;
完整配置如下:
server {
listen 80;
listen 443 ssl;
server_name your_domain.com;
add_header Strict-Transport-Security max-age=15768000;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
return 301 https://$host$request_uri;
}
server {
listen 80;
server_name www.your_domain.com;
add_header Strict-Transport-Security max-age=15768000;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
root /your_website_root_directory/;
server_name www.your_domain.com;
add_header Strict-Transport-Security max-age=15768000;
ssl_certificate /your_certificate_directory/your_certificate.cer;
ssl_certificate_key /your_certificate_directory/your_private_key_of_the_certificate.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
}
注意:这个响应头消息应该出现在所有情况下,包括那个只监听 80 端口的 server。
如果你对信息安全足够敏锐,可能会意识到,HSTS 目前的机制需要在首次访问后生效,并不能杜绝降级可能。
这个顾虑是必要的。为此,Chrome(准确讲是 Chromium)维护了一个预载表,将启用 HSTS 的网站收录其中并硬编码给浏览器,以便浏览器在初次遇见你的域名时就能正确执行 HSTS 策略。
目前预载表的收录要求是:时间长达一年(max-age=31536000
)、包含子域名(includeSubDomains
)、包含一个标识(preload
)。在 Nginx 配置文件中应该这样写:add_header Strict-Transport-Security "max-age=15768000;includeSubDomains;preload";
。
如果你已经准备好将自己的域名提交到这个列表中,请访问这里
你可以在Chromium 预载表源文件中查询到本站域名 yvesx.com
还挺靠前。
注意:被预载表收录以后,你的域名只有在正确配置 TLS 的情况下才能访问。由于这是浏览器内置的列表,相较于单纯的 HSTS 来说更加不可控,你可能会遇到因证书错误而无法访问网站、自己没有办法临时绕过的尴尬情况。
中场休息:跑个分试试
TLS 的配置到这里就基本结束了,为了检验成果,你可以在Qualys SSL Labs测评一下。
如果你完全按照本教程走,现在拥有一个高性能、安全、现代的 TLS 配置,必然能拿到 A+。
1 条评论
9012 不是应该直接用 Caddy 嘛?