如何在 NGINX 中安全地分发 SSL 私钥

语言: CN / TW / HK

原文作者:Owen Garrett of F5

原文链接:如何在 NGINX 中安全地分发 SSL 私钥

转载来源:NGINX 官方网站


NGINX 唯一中文官方社区 ,尽在 nginx.org.cn

本文介绍了 SSL 加密网站使用 NGINX 时可采用的几种安全分发 SSL 私钥的方法。本文讨论了:

对于多数的部署场景,标准方法就能够满足需求。本文还介绍了两种更复杂的方法,可防止攻击者通过其他方式获得 SSL 私钥。有关更多相关技术的探讨,请参阅以下博文:

  • 使用诸如 HashiCorp Vault 等第三方密钥存储安全地分发密码
  • 自动将证书从 Vault 提供到 NGINX Plus 键值存储,从而确保私钥材料永远不会存储在磁盘上

本文介绍的方法适用于需要自行管理密钥并创建安全密钥分发策略的用户。对于那些运行中的 NGINX 环境已经集成了密钥存储(例如 Kubernetes)的用户来说,无需使用这些方法。

本文适用于 NGINX 开源版和 NGINX Plus。为了便于阅读,我们把两者统称为NGINX

编者按——本文是有关如何在 NGINX 中保护 SSL 私钥的系列博文的第一篇。另请参阅本系列博文的其他文章:

为什么要保护 SSL 私钥?

SSL/TLS 往往被用于对网络交易的完整性进行身份验证、加密和验证。网站通过由证书颁发机构 (CA) 签名的公共证书表明自身身份,并通过使用相应的私钥(必须保密)进行计算,证明其拥有的证书。

如果私钥遭到泄露(披露给另一个实体),则面临两大风险。

  • 风险 1:伪装。窃取到私钥的攻击者可劫持网络流量,然后发起中间人攻击。此类攻击会捕获并解密所有流量,也可能会在客户或网站毫无察觉的情况下篡改流量。
  • 风险 2:解密。窃取到私钥并且已将网络流量记录下来的攻击者,可离线解密网络流量。值得一提的是,无法向使用完全向前保密 (PFS) 密钥的连接发起此类攻击。

如果私钥遭到泄露,您唯一的找回方式就是与 CA 中心联系,申请撤销证书;然后必须依靠客户来检查并执行撤销状态。

此外,建议使用有效期较短的证书,(例如 Let’s Encrypt 证书的有效期为 90 天)。证书即将到期时,您需要生成一个新的私钥,并从 CA 获得新证书。一旦私钥遭到泄露,这可帮助您降低风险。

NGINX 安全边界

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

首先,拥有 root 权限的任何用户都能够接入运行 NGINX 的服务器,并读取和使用 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/ 的文件系统中。该私钥仅可由 NGINX 主进程读取,通常以 root 身份或权限运行,因此可为其设置最严格的访问权限,如下所示。

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 管理指南》

标准配置的安全隐患

如上所述,当攻击者已窃取到运行 NGINX 的容器、虚拟机或服务器的 root 权限,攻击者就可以读取 SSL 私钥。

加密 SSL 私钥

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

root@web1:/etc/nginx/ssl# mv a.dev0.key a.dev0.key.plain
root@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 -t
Enter 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 密码文件

以交互的方式输入密码很不方便,且难以自动化,但是您可以通过 ssl_password_file 命令将 NGINX 配置为使用此命令所指定的单独文件中的密码列表。当 NGINX 需要读取私钥时,它会尝试使用该文件中的每个密码解密密钥。如果密码都无效,NGINX 将拒绝启动。

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

ssl_password_file 必须独立于配置分发,并且仅可供 root 用户读取。您可将其视为放置在可信服务器上的授权令牌。NGINX 在使用授权令牌的服务器上运行时,才能解密私钥。

将加密密钥存储在单独文件中的安全隐患

该方法对攻击者而言,其仅仅获取到单独的 NGINX 配置是无用的,从而减少攻击面。攻击者还必须获取 ssl_password_file 中的内容。

如果攻击者获取了 ssl_password_file 所在文件系统的 root 权限(例如从备份中或通过主机系统),攻击者就可以读取文件并使用密码解密 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 上通过按需添加或删除身份验证令牌,来启用或撤销访问权限。可利用 Web 服务器(例如 NGINX )实施密码分发服务器,并视情况使用任何类型的身份验证令牌。

接下来,需要将 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 -t
nginx: 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 密码从不存储在服务器的文件系统上,因此即便攻击者能够访问文件系统,也无法直接访问 SSL 密码。
  • 从中央接入点分发密码,这可简化监控和审计。
  • 可集中控制对各台服务器的访问。举例来说,一旦服务器被淘汰,就可以撤销其访问令牌。

请注意,可以访问文件系统的用户或许可以提取用于访问 PDP 的凭证。因此,撤销不再需要的凭证至关重要。

总结

下列几种方法可保护 SSL 私钥免遭泄露,并且安全性和复杂性越来越高:

  • 对于大多数组织,对运行 NGINX 的环境严格限制访问权限,使得未经授权的用户无法获得 root 权限,或查看 NGINX 配置。
  • 而在某些环境中,可能无法完全限制对 NGINX 配置的访问权限,则可以使用 SSL 密码文件。
  • 在少数情况下,组织可能希望永远不将密钥和密码存储在磁盘上。这时,可通过密码分发点流程实现这一点。

本系列中的其他文章介绍了更多 SSL 密钥保护方法:


NGINX 唯一中文官方社区 ,尽在 nginx.org.cn

更多 NGINX 相关的技术干货、互动问答、系列课程、活动资源: