NGINX Full Version

使用 HashiCorp Vault 保护 NGINX 中的 SSL 私钥

本系列博文的第一篇中,我们介绍了几种提高 SSL 私钥安全性的方法,并在结尾处演示了如何使用远程密码分发点 (PDP) 与 NGINX 实例安全地共享加密密码。

HashiCorp Vault 等密钥管理系统的工作方式与 PDP 示例相似:

在本文中,我们将展示如何设置 HashiCorp Vault 以分发 SSL 密钥。要想进一步加强安全性,您可以设置外部硬件安全模块 (HSM)。

如要彻底清除存储在磁盘上的 SSL 证书密钥对,请参阅本系列博文的第三篇——使用 NGINX Plus 键值存储保护来自 HashiCorp Vault 的临时 SSL 密钥。该文介绍了如何从 Hashicorp Vault 生成临时 SSL 密钥,并将其存储在 NGINX Plus 内存键值存储中。

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

 

使用 HashiCorp Vault 保护 SSL 私钥

本节说明部分介绍了如何使用 Vault 设置集中式 PDP 服务器,以分发 SSL 密码。这些设置基于 DigitalOcean 说明;可按需进行修改以符合自己的 Vault 策略。

在我们的示例中,每台远程 Web 服务器都拥有一个唯一身份验证令牌。这些令牌可用于访问 Vault secret/webservers/ 路径中的密钥,我们将 SSL 密码存储在 secret/webservers/ssl_passwords 中。

本文将介绍如何保护令牌,以及在必要时如何撤销个人身份验证令牌。

在 PDP 服务器上下载、安装和初始化 HashiCorp Vault

  1. 按照 Digitalocean 说明在 PDP 服务器上下载并提取 Vault。使用下列 /etc/vault.hcl 样本文件使 Vault 远程可访问,并禁用 TLS(为方便测试时使用):

    backend "file" {
        path = "/var/lib/vault"
    }
    
    listener "tcp" {
        address = "0.0.0.0:8200"
        tls_disable = 1
    }
  2. 使用启动脚本(如已创建)或手动启动 Vault:

    user@pdp:~$ sudo /usr/local/bin/vault server -config=/etc/vault.hcl
  3. 初始化 Vault,并获取初始 root 令牌:

    user@pdp:~$ export VAULT_ADDR=http://localhost:8200
    user@pdp:~$ vault init -key-shares=3 -key-threshold=2
    user@pdp:~$ vault operator unseal
    Initial Root Token: 86c5c2a4-8ab2-24dd-1816-48449c83114e
  4. 我们需要在下列许多命令中都提供初始 root 令牌。为了方便起见,我们将它分配给 root_token shell 变量:

    user@pdp:~$ root_token=86c5c2a4-8ab2-24dd-1816-48449c83114e

存储密钥

  1. 这个过程仍在 PDP 服务器上运行,创建一个名 /tmp/ssl_passwords.txt 的临时文件,并将密码存储其中;

  2. 将此文件作为密钥存储在 Vault 中,并验证能否检索该文件:

    user@pdp:~$ VAULT_TOKEN=$root_token vault kv put secret/webservers/ssl_passwords value=@/tmp/ssl_passwords.txt
    
    user@pdp:~$ VAULT_TOKEN=$root_token vault kv get -field=value secret/webservers/ssl_passwords
    password1
    password2
    ...
  3. 为了安全起见,删除 /tmp/ssl_passwords.txt

  4. 在名为 web.hcl 的文件中创建策略规范,并在该文件中包含以下内容:

    path "secret/webservers/*" {
        capabilities = ["read"]
    }
  5. 将策略加载到 Vault 中,并将其命名为 web:

    user@pdp:~$ VAULT_TOKEN=$root_token vault policy write web web.hcl
  6. 新建身份验证令牌,将其与 web 策略相关联,可以选择设置 display-name 参数以指定一个方便用户记住的名称如 webserver1。将 token 和 token_accessor 值记录下来,以供在后续命令中使用:

    user@pdp:~$ VAULT_TOKEN=$root_token vault token create -policy=web -display-name=webserver1
    Key                  Value
    ---                  -----
    token                dcf75ffd-a245-860f-6960-dc9e834d3385
    token_accessor       0c1d6181-7adf-7b42-27be-b70cfa264048

    NGINX Web 服务器使用此令牌来检索 SSL 密码。该 web 策略阻止 Web 服务器在 secret/webservers/* 路径之外检索密钥。

验证 Web 服务器能否检索令牌

  1. 仍在 NGINX Web 服务器上运行,安装 Vault 二进制文件。
  2. 声明远程 Vault 服务器的位置(在这里是 http://pdp:8200),然后验证 Web 服务器能否使用下列令牌检索 SSL 密码:

    user@web1:~$ export VAULT_ADDR=http://pdp:8200
    user@web1:~$ VAULT_TOKEN=dcf75ffd-a245-860f-6960-dc9e834d3385 vault kv get -field=value secret/webservers/ssl_passwords
    password1
    password2
    ...

在 Web 服务器上配置 NGINX Vault Connector

  1. 第一篇博文中的 PDP 示例设置部分,我们在 NGINX 主机(Web 服务器)上创建了一个名为 connector.sh 的脚本。在这里,我们将其修改以使用 Vault:

    #!/bin/sh
    # Usage: connector_v.sh 
  2. 将脚本作为后台进程运行,调用代码如下所示:

    root@web1:~# ./connector_v.sh /var/run/nginx/ssl_passwords \
    dcf75ffd-a245-860f-6960-dc9e834d3385 &
  3. 通过读取连接器路径,测试连接器:

    root@web1:~$ cat /var/run/nginx/ssl_passwords
    password1
    password2
    ...
  4. 将 NGINX 配置为启动时读取 ssl_passwords 文件,并将文件内容用作解密加密私钥的密码。可通过在 server 代码块(就像在第一篇博文中为 标准配置所创建的)或者 http 上下文中引用 ssl_password_file 指令,将其应用于多台虚拟服务器:

    ssl_password_file /var/run/nginx/ssl_passwords;
  5. 验证 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

撤销 Web 服务器的身份验证令牌

当 Web 服务器遭到攻击或被淘汰时,可轻松撤销访问权限。可直接撤销 Web 服务器所使用的身份验证令牌:

user@pdp:~$ VAULT_TOKEN=$root_token vault token revoke dcf75ffd-a245-860f-6960-dc9e834d3385

Vault 令牌是敏感的数据项,对于发放给经过身份验证的客户端的令牌,许多 Vault 工作流都不存储其副本。如能窃取这些令牌的副本,攻击者就可以冒充客户端。

相反,通常使用令牌访问器来管理活跃令牌,令牌访问器可限制令牌权限,但无法检索令牌值。令牌发放之后,不存储令牌,而是存储其相应的访问器。Token Accessors:创建令牌时,会同时创建并返回令牌访问器。此访问器是一个值,用作对令牌的引用,并且只能用于执行有限的操作。

如需确定 Web 服务器身份验证令牌的访问器,则运行 vault list 命令以检索访问器列表,并在每个访问器上运行 vault token lookup 命令以查找具有相关显示名称和策略的访问器:

user@pdp:~$ VAULT_TOKEN=$root_token vault list /auth/token/accessors
Keys
----
83be5a73-9025-1221-cb70-4b0e8a3ba8df
0c1d6181-7adf-7b42-27be-b70cfa264048
f043b145-7a63-01db-ea85-9f22f413c55e

user@pdp:~$ VAULT_TOKEN=$root_token vault token lookup -accessor 0c1d6181-7adf-7b42-27be-b70cfa264048
Key                 Value
---                 -----
...
display-name        webserver1       
...
policy              web
...

然后,可使用相应访问器撤销令牌:

user@pdp:~$ VAULT_TOKEN=$root_token vault token revoke -accessor 0c1d6181-7adf-7b42-27be-b70cfa264048

安全隐患

Vault 与第一篇博文中所述的 PDP 示例具有类似的安全隐患。只能在获得相应的密码后才能获得 SSL 私钥,因此攻击者需要知道当前身份验证令牌的值。

使用 Vault 的最大优势在于能够自动配置和扩展密钥存储。

 

使用外部 HSM 管理私钥

一旦攻击者获得对 NGINX 服务器的 root 权限,迄今为止,我们在本系列里所介绍的解决方案都无法为私钥提供有效的保护。如果攻击者可以访问 NGINX 的运行时内存或生成核心转储,则可通过已知技术扫描该进程的内存并定位私钥数据。

外部硬件安全模块 (HSM) 通过将 SSL 私钥存储在外部防篡改硬件中来解决此问题。它们提供解密即服务,当 NGINX 需要执行要求提供密钥的 SSL 操作时就会访问该服务。

NGINX 服务器从不查看 SSL 私钥数据。获取服务器 root 权限的攻击者无法获得 SSL 私钥,但可通过使用 NGINX 凭证访问 HSM 解密服务,根据需要解密数据。

将 NGINX 配置为访问 HSM

NGINX 将全部 SSL 私钥操作都委派给一个名为 OpenSSL 的加密库。可使用 HSM 厂商的 OpenSSL 引擎为 NGINX 提供第三方 HSM 设备。

NGINX 配置对于每个厂商 HSM 会有一些差别,但通常遵循一条简单的路径:

如欲查看 HSM 设置示例,请参阅 Amazon CloudHSM 文档

安全隐患

外部 HSM 是一种高度安全的 SSL 私钥存储方法。在使用 HSM 的情况下,拥有 NGINX 服务器 root 权限的攻击者可利用 NGINX 凭证解密任意数据,但无法获取未加密的私钥。HSM 可显著增加攻击者冒充网站或离线解密任意数据的难度。

 

结语

必须确保 SSL 私钥等密钥数据得到充分保护,因为如果这些数据遭到泄露,会造成非常严重的后果。

对于许多拥有适当安全流程的组织来说,只需将私钥存储在前端负载均衡器,然后限制并审计对这些服务器的访问即可(第一篇博文中描述的标准配置)。

对于需要频繁部署 NGINX 配置的组织来说,则可以结合使用本文中所介绍的措施以及上述第一种方法来限制可查看私钥数据的用户或实体。

在本系列博文的第三篇使用 Nginx Plus 键值存储保护来自 Hashicorp Vault 的临时 SSL 密钥中,我们介绍了如何使用 NGINX Plus API 自动将密钥和证书从 Vault 配置到 NGINX Plus 键值存储

再次提醒,即便采用这些方法,也需要全面保护 NGINX 运行实例免遭远程访问或配置操纵。

如欲试用 NGINX Plus,请立即下载 30 天免费试用版,或与我们联系以讨论您的用例