EMQX 节点 Cookie 安全加载:使用 `file://` URL 从文件或命名管道引入 `node.cookie`
EMQX 节点 Cookie 安全加载使用file://URL 从文件或命名管道引入node.cookie【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址: https://gitcode.com/gh_mirrors/em/emqx本篇技术指南介绍 EMQX 集群节点密钥node.cookie的一种新加载方式通过file://URL 形式从普通文件或 FIFO命名管道读取集群密钥从而避免在配置文件或生成文件中以明文持久化敏感信息。读完本文你将掌握node.cookie file:///path/to/cookie与EMQX_NODE__COOKIE环境变量的正确用法、FIFO 场景下编排器的写入时机约束以及该能力在源码与测试中的落地实现能够安全地在容器化、自动化编排环境中部署 EMQX 集群。功能背景为什么需要从文件读取集群密钥在 EMQX 中node.cookie是 Erlang 分布式节点间的共享密钥同一集群内所有节点必须使用相同的 cookie 才能互连。传统做法是直接在配置文件如emqx.conf或EMQX_NODE__COOKIE环境变量中写入明文 cookie这带来两个安全风险配置明文暴露集群密钥以纯文本形式存在于配置文件中任何能读取配置的进程或日志都可能泄露密钥启动产物落盘解析后的 cookie 若被写入启动生成的参数文件会在磁盘上留下永久性的密钥副本扩大攻击面。本次新增的功能对应变更 changes/ee/feat-17768.en.md允许操作者将node.cookie指向一个外部文件或命名管道EMQX 在节点启动时一次性读取密钥同时不再将解析结果写入磁盘上的启动参数文件让集群密钥的存储与分发脱离配置文件本身。使用方法file://URL 形式的两种配置入口从该版本起node.cookie支持file://URL 形式的值。有两种等价的配置入口方式一配置文件node { cookie file:///path/to/cookie }方式二环境变量export EMQX_NODE__COOKIEfile:///path/to/cookie两种方式效果一致EMQX 启动时解析file://指向的路径读取其中的内容作为集群密钥而不是把字面量当作 cookie 本身。路径既可以是普通文件也可以是 FIFO命名管道。EMQX_NODE__COOKIE环境变量可直接写入 apps/emqx_conf/etc/emqx.envEMQX 启动时的环境变量文件该文件在bin/emqx、/usr/bin/emqx每次被调用时都会加载包括服务启动、前台启动以及emqx ctl等维护命令。需要说明的是这些变量在解析emqx.conf之前生效因此不能在emqx.conf中重复设置同名项环境变量的优先级高于配置文件。支持的密钥来源普通文件与 FIFO 命名管道普通文件最常见的方式是将 cookie 写入一个权限受控的普通文件并在配置中引用# 生成并写入 cookie 文件示例注意权限控制 printf %s\n Yzc0NGExM2Rj /etc/emqx/cookie chmod 0400 /etc/emqx/cookie # 然后在配置中引用 # node.cookie file:///etc/emqx/cookieFIFO 命名管道当密钥由外部编排器如 Kubernetes init 容器、Nomad、systemd 等在启动时动态注入时可以使用 FIFO# 编排器在启动前创建 FIFO 并写入 cookie mkfifo /run/emqx/cookie.fifo printf %s\n Yzc0NGExM2Rj /run/emqx/cookie.fifo无论哪种来源文件内容只会在节点启动时被读取一次。这意味着普通文件的后续修改不会影响已运行的节点FIFO 是写一次、读一次的语义EMQX 启动时读取后管道即被排空。FIFO 场景的编排约束写入时机至关重要当使用 FIFO 作为 cookie 来源时编排器必须在每次节点启动时、且在任何其他emqx命令被调用之前将 cookie 写入 FIFO。原因如下只有emqx的启动路径如console、start会读取file://指向的 FIFO后续的维护命令如emqx ctl、emqx eval、emqx stop不会重新读取该文件而是从已运行的节点获取 cookie——通常通过读取节点进程的启动参数如ps输出获得而不是再次访问file://路径。因此一个典型的启动序列应该是# 1. 编排器先写入 cookie 到 FIFO printf %s\n $COOKIE /run/emqx/cookie.fifo # 2. 再启动 EMQX emqx start # 3. 之后可正常使用维护命令它们会从运行中的节点获取 cookie emqx ctl status如果编排器在emqx已启动、FIFO 已被读取后才写入那么下一次重启时由于 FIFO 中已无数据写一次读一次启动读取会阻塞或失败。这也是编写编排脚本时必须保证先写 FIFO、后启动节点的根本原因。安全改进解析后的密钥不再落盘在旧实现中解析出的 cookie 会进入生成的启动参数文件位于数据目录下的data/configs/vm.*.args以明文形式持久化到磁盘。本次改动将解析后的 cookie 通过-setcookie命令行参数直接传给 Erlang VM不再写入生成的vm.*.args文件因此密钥在节点启动过程中不会以任何形式落盘。这一设计在源码中有明确注释说明apps/emqx_conf/src/emqx_conf_schema.erl中node.cookie字段刻意不映射到vm_args.-setcookieintentionally NOT mapped to vm_args.-setcookie而是由bin/emqx通过-setcookie命令行标志在 VM 启动前传入。这样做的另一个原因是file://的解析只能在启动脚本层完成——cookie 在 Erlang VM 启动之前就是必需的而 HOCON/VM 配置层此时还无法介入因此解析必须由bin/emqx承担。对应地单元测试 apps/emqx_conf/test/emqx_conf_schema_tests.erl 中的cookie_not_in_vm_args_test明确断言生成的 VM 参数中不存在-setcookie项即 cookie 不会出现在vm.*.args文件中从测试层面锁定了这一安全行为。源码级实现字段定义、解析与校验配置 Schema 定义node.cookie字段定义于 apps/emqx_conf/src/emqx_conf_schema.erl#L588-L607{cookie, sc( string(), #{ %% NOTE: intentionally NOT mapped to vm_args.-setcookie. %% The cookie is passed to the Erlang VM via the -setcookie %% command-line flag from bin/emqx instead, so the resolved %% secret is never written to the generated vm.time.args file %% on disk. This also lets bin/emqx resolve file:// sources %% (which the VM/HOCON layer cannot do, as the cookie is needed %% before the VM starts). required true, readOnly true, sensitive true, desc ?DESC(node_cookie), importance ?IMPORTANCE_HIGH, converter fun emqx_schema:password_converter/2, validator fun validate_cookie/1 } )},关键点解读sensitive true字段被标记为敏感避免在日志、API 响应等场景中回显明文converter fun emqx_schema:password_converter/2对值做密码类转换处理required true且readOnly truecookie 是必填项且节点启动后不可动态修改集群所有节点必须一致属于节点级不可变配置注释明确了 cookie 的传递路径bin/emqx→-setcookie命令行标志 → Erlang VM绕开vm.*.args文件。校验规则validate_cookie/1及辅助函数实现于 apps/emqx_conf/src/emqx_conf_schema.erl#L2111-L2131规则如下规则说明非空空字符串直接拒绝Cookie must be non-empty string长度上限不超过 255 字节Cookie cannot be more than 255 bytes禁用字符不允许反斜杠\、单引号、双引号、空格源码注释说明Erlang 的erl命令本身没有这类限制但这些字符在 bash 环境下难以安全地传递给ctl、remote等维护命令因此统一禁用这些校验规则在 apps/emqx_conf/test/emqx_conf_schema_tests.erl 中有完整测试覆盖validate_cookie_test_用例组包括空 cookie、超过 255 字节的长 cookie、含反斜杠、单引号、双引号的非法 cookie 等。参考配置示例rel/config/examples/node.conf.example 给出了node段的参考写法node { name emqx127.0.0.1 ## Secret cookie is a random string that should be the same on all nodes in the cluster, but unique per EMQX cluster cookie Yzc0NGExM2Rj ... }将cookie的值替换为file://形式即可启用文件来源。该示例同时提醒node段所有字段在 EMQX 启动后均不可变且集群内所有节点必须保持一致。启动行为与失败模式测试给出的边界保证scripts/test/test_emqx_boot.py 中针对该功能编写了系统级启动测试直接印证了上文描述的各种行为1. 从文件读取成功test_node_cookie_from_fileL162-L181写入带尾随换行的 cookie 文件echo secret file的典型场景通过EMQX_NODE__COOKIEfile://...启动节点正常启动即证明erl -setcookie拿到了解析后的值测试同时说明尾随换行会被正确去除无需手动 trim。2. 文件缺失时快速失败test_node_cookie_from_missing_file_fails_fastL184-L192引用的文件不存在时EMQX 在启动前即报错退出错误信息包含does not exist不会带着错误的 cookie 半启动。3. 空文件快速失败test_node_cookie_from_empty_file_fails_fastL195-L205空文件同样在启动前失败错误信息包含is empty避免产生空密钥。4. FIFO 只读取一次test_node_cookie_from_fifo_read_onceL208-L267该测试复现了最关键的边界条件emqx start会通过run_erl重新执行console如果启动链路中对 FIFO 读取两次第二次读取会因没有写入者而永久阻塞节点将永远无法启动。测试用一个单次写入的 writer 进程验证了 FIFO 恰好被排空一次随后不带file://覆盖的环境执行emqx eval erlang:get_cookie().验证维护命令是从已运行的节点获得 cookie而不是重新读取文件。5. 维护命令不需要file://覆盖同上测试中start使用带file://的环境而后续eval、stop都使用普通环境plain_env印证了原文档中的约束只有启动路径读取文件维护命令从运行中节点获取密钥。落地建议与注意事项结合原变更说明与上述实现在实际部署中请注意以下几点文件权限无论普通文件还是 FIFO都应限制为仅 EMQX 运行用户可读例如chmod 0400避免密钥被其他进程读取。FIFO 的写入时序编排器必须先写 FIFO、后启动 EMQX且每次重启都要重新写入维护命令调用顺序上不存在该约束它们不依赖file://。单次读取语义文件内容只在启动时读取一次。若需轮换 cookie必须重启节点并从新的文件内容重新加载。校验约束cookie 文件内容去除尾随换行后须满足非空、≤255 字节、不含\、、、空格等规则否则启动会被校验器拦截。适用前提file://解析由启动脚本bin/emqx完成因此该能力适用于所有通过bin/emqx或发行包中的/usr/bin/emqx启动的部署形态它绕过了 VM/HOCON 配置层属于启动脚本与 VM 之间约定的行为。与安全配置的配合EMQX 的安全配置hardened 等会拒绝使用众所周知的弱 cookie如emqxsecretcookie从文件读取一个强随机密钥并与高权限管理配合才能构成完整的密钥安全方案。通过file://形式加载node.cookieEMQX 让集群密钥的存储与分发彻底脱离配置文件与磁盘启动产物同时保持了与既有环境变量、配置文件两种入口的兼容性是容器化、密钥托管如 K8s Secret 注入、Vault 挂载场景下的推荐做法。【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址: https://gitcode.com/gh_mirrors/em/emqx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考