使用NGINX安全分发SSL私钥

NGINX | 2019年4月02日

本篇博客介绍了几种安全分发SSL私钥的方法,这些私钥供NGINX托管采用SSL加密的网站时使用。文中将说明:

  • 使用NGINX配置SSL的标准方法及其潜在的安全限制
  • 如何使用与NGINX配置分开存储的密码加密密钥 using passwords that are stored separately from the NGINX configuration
  • 如何安全分发加密密码,避免将其存储在磁盘上,并在需要时将其撤销

对于许多部署,标准方法已经足够。本文讨论的两种更复杂的方法可阻止攻击者通过其他途径获取SSL私钥。我们还将在后续文章中介绍另外几种技术:

  • 使用HashiCorp Vault等第三方密钥存储安全分发密码
  • 自动将证书从Vault配置到NGINX Plus的键值存储中,确保私钥材料绝不会存储在磁盘上

本文介绍的方法适用于需要自行管理密钥并制定安全密钥分发策略的用户。对于在已集成机密存储的环境中运行NGINX的用户,则无需采用这些方法,例如Kubernetes。

本文同时适用于NGINX Open Source和NGINX Plus。为便于阅读,下文将统一称为“NGINX”。

编者按:本文是“保护NGINX中的SSL私钥”系列文章的第一篇。另请参阅本系列的其他文章:

为何要保护SSL私钥?

SSL/TLS用于对网络事务进行身份验证和加密,并验证其完整性。网站使用由证书颁发机构(CA)签名的公钥证书进行身份验证,并使用对应的私钥(必须保密)执行计算,以证明其拥有该证书。

如果私钥泄露(即被其他实体获取),将面临两项主要风险。

  • 风险1:冒充。获得私钥的攻击者可以拦截网络流量,进而发起中间人(MITM)攻击。此类攻击会在客户端和网站均不知情的情况下捕获并解密所有流量,还可能篡改流量。
  • 风险2:解密。拥有私钥且已记录网络流量的攻击者可以离线解密这些网络流量。请注意,此类攻击无法用于采用完全前向保密(PFS)密码套件的连接。

如果私钥遭到泄露,唯一的补救措施是联系CA并请求吊销证书;之后只能依赖客户端检查并遵循证书的吊销状态。

此外,建议使用有效期较短的证书(例如,Let’s Encrypt证书将在90天后过期)。在证书即将过期时,需要生成新的私钥,并从CA获取新证书。这样可以降低私钥泄露时面临的风险。

NGINX安全边界

哪些人员和进程可以访问NGINX中的SSL私钥?

首先,任何获得运行NGINX的服务器root访问权限的用户,都可以读取和使用NGINX本身所使用的所有资源。例如,已有方法可从运行中进程的内存中提取SSL私钥。

因此,无论如何存储和分发私钥,都无法防止私钥被主机服务器上拥有root权限的攻击者获取。

其次,任何能够修改并提交NGINX配置的用户都可以通过多种方式利用这一权限,例如开放对内部服务的代理访问、绕过身份验证措施等。该用户可以修改NGINX配置,以获取服务器的root访问权限(或同等权限),不过SELinux和AppArmor等工具有助于降低这种可能性。

因此,通常无法防止私钥遭到能够修改并提交NGINX配置的攻击者窃取。

所幸,任何具备相应能力的组织都有完善的安全流程,使攻击者难以获取root权限或修改NGINX配置。

不过,权限较低的攻击者还可能通过另外两种方式获取私钥:

  • 用户可能出于正当原因需要查看NGINX配置,也可能获得配置数据库或备份的访问权限。NGINX私钥通常存储在配置中。
  • 用户可能会通过虚拟机监控程序或系统备份等途径访问NGINX服务器的文件系统。文件系统中存储的任何数据(包括私钥材料)都可能被访问。

本文所述流程可封堵这两种攻击途径。

标准NGINX配置

首先,我们来看看采用SSL/TLS的典型NGINX配置:

server {
    listen 443 ssl;

    server_name a.dev0; 

    ssl_certificate ssl/a.dev0.crt;
    ssl_certificate_key ssl/a.dev0.key;

    location / {
        return 200 "Hello from service A\n";
    }
}

SSL公钥证书(a.dev0.crt)和私钥(a.dev0.key)存储在文件系统的/etc/nginx/ssl/目录中。只有通常以root身份运行的NGINX主进程会读取该私钥,因此可以为其设置尽可能严格的访问权限:

root@web1:/etc/nginx/ssl# ls -l a.dev0.key-r-------- 1 root root 1766 Aug 15 16:32 a.dev0.key

私钥必须始终可用;每当NGINX软件启动、重新加载配置或执行语法检查(nginx -t)时,NGINX主进程都会读取该私钥。

有关配置SSL/TLS的更多信息,请参阅《NGINX Plus管理指南》。

标准配置的安全影响

如上所述,攻击者一旦获得运行中容器、虚拟机或服务器的root访问权限,即可读取运行NGINX软件的该环境中的SSL私钥。

加密SSL私钥

NGINX支持使用AES256等安全算法加密的私钥:

root@web1:/etc/nginx/ssl# mv a.dev0.key a.dev0.key.plainroot@web1:/etc/nginx/ssl# openssl rsa -aes256 -in a.dev0.key.plain -out a.dev0.key
writing RSA key
Enter PEM pass phrase: secure password
Verifying - Enter PEM pass phrase: secure password again

随后启动NGINX,或重新加载或测试NGINX配置时,NGINX会以交互方式要求输入解密密码:

root@web1:/etc/nginx# nginx -tEnter PEM pass phrase: secure password
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

使用SSL密码文件

以交互方式输入密码既不方便,也难以实现自动化,但可以配置NGINX使用存储在单独文件中的密码列表,该文件由ssl_password_file指令指定。当NGINX需要读取私钥时,会依次使用文件中的每个密码尝试解密私钥。如果所有密码均无效,NGINX将拒绝启动。

ssl_password_file /var/lib/nginx/ssl_passwords.txt;

必须将ssl_password_file与配置分开分发,并且仅允许root用户读取。可将其视为放置在可信服务器上的授权令牌。只有在具有该授权令牌的服务器上运行时,NGINX才能解密私钥。

将加密密钥存储在单独文件中的安全影响

此方法使攻击者仅凭NGINX配置无法得逞,从而缩小攻击面。攻击者还必须获取ssl_password_file的内容。

如果攻击者确实获得了存储root的文件系统的ssl_password_file访问权限(例如,通过备份或主机系统),便可读取该文件,并使用其中的密码解密SSL私钥。

将ssl_password_file存储在RAM磁盘或tmpfs上,可以降低此风险。外部攻击者通常更难访问此类存储(例如,服务器重启时会清除其中的数据),并且可以将其排除在系统备份之外。必须确保系统启动时初始化密码文件。

更安全地分发SSL密码列表

以下流程介绍了如何从中央分发点以更安全的方式分发SSL密码列表。

每当NGINX需要解密SSL密钥时,都会查询中央分发点并使用密码,而不会将密码存储在本地磁盘上。NGINX实例使用令牌向中央密码服务器进行身份验证。您可以随时撤销该令牌,以阻止其访问密码。

创建中央密码分发点

首先创建一个密码分发点(PDP)。在这个简单的实现中,我们使用HTTPS服务来传送密码列表,并通过用户名和密码进行身份验证:

$ curl -u dev0:mypassword https://pdpserver.local/ssl_passwords.txt
password1
password2
...

然后,您可以根据需要在PDP中添加或移除身份验证令牌,以授予或撤销访问权限。您可以使用NGINX等Web服务器实现密码分发服务器,并采用适当类型的身份验证令牌。

接下来,需要配置NGINX以从PDP获取密码。首先,创建一个名为connector.sh的shell脚本,内容如下:

#!/bin/sh
# Usage: connector.sh   

CONNECTOR=$1
CREDS=$2
PDP_URL=$3

[ -e $CONNECTOR ] && /bin/rm -f $CONNECTOR

mkfifo $CONNECTOR; chmod 600 $CONNECTOR

while true; do
    curl -s -u $CREDS -k $PDP_URL -o $CONNECTOR
done

该脚本需要作为后台进程运行,调用方式如下:

root@web1:~# ./connector.sh /var/run/nginx/ssl_passwords \dev0:mypassword https://pdpserver.local/ssl_passwords.txt &

连接器会挂载到指定的本地路径(/var/run/nginx/ssl_passwords),然后使用ssl_password_file指令配置NGINX访问该路径:

ssl_password_file /var/run/nginx/ssl_passwords;

通过从连接器路径读取数据来测试连接器:

root@web1:~# cat /var/run/nginx/ssl_passwords
password1
password2
...

验证NGINX能否读取密码并解密SSL密钥:

root@web1:~# nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

可以使用集中式PDP方法,安全分发NGINX通常从磁盘读取的任何资源,例如单独的私钥或其他敏感数据。

PDP的安全影响

与将SSL密码存储在磁盘上相比,此解决方案具有以下几项优势:

  • SSL密码绝不会存储在服务器的文件系统中,因此,即使攻击者能够访问该文件系统,也无法直接获取这些密码。
  • 密码从中央访问点分发,便于开展监控和审计。
  • 可集中控制各台服务器的访问权限。例如,服务器停用后,可撤销其访问令牌。

请注意,有权访问文件系统的用户可能会提取用于访问PDP的凭据。不再需要这些凭据时,务必将其撤销。

总结

可通过多种方式防止SSL私钥泄露,其安全性和复杂性逐级提高:

  • 对于绝大多数组织,只需限制对NGINX运行环境的访问,确保未经授权的用户无法获得root访问权限,也无法查看NGINX配置。
  • 对于某些环境,可能无法完全限制对NGINX配置的访问,因此可以使用SSL密码文件。
  • 在少数情况下,组织可能希望确保密钥和密码绝不存储在磁盘上。密码分发点流程展示了此解决方案的概念验证。

本系列的其他文章介绍了可用于保护SSL密钥的其他措施:

亲自体验NGINX Plus,立即开始30天免费试用,或联系我们,探讨您的使用场景。


分享

关于作者

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。