BLOG | NGINX

面向物联网的 NGINX Plus:对 MQTT 流量进行加密和身份验证

NGINX-Part-of-F5-horiz-black-type-RGB
Liam Crilly 缩略图
Liam Crilly
Published March 29, 2017

在关于 NGINX Plus 和物联网(IoT)的系列博文(共两篇)的第一篇中,我们介绍了 NGINX Plus 作为一款支持 TCP 和 UDP 应用的全功能应用交付控制器(ADC),如何提高物联网应用的可用性和可靠性。在第二篇(本文)中,我们将探讨使用 NGINX Plus 来提高物联网安全性的两种方法。

这两篇文章涉及以下用例:

 

MQTT 服务器的 TLS 卸载

第一篇文章中的所有用例示例中,所有 MQTT 流量都是明文且未加密。只要物联网设备或物联网网关支持 TLS 加密,提升物联网安全性的最佳方法就是使用 TLS 加密客户端和上游服务器之间传输的 MQTT 数据。加密可在数据穿过公共网络时提供保护,但在拥有数百万台设备的生产环境中,加密会给 MQTT 服务器带来巨大负担。

如图 1 所示,NGINX Plus 可从 MQTT 服务器上卸载与 TLS 加密相关的 CPU 密集型工作负载(通常被称为 SSL 卸载)。这一关注点分离做法允许负载均衡层和 MQTT 数据处理层独立扩展,而且只需对 MQTT 测试环境进行简单修改。

(与在第一篇文章中一样,我们使用 Mosquitto 命令行工具作为客户端,并使用在 Docker 容器内运行的 HiveMQ 作为 MQTT 代理。 有关安装说明,请参阅第一篇文章中的“创建测试环境”一节。)

图 1.NGINX Plus 为 MQTT 客户端执行 TLS 卸载

为 NGINX Plus 实例提供 TLS 证书密钥对后,我们可以使用来自流式 SSL 模块的指令来启用 TLS 卸载。

除了第 2 行指定了安全 MQTT 流量的标准端口号为 8883,以及添加了第 6 到 11 行以配置服务器来终止客户端的 TLS 连接以外,该 server 代码块与上一篇博文中的会话保持配置完全相同。如何获取和安装证书不在本文讨论范围之内,因为生产物联网部署通常使用自己的公钥基础设施(PKI)。本文也不会详述与 TLS 相关的指令。有关 TLS 卸载的更多信息,请参阅《NGINX Plus 管理指南》。

完成此配置后,我们就可以使用 Mosquitto MQTT 客户端发布加密信息。请注意,我们指定了安全 MQTT 端口(8883)和一个包含证书颁发机构公钥的文件——证书颁发机构在我们的 NGINX 实例 (cafile.pem)上颁发服务器证书。

$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 8883 --cafile cafile.pemClient thing001 sending CONNECT
Client thing001 received CONNACK
Client thing001 sending PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes))
Client thing001 sending DISCONNECT
$ tail --lines=1 /var/log/nginx/mqtt_access.log
192.168.91.1 [23/Feb/2017:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18832 thing001

 

使用客户端证书对 MQTT 客户端进行身份验证

[编者按 – NGINX JavaScript 模块的用例有很多,以下用例只是其中之一。如欲获取完整列表,请访问 NGINX JavaScript 模块的用例本文已更新,为 NGINX JavaScript 0.2.4 中引入的 Stream 模块使用了重构的会话对象。]

尽管 MQTT 在物联网用例中大获成功并被广泛采用,但该协议本身在验证客户端身份方面的功能非常有限。我们通过在 MQTT CONNECT 数据包中使用用户名和密码字段来支持身份验证,但实际上这很难管理。

虽然 MQTT 规范并不正式支持 X.509 客户端证书,但 X.509 客户端证书常常被用于客户端身份验证,而且与 TLS 加密结合使用时尤其有用,支持双向身份验证

  • 客户端验证服务器身份
  • 服务器验证客户端身份

NGINX Plus 可以组合使用 TLS 卸载与客户端证书身份验证,在这种情况下,MQTT 客户端就必须提供证书,而且证书通用名(CN)必须与 MQTT ClientId 匹配。通过将 ClientId 链接至 X.509 证书,MQTT 服务器可确保接收到的消息来自真实可信的设备。

图 2.提供 X.509 证书和私钥进行双向身份验证

用于 MQTT 客户端身份验证的 NGINX Plus 配置

针对此用例,我们扩展了上一节中的 NGINX Plus 配置(以启用客户端证书身份验证)和上一篇文章中的 NGINX JavaScript 代码(以匹配证书 CN 与 ClientId)。在 server 代码块中添加以下配置片段即可启用客户端证书身份验证。

ssl_verify_client 指令指示 NGINX 客户端必须出示证书。在本例中,客户端证书由中间证书颁发机构颁发,因此我们使用了 ssl_verify_depth 指令来指示 NGINX 验证两级颁发机构证书。ssl_client_certificate 指令指定了向客户端颁发证书的证书颁发机构(CA)的公共证书在磁盘上的位置;NGINX 在客户端身份验证过程中使用公共 CA 证书。

用于 MQTT 客户端身份验证的 JavaScript 代码

最后,我们扩展了 NGINX JavaScript 代码(mqtt.js),该代码是我们在上一篇文章中专为讨论会话保持用例而创建的。新增代码可验证 CONNECT 数据包中显示的 MQTT ClientId 是否与发送到同一客户端的证书中的 CN 具有相同的值。

我们添加了 parseCSKVpairs 函数以从 X.509 证书中提取 CN 值。它由另一个函数(getClientId
函数)调用,因此在文件中必须位于后者的前面。

第 14 到 45 行中的 getClientId 函数与我们专为会话保持用例而创建的 mqtt.js 文件中的第 1 到 32 行相同。

第 48 到 49 行涉及 ClientId 与证书 CN 的匹配。CN 本身包含在证书的 subject distinguished name 中,其值可从 $ssl_client_s_dn 变量中获取。 NGINX JavaScript 可通过 s.variables 对象访问所有 NGINX 变量。该变量包含大量属性,显示为用逗号分隔的键值对列表。

最后几行(53 到 64 行)与用于会话保持用例的 mqtt.js 文件中的第 34 到 45 行相同。

测试 MQTT 客户端身份验证

完成此配置后,我们即可使用 Mosquitto 客户端向测试环境发送经过身份验证和加密的消息。我们可通过运行此 openssl x509(1) 命令检查发送给 thing001 的证书密钥对。

$ openssl x509 -subject -noout < thing0001.crtsubject= /C=GB/L=Cambridge/O=example.com/OU=Example CA/CN=thing001

现在,我们可以将此证书密钥对提供给测试环境。

$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 8883 --cafile cafile.pem --cert thing0001.crt --key thing0001.keyClient thing001 sending CONNECT
Client thing001 received CONNACK
Client thing001 sending PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes))
Client thing001 sending DISCONNECT
$ tail --lines=1 /var/log/nginx/mqtt_access.log
192.168.91.1 [24/Feb/2017:14:37:08 +0000] TCP 200 23 4 127.0.0.1:18832 thing001

如果试图建立连接的客户端提供的 ClientId 与我们的证书不匹配,或者根本无法提供证书,NGINX Plus 将立即终止连接,这样未经身份验证的消息永远都不会到达上游 MQTT 服务器。因此,NGINX Plus 能够为 MQTT 服务器提供额外的保护,拒绝来自恶意或错误客户端的连接。

$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "BADTHING" -p 8883 --cafile cafile.pem --cert thing0001.crt --key thing0001.keyClient BADTHING sending CONNECT
Error: The connection was lost.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "NOCERT" -p 8883 --cafile cafile.pem
Client NOCERT sending CONNECT
Error: The connection was lost.
$ tail --lines=2 /var/log/nginx/mqtt_access.log
192.168.91.1 [24/Feb/2017:14:37:16 +0000] TCP 500 0 0 - BADTHING
192.168.91.1 [24/Feb/2017:14:42:16 +0000] TCP 500 0 0 - -

 

结语

使用 NGINX Plus 从 MQTT 服务器卸载加密和身份验证工作负载,可提升物联网安全性,并改善物联网部署的整体性能和流量容量。欢迎大家在下方评论区与我们分享有关 NGINX Plus、NGINX JavaScript、IoT 或其他方面的用例。

如欲试用 NGINX JavaScript 和 NGINX Plus,请立即申请 30 天免费试用联系我们讨论您的物联网项目。


"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."