面向物联网的 NGINX Plus:对 MQTT 流量进行加密和身份验证
原文作者:Liam Crilly - F5 产品管理高级总监
原文链接:面向物联网的 NGINX Plus:对 MQTT 流量进行加密和身份验证
转载来源:NGINX 中文官网
NGINX 唯一中文官方社区 ,尽在 nginx.org.cn
在关于 NGINX Plus 和物联网(IoT)的系列博文(共两篇)的第一篇中,我们介绍了 NGINX Plus 作为一款支持 TCP 和 UDP 应用的全功能应用交付控制器(ADC),如何提高物联网应用的可用性和可靠性。在第二篇(本文)中,我们将探讨使用 NGINX Plus 来提高物联网安全性的两种方法。
这两篇文章涉及以下用例:
-
对 MQTT 流量进行加密和身份验证(本文)
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 卸载。
server { listen 8883 ssl; # MQTT 安全端口 preread_buffer_size 1k; js_preread getClientId; ssl_certificate /etc/nginx/certs/my_cert.crt; ssl_certificate_key /etc/nginx/certs/my_cert.key; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:128m; # 128MB 约等于 50 万个会话 ssl_session_tickets on; ssl_session_timeout 8h; proxy_pass hive_mq; proxy_connect_timeout 1s; access_log /var/log/nginx/mqtt_access.log mqtt; error_log /var/log/nginx/mqtt_error.log info; # NGINX JavaScript 调试日志记录 }
除了第 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.pem Client 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 on; # 客户端必须提供证书 ssl_verify_depth 2; # 如果存在中间证书颁发机构 ssl_client_certificate /etc/nginx/certs/cafile.pem; # 客户端证书的颁发机构
ssl_verify_client 指令指示 NGINX 客户端必须出示证书。在本例中,客户端证书由中间证书颁发机构颁发,因此我们使用了 ssl_verify_depth 指令来指示 NGINX 验证两级颁发机构证书。ssl_client_certificate 指令指定了向客户端颁发证书的证书颁发机构(CA)的公共证书在磁盘上的位置;NGINX 在客户端身份验证过程中使用公共 CA 证书。
用于 MQTT 客户端身份验证的 JavaScript 代码
最后,我们扩展了 NGINX JavaScript 代码(mqtt.js),该代码是我们在上一篇文章中专为讨论会话保持用例而创建的。新增代码可验证 CONNECT
数据包中显示的 MQTT ClientId 是否与发送到同一客户端的证书中的 CN 具有相同的值。
1 function parseCSKVpairs(cskvpairs, key) { 2 if ( cskvpairs.length ) { 3 var kvpairs = cskvpairs.split(','); 4 for ( var i = 0; i < kvpairs.length; i++ ) { 5 var kvpair = kvpairs[i].split('='); 6 if ( kvpair[0].toUpperCase() == key ) { 7 return kvpair[1]; 8 } 9 } 10 } 11 return ""; // Default condition 12 }
我们添加了 parseCSKVpairs
函数以从 X.509 证书中提取 CN 值。它由另一个函数(getClientId
函数)调用,因此在文件中必须位于后者的前面。
14 var client_messages = 1; 15 var client_id_str = "-"; 16 17 function getClientId(s) { 18 s.on('upload', function (data, flags) { 19 if ( data.length == 0 ) { // Initial calls may contain no data, so 20 s.log("No buffer yet"); // ask that we get called again 21 //s.done(1); // (supposing that code=1 means that) 22 return; 23 } else if ( client_messages == 1 ) { // Connect is first packet from the client 24 // Connect packet is 1, using upper 4 bits (00010000 to 00011111) 25 var packet_type_flags_byte = data.charCodeAt(0); 26 s.log("MQTT packet type+flags = " + packet_type_flags_byte.toString()); 27 if ( packet_type_flags_byte >= 16 && packet_type_flags_byte < 32 ) { 28 // Calculate remaining length with variable encoding scheme 29 var multiplier = 1; 30 var remaining_len_val = 0; 31 var remaining_len_byte; 32 for (var remaining_len_pos = 1; remaining_len_pos < 5; remaining_len_pos++ ) { 33 remaining_len_byte = data.charCodeAt(remaining_len_pos); 34 if ( remaining_len_byte == 0 ) break; // Stop decoding on 0 35 remaining_len_val += (remaining_len_byte & 127) * multiplier; 36 multiplier *= 128; 37 } 38 39 // Extract ClientId based on length defined by 2-byte encoding 40 var payload_offset = remaining_len_pos + 12; // Skip fixed header 41 var client_id_len_msb = data.charCodeAt(payload_offset).toString(16); 42 var client_id_len_lsb = data.charCodeAt(payload_offset + 1).toString(16); 43 if ( client_id_len_lsb.length < 2 ) client_id_len_lsb = "0" + client_id_len_lsb; 44 var client_id_len_int = parseInt(client_id_len_msb + client_id_len_lsb, 16); 45 client_id_str = data.substr(payload_offset + 2, client_id_len_int); 46 s.log("ClientId value = " + client_id_str);
第 14 到 45 行中的 getClientId
函数与我们专为会话保持用例而创建的 mqtt.js 文件中的第 1 到 32 行相同。
47 // If client authentication then check certificate CN matches ClientId 48 var client_cert_cn = parseCSKVpairs(s.variables.ssl_client_s_dn, "CN"); 49 if ( client_cert_cn.length && client_cert_cn != client_id_str ) { 50 s.log("Client certificate common name (" + client_cert_cn + ") does not match client ID"); 51 return s.ERROR; // Close the TCP connection (logged as 500) 52 } 53 } else { 54 s.log("Received unexpected MQTT packet type+flags: " + packet_type_flags_byte.toString()); 55 } 56 } 57 client_messages++; 58 } 59 return s.OK; 60 } 61 62 function setClientId(s) { 63 return client_id_str; 64 }
第 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.crt subject= /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.key Client 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.key Client 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 开源版、NGINX Plus、NGINX JavaScript、IoT 或其他方面的用例。
NGINX 唯一中文官方社区 ,尽在 nginx.org.cn

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Ubuntu 开发商 Canonical 2023 年收入达 2.51 亿美元
Ubuntu 开发商 Canonical 最近向英国 Companies House 提交了财务报表。 Canonical 称 2023 年的收入为 2.51 亿美元,较 2022 年的 2.05 亿美元大幅增长(同比增长 22%)。不过其毛利率略有下降,从 82% 降至 80%。净利润为 1250 万美元,比上一年的 390 万美元大幅增长 220%。 现金流方面,Canonical 称 2023 年该数据为 4700 万美元,而上一年为 6200 万美元。 此外,2023 年 Canonical 的平均员工人数为 1,034 人,而前一年为 858 人。
- 下一篇
BI 工具助力企业解锁数字化工厂,开启工业智能新视界
背景 在 2022 年公布的《"十四五"数字经济发展规划》中,政府不断增加对制造业数字化转型的政策支持力度,积极倡导制造企业采用最新技术,提升自动化、数字化和智能化水平。这一举措旨在强化国际竞争力,推动制造业由制造大国向制造强国转变。 然而,在制造业数字化和智能化升级的过程中,涉及以下一系列挑战和难题: 为了解决这些痛点问题,更好地实现制造业数字化升级,葡萄城的嵌入式 BI 工具 Wyn 商业智能提供了全面的解决方案,旨在提升企业的数据分析能力和运营效率,解决方案包括以下几个部分: 数据接入: 通过MQTT、websocket、http采集协议集成来自物联网的硬件数据; 支持多种类型的业务系统数据库,来整合生产、供应链、客户及销售等多个环节的业务数据; 通过Web JSON接口连接至已处理的数据仓库,灵活打通业务系统数据。 数据准备: 提供多样化的建模方式,包括直连模型、跨源抽取模型以及多种数据集类型(如直连、推送和流式数据集); 支持配置基于用户、组织上下文的行级、列级和文档级权限,确保数据的安全性; 通过可视化UI建模快速将原始业务数据转换成适用于可视化分析和展示的业务模型。 可视...
相关文章
文章评论
共有0条评论来说两句吧...