ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

EMQX 从 TLS 客户端证书提取 Subject Alternative Name(SAN)到 MQTT 客户端属性实战指南

EMQX 从 TLS 客户端证书提取 Subject Alternative Name(SAN)到 MQTT 客户端属性实战指南 EMQX 从 TLS 客户端证书提取 Subject Alternative NameSAN到 MQTT 客户端属性实战指南【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址: https://gitcode.com/gh_mirrors/em/emqx本文介绍 EMQX 5.x/6.x 中一项安全增强能力将直连directly connectedTLS 客户端证书中的 Subject Alternative NameSAN字段提取出来通过mqtt.client_attrs_init配置写入 MQTT 客户端属性Client Attributes并以cert_san.dns、cert_san.ip、cert_san.email、cert_san.uri四种类型在属性初始化表达式中使用。读完本文你将掌握如何在 SSL 监听器上开启双向 TLS 校验、配置客户端属性初始化表达式并理解其底层渲染上下文与安全校验逻辑为基于证书的身份识别、租户隔离与访问控制提供数据基础。功能概述证书 SAN 成为客户端属性的新数据源该功能来源于变更记录 changes/ee/feat-17603.en.md其核心内容是支持从直连 TLS 客户端证书中提取 Subject Alternative Names 到 MQTT 客户端属性可在mqtt.client_attrs_init中通过cert_san.dns、cert_san.ip、cert_san.email和cert_san.uri引用。在此之前EMQX 的client_attrs_init渲染上下文已支持从客户端证书中提取cert_common_name证书通用名 CN和cert_subject证书主题 DN。本次变更将数据源扩展到了 SAN 扩展字段使客户端属性可以承载证书中的 DNS 名称、IP 地址、Email 地址和 URI如 SPIFFE ID从而支持更细粒度的身份标识。适用前提该功能只对“直连”的 TLS 客户端生效即客户端直接通过 SSL 监听器连接到 EMQX而非经集群链接/网关代理转发。客户端必须携带证书且监听器配置为校验对端证书verify_peerfail_if_no_peer_cert否则连接层拿不到对端证书SAN 渲染上下文为空。第一步开启双向 TLSmTLS监听器SAN 提取的前提是 EMQX 能拿到客户端证书。需要在 SSL 监听器上启用对端证书校验参考测试用例 emqx_client_cert_san_SUITE.erl 中的监听器配置listeners.ssl.default { bind 0.0.0.0:8883 ssl_options { cacertfile etc/certs/cacert.pem certfile etc/certs/server-cert.pem keyfile etc/certs/server-key.pem verify verify_peer fail_if_no_peer_cert true } }关键点说明配置项取值作用verifyverify_peer校验客户端证书链必须由cacertfile指定的 CA 签发fail_if_no_peer_certtrue客户端未提供证书时直接拒绝握手保证每个连接都有证书可供提取cacertfileCA 证书路径用于验证客户端证书签发的信任锚从测试用例可以看到客户端侧也需要携带由同一 CA 签发的证书certfilekeyfile并在握手时通过verify_fun允许任意主机名避免 SAN 主机名校验干扰测试。第二步配置mqtt.client_attrs_init提取 SAN客户端属性初始化由 MQTT 配置项client_attrs_init列表类型驱动每一项包含两个字段其 schema 定义见 emqx_schema.erl字段类型说明expression字符串emqx_variform 模板表达式支持引用渲染上下文中的变量set_as_attr字符串受限字符集渲染结果要写入的客户端属性名需通过restricted_string校验一个典型的配置示例mqtt { client_attrs_init [ { expression nth(1, cert_san.dns) set_as_attr san_dns }, { expression join_to_string(,, cert_san.dns) set_as_attr san_dns_all }, { expression nth(1, cert_san.ip) set_as_attr san_ip }, { expression nth(1, cert_san.email) set_as_attr san_email }, { expression nth(1, cert_san.uri) set_as_attr san_uri } ] }四种 SAN 变量详解在client_attrs_init表达式中SAN 通过cert_san.type引用对应证书 X.509 SAN 扩展中的不同类型变量对应 SAN 类型示例值说明cert_san.dnsdNSNameexample.comDNS 域名一个证书可含多个表现为列表cert_san.ipiPAddress192.168.1.100、2001:db8::1IP 地址含 IPv4/IPv6列表cert_san.emailrfc822Nameopsexample.comEmail 地址列表cert_san.uriuniformResourceIdentifierspiffe://example.com/clientURI如 SPIFFE 身份标识列表多值行为SAN 的每种类型都是列表。渲染上下文的默认结构为#{dns [], ip [], email [], uri []}见 emqx_channel.erl即当证书无 SAN 时各变量为空列表。配合 emqx_variform 内置函数可灵活取值nth(1, cert_san.dns)取第一个 DNS 值join_to_string(,, cert_san.dns)将全部 DNS 值拼接为逗号分隔的字符串。渲染上下文优先级client_attrs_init表达式可引用的完整渲染上下文构建于 emqx_channel.erl包括客户端信息ClientInfoclientid、username等与连接包属性user_propertyCONNECT 报文用户属性、password连接密码证书主题数据cert_common_nameCN、cert_subjectDN本次新增cert_sanSAN 四种类型。最终通过emqx_variform:render/2渲染表达式initialize_client_attrs渲染结果写入client_attrs与客户端自带属性合并后随ClientInfo下发到认证、授权、规则引擎等后续环节。源码级原理SAN 提取的调用链从连接建立到属性写入完整调用链如下实现位于 apps/emqx/src/emqx_channel.erlmaybe_set_client_initial_attrs/3L2439-L2453在 CONNECT 处理阶段读取get_client_attrs_init_config(Zone)对应的mqtt.client_attrs_init配置若为空则直接跳过否则构造渲染上下文并逐条执行属性初始化client_attrs_init_render_ctx/2L2458-L2459先调用add_cert_san_render_ctx/2再依次补充cert_common_name与cert_subjectadd_cert_san_render_ctx/2L2471-L2475从ConnInfo的peercert字段取出对端证书调用esockd_peercert:subject_alt_names/1解析出 SAN 映射经validate_cert_san/2校验后注入上下文cert_san键cert_san_render_ctx/1L2492-L2498无 SAN 时返回#{dns [], ip [], email [], uri []}有 SAN 时与空结构合并保证四种键始终存在initialize_client_attrs/2L2500 起对每个#{expression, set_as_attr}条目渲染表达式。渲染结果为空字符串时记录 debug 日志并跳过不写入属性结果非 MQTT 安全 UTF-8 时丢弃并告警。注意cert_san本身只存在于渲染上下文中不会被写入ClientInfo的client_attrs之外的字段测试用例也断言了client_attrs中不含cert_san键L100。安全校验拒绝证书字段中的控制字符SAN 来自客户端可控的证书恶意客户端可能构造含 CRLF 等控制字符的 SAN 值。若这些字节未经校验流入 HTTP 认证/授权或 Bridge 请求可能造成 CRLF 注入。为此 emqx_channel.erl 在提取阶段对 SAN 各值执行validate_peercert_string/3校验规则与 MQTT 报文帧的emqx_frame:validate_utf8/1一致的字节类检查拒绝 0x00–0x1F 与 0x7F–0x9F 控制字符失败行为记录peer_certificate_field_rejected告警日志含字段与来源并以{shutdown, peercert_field_invalid}终止连接适用范围同一校验同时保护${cert_common_name}、${cert_subject}与${cert_san.*}三类证书来源字段。测试用例t_invalid_cert_san_rejectedL106-L137构造了tenant-a\r\nX-Override-Result: allow这样的恶意 DNS SAN验证此类连接会被拒绝杜绝了经证书字段注入 HTTP 头部的攻击路径。测试验证与预期行为仓库提供了专门的测试套件 apps/emqx/test/emqx_client_cert_san_SUITE.erl覆盖三类关键场景可作为你验证配置行为的参照t_cert_san_as_client_attrsL46-L104证书同时含 2 个 DNS、2 个 IPIPv4IPv6、1 个 Email、1 个 URI通过nth(1, ...)与join_to_string(,, ...)提取后断言最终client_attrs为san_dnsexample.comsan_dns_allexample.com,www.example.comsan_ip192.168.1.100san_ipv62001:db8::1san_emailopsexample.comsan_urispiffe://example.com/clientt_invalid_cert_san_rejected含控制字符的 SAN 导致连接被拒t_empty_cert_san_do_not_set_attrL139-L162证书无 SAN 时nth(1, cert_san.dns)渲染结果为空属性不写入client_attrs保持空映射。此外client_attrs_init生成的属性可被规则引擎消费在 emqx_rule_engine_SUITE.erl 中通过SELECT client_attrs as payload FROM t/1可将客户端属性作为消息载荷路由这意味着证书 SAN 提取的属性同样可用于规则 SQL 的匹配与重发布场景。典型应用场景基于证书的租户隔离为不同租户签发含专属 DNS SAN 的客户端证书通过set_as_attr tenant 规则引擎匹配属性实现消息路由隔离SPIFFE 工作负载身份cert_san.uri支持spiffe://URI可在零信任架构中把 SPIFFE ID 写入客户端属性供授权策略使用设备指纹与审计将证书 IP/Email 落入客户端属性便于在 Dashboard、WebHook 审计与 Bridge 转发中携带设备身份替代peer_cert_as_username的更灵活方案相比直接把 CN 作为用户名SAN 属性支持多值提取与拼接表达能力更强。注意事项与边界该功能仅适用于直连的 TLS 客户端非 TLS 连接nossl或未开启对端证书校验时SAN 上下文为空列表表达式渲染为空字符串对应属性不会写入set_as_attr属性名需符合restricted_string校验见 emqx_schema.erl非法字符会导致配置校验失败SAN 值必须是 MQTT 安全 UTF-8含控制字符的值会导致连接终止而非仅丢弃该属性部署前建议对证书签发流程做字符约束属性在 CONNECT 阶段初始化并合并到client_attrs后续认证/授权环节如emqx_auth的 ACL 匹配可直接引用这些属性。通过以上配置与源码分析你可以安全、精准地把 TLS 客户端证书中的身份信息转化为可被 EMQX 全链路消费的客户端属性为证书驱动的安全策略奠定数据基础。【免费下载链接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles项目地址: https://gitcode.com/gh_mirrors/em/emqx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表