13.SSL终止
简介
SSL Termination 是一种网络架构设计模式,其中 SSL(Secure Sockets Layer)或其继任者 TLS(Transport Layer Security)的加密和解密功能被放置在网络通信的终点,而不是在整个网络链路上。
在 Nginx 中,SSL Termination 意味着 Nginx 服务器负责处理 SSL/TLS 握手过程和加解密通信的任务,然后将未加密的流量传递给后端服务器。这样,后端服务器无需处理加密和解密的任务,从而减轻了服务器的负担。
具体步骤如下:
- 客户端与 Nginx 之间的通信: 客户端与 Nginx 之间的通信使用 SSL/TLS 进行加密。Nginx 接收到客户端的加密请求。
- SSL/TLS 握手过程: Nginx 执行 SSL/TLS 握手过程,与客户端协商密钥并建立加密通道。
- 解密和转发: Nginx 解密客户端的请求,然后将未加密的请求转发 给后端服务器。
- 后端服务器处理: 后端服务器处理未加密的请求,生成响应。
- 加密和返回: Nginx 将后端服务器的响应加密,然后将加密的响应返回给客户端。
这种模式的优势在于:
- 后端服务器无需处理 SSL/TLS 握手和加解密,减轻了服务器的负担,提高了性能。
- Nginx 可以集中管理 SSL/TLS 证书,简化证书的部署和更新。
- 可以方便地在 Nginx 层面实现负载均衡、缓存和其他高级功能。
获取 SSL 证书
首先,您需要获取服务器证书和私钥并将它们放在服务器上。证书可以从受信任的证书颁发机构 (CA) 获取,也可以使用 SSL 库(如 OpenSSL)生成。
添加 SSL 证书
要添加 SSL 证书,请使用 ssl_certificate 指令指定证书的路径(必须采用 PEM 格式),并在 ssl_certificate_key 指令中指定私钥的路径:
server {
#...
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/certs/server.key;
}
此外,ssl_protocols 和 ssl_ciphers 指令可用于限制连接,并仅包含 SSL/TLS 的强版本和密码:
server {
#...
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
}
ssl_ciphers指令告诉NGINX通知SSL库它更喜欢哪种密码。
设置 HTTPS 服务器
要设置 HTTPS 服务器,请在 nginx.conf 文件中将 ssl参数包含在服务器块中的 listen 指令中,然后指定服务器证书和私钥文件的位置:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}
服务器证书是一个公共实体。它被发送到连接到NGINX服务器的每个客户端。私钥是一个安全实体,应存储在具有受限访问权限的文件中。但是,NGINX 主进程必须能够读取此文件。或者,私钥可以存储在与证书相同的文件中:
ssl_certificate www.example.com.cert;
ssl_certificate_key www.example.com.cert;
ssl_protocols 和 ssl_ciphers 指令可用于要求客户端在建立连接时仅使用 SSL/TLS 的强版本和密码。
从版本1.9.1开始,NGINX使用以下默认值:
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
在旧密码的设计中有时会发现漏洞,我们建议在现代NGINX配置中禁用它们(不幸的是,由于现有NGINX部署的向后兼容性,默认配置无法轻松更改)。请注意,CBC 模式密码可能容易受到许多攻击(特别是 CVE-2011-3389 中所述的 BEAST 攻击),我们建议不要使用 SSLv3,因为 POODLE 攻击,除非您需要支持旧客户端。
客户端证书的 OCSP 验证(OCSP Validation of Client Certificates)
在 Nginx 中,客户端证书的 OCSP(Online Certificate Status Protocol)验证是一种用于验证客户端证书的有效性的机制。OCSP 允许 Nginx 向证书颁发机构(CA)的 OCSP 服务器发送请求,以获取有关客户端证书当前状态的信息。
NGINX可以配置为使用在线证书状态协议(OCSP)来检查X.509客户端证书的有效性。针对客户端证书状态的 OCSP 请求将发送到 OCSP 响应程序,该响应程序检查证书有效性并返回包含证书状态的响应:
Good- 证书未吊销Revoked- 证书被吊销Unknown- 没有关于客户端证书的信息
要启用 SSL 客户端证书的 OCSP 验证,请指定 ssl_ocsp 指令以及启用证书验证的 ssl_verify_client 指令:
server {
listen 443 ssl;
ssl_certificate /etc/ssl/foo.example.com.crt;
ssl_certificate_key /etc/ssl/foo.example.com.key;
ssl_verify_client on;
ssl_trusted_certificate /etc/ssl/cachain.pem;
ssl_ocsp on; # Enable OCSP validation
#...
}
NGINX将OCSP请求发送到客户端证书中嵌入的OCSP URI,除非使用ssl_ocsp_responder指令定义了不同的URI。 支持 Only http:// OCSP 响应程序:
#...
ssl_ocsp_responder http://ocsp.example.com/;
#...
要将 OCSP 响应缓存在所有工作进程共享的单个内存区域中,请指定 ssl_ocsp_cache 指令以定义区域的名称和大小。响应将缓存 1小时,除非 OCSP 响应中的 nextUpdate值指定了不同的值:
#...
ssl_ocsp_cache shared:one:10m;
#...
客户端证书验证的结果在 $ssl_client_verify 变量中可用,包括 OCSP 失败的原因。
HTTPS 服务器优化(HTTPS Server Optimization)
实现 SSL/TLS 会显著影响服务器性能,因为 SSL 握手操作(客户端和服务器交换以验证连接是否受信任的一系列消息)占用大量 CPU。有两种方法可以最大程度地减少每个客户端的这些操作数:
- 启用保持连接以通过一个连接发送多个请求(Reusing SSL session parameters to avoid SSL handshakes for parallel and subsequent connections)
- 重用 SSL 会话参数以避免并行和后续连接的 SSL 握手(Reusing SSL session parameters to avoid SSL handshakes for parallel and subsequent connections)
SSL 握手的默认超时为 60 秒,可以使用 ssl_handshake_timeout 指令重新定义。我们不建议将此值设置得太低或太高,因为这可能会导致握手失败或等待握手完成的时间过长:
server {
#...
ssl_handshake_timeout 10s;
}
优化 SSL 会话缓存(Optimizing the SSL Session Cache)
会话存储在工作进程之间共享的 SSL 会话缓存中,创建适用于每个 SSL/TLS 连接的会话参数的缓存可减少握手次数,从而显著提高性能。缓存是使用 ssl_session_cache 指令设置的。1 MB的缓存包含大约 4000 个会话。默认缓存超时为 5 分钟。可以使用 ssl_session_timeout 指令增加此超时。以下是针对具有 10 MB 共享会话缓存的多核系统优化的示例配置:
worker_processes auto;
http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name www.example.com;
keepalive_timeout 70;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}
}
Session Tickets
会话票证(Session Tickets)是会话缓存的替代方法。会话信息存储在客户端,无需服务器端缓存来存储会话信息。当客户端恢复与后端服务器的交互时,它会显示会话票证,无需重新协商。将 ssl_session_tickets 指令设置为 :on
server {
#...
ssl_session_tickets on;
}
注意:对upstream组使用会话票证时,必须使用相同的会话密钥初始化每个上游服务器。最佳做法是经常更改会话密钥,建议实施一种机制来在所有upstream服务器之间轮换共享密钥:
server {
#...
ssl_session_tickets on;
ssl_session_ticket_key /etc/ssl/session_ticket_keys/current.key;
ssl_session_ticket_key /etc/ssl/session_ticket_keys/previous.key;
}
完整示例
stream {
upstream stream_backend {
server backend1.example.com:12345;
server backend2.example.com:12345;
server backend3.example.com:12345;
}
server {
listen 12345 ssl;
proxy_pass stream_backend;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/certs/server.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 4h;
ssl_handshake_timeout 30s;
#...
}
}
在此示例中,server块中的指令指示NGINX终止和解密来自客户端的安全TCP流量,并将其未加密地 流量传递给由三个服务器组成的upstream组 stream_backend。
侦听(listen)指令的参数指示NGINX 接受SSL连接。当 clent 请求安全 TCP 连接时,NGINX 会启动握手过程,该过程使用 ssl_certificate 指令指定的 PEM 格式证书、ssl_certificate_key 指令指定的证书私钥以及 ssl_protocols 和 ssl_ciphers 指令列出的协议和密码。ssl
一旦建立了安全的TCP连接,NGINX 就会根据ssl_session_cache指令缓存会话参数。在此示例中,会话缓存在所有工作进程(参数 shared)之间共享,大小为 20 MB(参数 20m),并将每个 SSL 会话保留 4 小时以供重用(ssl_session_timeout 指令)。
SSL 证书链(SSL Certificate Chains)
某些浏览器可 能会抱怨由知名证书颁发机构签名的证书,而其他浏览器可能会毫无问题地接受该证书。发生这种情况是因为颁发机构使用中间证书对服务器证书进行了签名,该中间证书不存在于分布在特定浏览器中的已知受信任证书颁发机构库中。在这种情况下,颁发机构提供应连接到签名服务器证书的链式证书捆绑包。服务器证书必须出现在合并文件中链接的证书之前:
$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt
生成的文件应在 ssl_certificate 指令中使用:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.chained.crt;
ssl_certificate_key www.example.com.key;
#...
}
如果服务器证书和捆绑包以错误的顺序连接,NGINX 将无法启动并显示以下错误消息:
SSL_CTX_use_PrivateKey_file(" ... /www.example.com.key") failed
(SSL: error:0B080074:x509 certificate routines:
X509_check_private_key:key values mismatch)
发生此错误的原因是 NGINX 尝试将私钥与捆绑包的第一个证书而不是服务器证书一起使用。
浏览器通常存储它们接收并由受信任的颁发机构签名的中间证书。因此,活跃使用的浏览器可能已经拥有所需的中间证书,并且可能不会抱怨在没有链式捆绑包的情况下发送的证书。为了确保服务器发送完整的证书链,可以使用 openssl 命令行实用程序:
$ openssl s_client -connect www.godaddy.com:443
...
Certificate chain
0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
/1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
/OU=MIS Department/CN=www.GoDaddy.com
/serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
/OU=http://certificates.godaddy.com/repository
/CN=Go Daddy Secure Certification Authority
/serialNumber=07969287
1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
/OU=http://certificates.godaddy.com/repository
/CN=Go Daddy Secure Certification Authority
/serialNumber=07969287
i:/C=US/O=The Go Daddy Group, Inc.
/OU=Go Daddy Class 2 Certification Authority
2 s:/C=US/O=The Go Daddy Group, Inc.
/OU=Go Daddy Class 2 Certification Authority
i:/L=ValiCert Validation Network/O=ValiCert, Inc.
/OU=ValiCert Class 2 Policy Validation Authority
/CN=http://www.valicert.com//[email protected]
...
在此示例中,www.GoDaddy.com服务器证书的主题 (“s”) 由颁发者 (“i”) 签名,该颁发者本身就是证书 #1 的主题。证书 #1 由颁发者签名,该颁发者本身是证书 #2 的主题。但是,此证书由知名颁发者 ValiCert, Inc.签名,其证书存储在浏览器中。
如果尚未添加证书捆绑包,则仅显示服务器证书 。
单个 HTTP/HTTPS 服务器(A Single HTTP/HTTPS Server)
可以通过在同一虚拟服务器中放置一个带 ssl参数的 listen指令和一个不带参数的指令来配置处理 HTTP 和 HTTPS 请求的单个服务器:
server {
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
#...
}
在 NGINX 版本 0.7.13 及更早版本中,无法有选择地为单个侦听套接字启用 SSL,如上所示。SSL只能使用SSL指令为整个服务器启用,因此无法设置单个HTTP / HTTPS服务器。已将侦听指令的 ssl参数添加到以解决此问题。因此,在版本 0.7.14 及更高版本中不推荐使用 ssl 指令。
基于名称的 HTTPS 服务器(Name-Based HTTPS Servers)
当将两个或多个 HTTPS 服务器配置为侦听单个 IP 地址时,会出现一个常见问题:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}
server {
listen 443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}
使用此配置,浏览器将接收默认服务器的证书。在这种情况下,它与请求的服务器名称 www.example.com无关。这是由 SSL 协议本身的行为引起的。SSL连接是在浏览器发送HTTP请求之前建立的,NGINX不知道所请求服务器的名称。因此,它可能只提供默认服务器的证书。
解决此问题的最佳方法是为每个HTTPS服务器分配一个单独的IP地址:
server {
listen 192.168.1.1:443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}
server {
listen 192.168.1.2:443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}
请注意,HTTPSupstream(proxy_ssl_ciphers,proxy_ssl_protocols和proxy_ssl_session_reuse)也有一些特定的代理设置,可用于微调NGINX和上游服务器之间的SSL。
具有多个名称的 SSL 证书(An SSL Certificate With Several Names)
还有其他方法可以在多个 HTTPS 服务器之间共享一个 IP 地址。 然而,它们都有缺点。 一种方法是在 SubjectAltName证书字段中使用具有多个名称的证书,例如 www.example.com 和 www.example.org。 但是,SubjectAltName、】字段的长度是有限的。
另一种方法是使用带有通配符名称的证书,例如 *.example.org*。 通配符证书保护指定域的所有子域,但仅在一个级别上。 此证书与 *www.example.org匹配,但与 example.org 或 www.sub.example.org*不匹配。 这两种方法也可以结合使用。 证书可以在 SubjectAltName * 字段中包含准确名称和通配符名称。 例如, example.org * 和 * .example.org。
最好将具有多个名称的证书文件及其私钥文件放在配置的 http 级别,以便它们在所有服务器上继承单个内存副本:
ssl_certificate common.crt;
ssl_certificate_key common.key;
server {
listen 443 ssl;
server_name www.example.com;
#...
}
server {
listen 443 ssl;
server_name www.example.org;
#...
}
服务器名称指示(Server Name Indication)
在单个 IP 地址上运行多个 HTTPS 服务器的更通用的解决方案是 TLS 服务器名称指示 (SNI) 扩展 ,它允许浏览器在 SSL 握手期间传递请求的服务器名称。使用此解决方案,服务器将知道应使用哪个证书进行连接。
在 SNI 中只能传递域名。但是,如果请求包含文字 IP 地址,某些浏览器会将服务器的 IP 地址作为其名称传递。最好不要依赖这个。
为了在NGINX中使用SNI,必须在构建NGINX二进制文件的OpenSSL库以及在运行时动态链接的库中支持SNI。OpenSSL 从版本 0.9.8f 开始支持 SNI,如果它是使用 option --enable-tlsext配置构建的。从 OpenSSL 版本 0.9.8j 开始 ,此选项默认处于启用状态。如果NGINX是在SNI支持下构建的,则NGINX在使用 -V开关运行时会显示以下内容:
$ nginx -V
...
TLS SNI support enabled
...
但是,如果启用了SNI的NGINX动态链接到不支持SNI的OpenSSL库,NGINX将显示警告:
NGINX was built with SNI support, however, now it is linked
dynamically to an OpenSSL library which has no tlsext support,
therefore SNI is not available