ARTICLE DETAIL

资讯详情

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

EMQX MQTT ACL 发布订阅权限配置与排障实战

EMQX MQTT ACL 发布订阅权限配置与排障实战 线上出过一次挺典型的故障一台测试设备拿着完全合法的账号密码把生产线的遥测主题整条订走了监控大屏刷了一大屏不该它看的数据。账号体系没问题密码也没泄露问题卡在 MQTT 的 ACL 上——EMQX 装完之后默认是认证过了就全放行谁拿到账号谁就能订阅全量主题。这件事之后我把整套 MQTT——EMQX 学习笔记重新梳理了一遍今天单独把 ACL发布与订阅权限这块拿出来讲透。这篇内容偏实操适合已经在用 EMQX 但还没真正管过权限的物联网开发、后端运维以及正在做设备接入平台的同学刚接触 MQTT 的新手也能看懂因为我会把每条规则拆到字段级别。1. 认证管你是谁ACL 管你能干什么1.1 一次典型事故引出的权限盲区很多人搭 MQTT 服务器的路径是装 EMQX → 建个用户名密码 → 客户端连上 → 能收发消息 → 上线。整条链路里压根没出现过授权这两个字。EMQX 的默认行为是只要客户端通过了认证或者压根没开认证它对所有主题的发布和订阅请求都会被允许。这在实验室环境里很方便一旦进入多租户或者多设备的真实场景就是个裸奔状态。我遇到的那次事故根本原因就是所有设备共用一个账号而 ACL 没配。设备 A 本该只发plant/line1/A001/telemetry结果它订了plant/#把三条产线的数据全收走了。这里要先把两个概念分清楚。认证Authentication解决你是谁靠的是用户名密码、客户端证书、JWT 这一类凭据授权Authorization也就是 ACL解决你能干什么靠的是规则表判断某个客户端能不能对某个主题执行发布或订阅。这两件事在 EMQX 里是两套独立配置各管一摊缺一不可。只做认证不做授权等于给每个员工发了工牌但办公室所有门都是敞开的。1.2 ACL 的三要素主体、动作、主题不管你在哪个 MQTT 服务器上配 ACL规则的本质都是同一个三段式判断谁主体对哪个主题资源执行什么动作发布/订阅结果是允许还是拒绝。主体可以是用户名也可以是客户端 ID还可以是来源 IP或者干脆写所有人。动作只有三种publish、subscribe、all。主题则是用 MQTT 主题过滤器表达的一段字符串支持和#通配符也支持基于用户名、客户端 ID 的占位符还能做精确匹配和正则匹配。这三要素的排列组合基本能覆盖设备接入侧的绝大多数需求。举几个真实场景传感器只允许发布、不允许订阅那就写该设备 → 自己的主题 → publish 允许其余拒绝网关需要订阅一堆设备的数据、再往上游转发那就给它subscribe的gateway//data加上publish的cloud/upstream运维工具要读$SYS/#看服务器状态那就单独给它开$SYS的订阅权限其他设备一律禁止碰$SYS。想清楚三要素规则基本就写出来了。1.3 EMQX 里授权规则能挂在哪儿EMQX 5.x 把授权做成了插件化的数据源结构你可以挂一个也可以按优先级挂一串。常见的有这么几类内置数据库规则存在 EMQX 自己的存储里通过 Dashboard 的访问控制 → 授权页面增删改改完立即生效不用重启。文件acl.conf规则写在一个 Erlang 项格式的配置文件里改动需要重载或重启适合策略固定、数量不多的场景。外部数据源HTTP 接口、MySQL、PostgreSQL、MongoDB、Redis 等。规则量大的时候把权限表放进现成的数据库用 SQL 或 Redis 命令查运维体系能统一。客户端信息某些版本支持直接从连接信息里取字段做判断。多数据源同时开启时EMQX 会按配置顺序依次查询任意一个数据源返回了明确的 allow 或 deny 就立即终止后面的不再查全部数据源都返回不匹配时才落到no_match这条兜底策略上。这个顺序逻辑很关键后面排障那一节会反复用到。2. 把 ACL 规则拆到字段级语法、通配符与匹配顺序2.1 一条规则由四个位置参数组成先看acl.conf文件版的写法因为它最直白把规则结构暴露得最清楚。EMQX 5.x 的默认文件长这样%% 允许用户名为 dashboard 的用户订阅 $SYS 开头的主题 {allow, {username, {re, ^dashboard$}}, subscribe, [$SYS/#]}. %% 允许来自本机的客户端发布和订阅所有主题 {allow, {ipaddr, 127.0.0.1}, all, [$SYS/#, #]}. %% 拒绝所有人订阅 $SYS 主题以及用 # 做的全量订阅 {deny, all, subscribe, [$SYS/#, {eq, #}]}. %% 其余请求一律允许 {allow, all}.一条规则是四个位置参数顺序不能乱权限allow或deny。主体all表示所有人{username, xxx}按用户名匹配{clientid, xxx}按客户端 ID 匹配{ipaddr, 192.168.1.0/24}按来源网段匹配想用正则就再包一层写成{username, {re, ^sensor-\\d$}}。动作publish、subscribe、all三选一。主题列表一个列表里面每一项都是一个主题过滤器命中任意一项就算命中这条规则。列表里还能塞特殊形式{eq, xxx}表示精确匹配不做通配符解释{re, xxx}表示正则匹配。有个版本差异必须提醒EMQX 4.x 里主体关键字写的是{user, xxx}5.x 改成了{username, xxx}。网上很多老教程还在用user你直接抄到 5.x 上会发现规则死活不生效。写之前先确认自己跑的是哪个大版本别在这上面浪费一晚上。2.2 主题里的通配符、占位符与精确匹配主题这一栏是最容易写错的地方坑集中在这里。先说通配符。MQTT 的匹配单层#匹配多层且必须在末尾。ACL 规则里的主题过滤器遵循同样的规则所以sensor//temp能覆盖sensor/A001/temp和sensor/B002/temp但覆盖不了sensor/A001/room1/temp。这里有个经典的认知偏差ACL 判断的不是客户端订阅的那个字符串而是这个订阅会触及哪些主题。客户端发一个sensor/#的订阅请求服务器要判断的是这条订阅一旦建立它能收到哪些主题的消息然后拿这些主题去匹配规则。所以如果你只给某用户开了sensor//temp的订阅权限他去订sensor/#很可能被拒因为#的覆盖面远超授权范围。再说占位符这是让规则活起来的关键。文件版里用%u代表用户名%c代表客户端 ID。比如%% 每个用户只能订阅自己名下的一层主题 {allow, {username, {re, ^dev-}}, subscribe, [devices/%u/#]}. %% 每个客户端只能发布自己 ID 对应的主题 {allow, {clientid, {re, ^dev-}}, publish, [devices/%c/up]}.Dashboard 内置数据库那一侧占位符的写法不一样用的是${username}和${clientid}devices/${username}/# devices/${clientid}/up这个差异我踩过从文档里抄%u到 Dashboard 输入框里保存成功、看着也对就是不生效因为内置数据库不认这个语法。两套语法各管各的记住这个对应关系能省不少事。至于{eq, xxx}它的作用是关掉通配符解释。默认情况下规则里的#是通配符但如果你想表达就是字面意义上的#这个主题就得写成{eq, #}。EMQX 默认配置里那条{deny, all, subscribe, [$SYS/#, {eq, #}]}就是这个意思拒绝所有对$SYS开头的主题的订阅同时专门拒绝用#来全量订阅这个行为——因为全量订阅等于把整个 broker 的消息都捞走属于典型的高危操作。2.3 匹配顺序、缓存与 no_match 的兜底逻辑规则在文件里是按顺序排的但EMQX 的匹配不是第一条命中最先返回而是有明确的优先级具体说在同一个数据源内部规则从上往下逐条匹配deny和allow谁先命中谁生效这里官方的实际行为是所有匹配的规则中只要有一条是 deny就拒绝只有全部匹配项都是 allow才允许。换句话说 deny 的优先级高于 allow。这个设计是出于安全考虑但也带来一个后果你写了一条{allow, all}在最上面后面再加{deny, ...}并不会因为allow 在前面就先放行deny 依然会生效。所以allow all这条一定要放在最后当兜底别放开头。第二个关键点是no_match也就是所有规则都没命中时的兜底策略。配置项是authorization { no_match deny # 可选 allow / deny默认 allow deny_action ignore # 可选 ignore / disconnect默认 ignore }no_match默认是allow这是最容易出事的一个默认值。它的含义是如果你的规则表里没有一条能匹配上当前请求那就放行。这意味着一个拼错的主题、一个没想到的用户名格式都可能悄悄绕开你的整套规则。生产环境我建议直接改成deny走白名单思路——只有明确写了的才允许其余全拒。改完之后一定要把规则表补全否则会出现设备突然发不出消息的情况。第三个是缓存。EMQX 支持把授权结果缓存起来避免每条消息都去查一遍数据源尤其是走 HTTP 或数据库的时候authorization { cache { enable true max_size 32 ttl 1m } }缓存能显著降低授权开销但也意味着改完规则不会立刻全量生效最坏要等一个ttl周期。排障的时候如果发现规则改了但行为没变先看看是不是缓存在起作用把 ttl 调小或者临时关掉再验证。2.4 deny_action拒绝之后是丢包还是断线deny_action这个参数决定了被拒绝之后发生什么很多人没注意它结果被现象绕晕。ignore默认发布被拒消息静默丢弃客户端不会有明显感知订阅被拒服务器返回订阅失败。disconnect一旦被拒绝服务器直接断开这个客户端的连接。选哪个取决于你的业务语义。如果客户端是那种写得很糙、会无脑重试的设备用ignore比较温和避免它陷入被拒 → 断线 → 重连 → 再被拒的循环风暴但如果你希望权限问题暴露得足够明显让开发和运维第一时间发现配错了那disconnect更直接。我一般是先在测试环境用disconnect把问题打出来规则调稳之后生产环境切回ignore。还有一点MQTT 5.0 的客户端在被拒的时候能拿到明确的原因码比如发布被拒会收到PUBACK里带的0x87 Not authorized订阅被拒会在SUBACK里看到对应失败码。MQTT 3.1.1 则只有SUBACK的0x80这个笼统的失败码发布被拒时 QoS 0 的消息更是连回执都没有完全静默。这就是为什么同样一套规则用 MQTT 5.0 的客户端排障会轻松很多——它至少会告诉你我被拒了而不是让你对着没有消息的界面发呆。3. 三种主流落地方式文件、内置数据库、外部数据源3.1 acl.conf 静态文件小而稳适合设备侧固定策略文件版的优点是不依赖任何外部组件重启即加载规则一目了然能进版本管理。缺点是改动要重载配置没有界面规则一多就难维护。启用方式是在配置文件里把 file 数据源打开authorization { sources [ { type file enable true path etc/acl.conf } ] }一个相对完整的设备侧策略可以这么写%% 允许名为 collector 的服务端账号订阅所有设备上行主题 {allow, {username, collector}, subscribe, [devices//up]}. %% 允许名为 collector 的账号向下发指令 {allow, {username, collector}, publish, [devices//down]}. %% 每台设备只能发布自己的上行主题 {allow, {clientid, {re, ^dev-}}, publish, [devices/%c/up]}. %% 每台设备只能订阅发给自己下行的主题 {allow, {clientid, {re, ^dev-}}, subscribe, [devices/%c/down]}. %% 运维账号可以看服务器自身状态 {allow, {username, ops}, subscribe, [$SYS/#]}. %% 兜底明确拒绝 {deny, all}.注意最后那条{deny, all}这是把兜底从allow换成显式拒绝。写这条的前提是你的规则表已经覆盖了所有合法行为否则会出现莫名其妙的设备连上了但发不出数据。我的建议是先在测试环境跑一轮全量回归确认没有漏网的合法请求再上这条。注意用{deny, all}做兜底时必须同时确认authorization.no_match的取值两者语义上有重叠。规则写全了就没问题写不全就会两边一起拒。3.2 Dashboard 内置数据库改规则不用重启内置数据库是我在中小规模场景里最推荐的方案因为它有图形界面、改完立即生效、支持动态增删运维和开发都能上手。操作路径是登录 EMQX Dashboard → 左侧访问控制 → 授权 → 确认开启了内置数据库数据源 → 在下方权限标签页里添加规则。内置数据库里每条规则要填的字段和文件版一一对应字段说明示例权限允许 / 拒绝允许用户名匹配连接时的 username可留空dev-A001客户端 ID匹配连接的 clientid可留空dev-A001IP 地址匹配来源网段可留空192.168.10.0/24操作发布 / 订阅 / 发布订阅发布订阅主题主题过滤器支持${username}、${clientid}devices/${clientid}/#这里有个细节用户名、客户端 ID、IP 地址这三个条件留空表示不限制填了才作为判断条件。多个条件同时填是与的关系必须全部满足才算命中。所以最常见的写法是用户名填上、其余留空一条规则对应一个账号。提示内置数据库的规则默认存储在 EMQX 的数据目录里。做集群的时候规则是通过集群同步机制自动下发的不需要每个节点手工加。但如果你是从单机扩成集群建议先确认好数据目录和持久化配置避免节点间规则不一致。另一个提效的点是 Dashboard 提供了规则导入导出在授权页面右上角可以把一套规则批量导入到不同环境。测试环境调好的规则导出 JSON 再导进生产比手敲一遍靠谱得多。3.3 对接 MySQL / Redis规则量大时的选择当设备量上到几千上万或者你的权限本身就和业务系统的用户表、设备表关联时把 ACL 放进现成数据库更合理——业务系统改权限MQTT 侧自动跟着变不用两头维护。MySQL 的思路是准备一张权限表然后配置一条带占位符的查询语句。表结构可以这样设计CREATE TABLE mqtt_acl ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) DEFAULT NULL, clientid VARCHAR(100) DEFAULT NULL, ipaddr VARCHAR(60) DEFAULT NULL, permission VARCHAR(10) NOT NULL, -- allow / deny action VARCHAR(10) NOT NULL, -- publish / subscribe / all topic VARCHAR(200) NOT NULL, -- 主题过滤器 INDEX idx_username (username), INDEX idx_clientid (clientid) );插入几条规则INSERT INTO mqtt_acl (username, permission, action, topic) VALUES (collector, allow, subscribe, devices//up), (collector, allow, publish, devices//down), (ops, allow, subscribe, $SYS/#);EMQX 侧的查询语句里可以用${username}、${clientid}、${peerhost}这些占位符实际执行时会被替换成当前连接的值。比如SELECT permission, action, topic FROM mqtt_acl WHERE (username ${username} OR username IS NULL) AND (clientid ${clientid} OR clientid IS NULL)查出来的每一行都会被当作一条规则参与匹配。这里我给两个实操上的建议第一查询字段上一定要建索引否则每条消息都全表扫描broker 的连接数和消息量一上去数据库先扛不住第二把查询语句写得尽量宽容比如允许字段为 NULL 表示通配这样一条 SQL 就能覆盖多种规则形态减少以后改配置的频率。Redis 那条路更适合高并发读取的场景。EMQX 支持用 Hash 结构存规则key 里带用户名或客户端 IDfield 是主题过滤器value 是权限描述形如allow:pubsub、deny:subscribe。用 Redis 的好处是查询延迟低、能扛住高频读取配合本地缓存之后基本不会有明显的性能损耗代价是数据结构设计要提前想清楚而且 Redis 本身的持久化策略要配好别把权限数据当成缓存给丢了。3.4 三种方案怎么选一张表说清维度文件 acl.conf内置数据库外部数据源MySQL/Redis上手成本最低低中需准备库表和连接动态修改需重载配置立即生效立即生效规则承载量几十条比较合适几百到几千条上万条也没压力与业务系统联动基本没有弱强集群一致性需各节点同步文件自动同步天然一致适合场景设备策略固定的小项目中小规模、要可视化运维平台级、权限与业务强关联选型上我的经验是别一上来就上外部数据库。很多项目评估时觉得以后肯定要扩结果规则表就几十条为了这点量多维护一套数据同步链路反而增加了故障面。先文件或者内置数据库跑起来等到规则数确实上千、或者业务系统要求实时联动再迁到外部数据源迁移成本并不高。4. 实测验证用 MQTTX 把发布和订阅权限各打一遍4.1 搭一个最小验证环境规则写完一定要验证而且要用站在客户端角度的方式验证而不是看着 Dashboard 觉得对了就算完。我习惯用 MQTTX 这类图形化客户端因为它能清楚地显示连接状态、订阅结果和收到的消息比命令行直观。准备三个账号来模拟不同角色dev-A001设备账号只该发devices/A001/up、收devices/A001/down。dev-A002另一台设备用来验证交叉访问会被拒。collector采集端该收devices//up、发devices//down。先在 EMQX 里把这三个用户名对应的认证配好认证怎么配这里不展开然后按 3.2 的字段规则建三条授权规则。建完之后用 MQTTX 分别建三个连接注意每个连接的用户名和客户端 ID 都要对上——因为规则里既有按 username 匹配的也有按 clientid 匹配的两个值填错一个验证结果就不可信了。提示客户端 ID 一定要显式设置。MQTTX 默认会生成一个随机 clientid如果你规则里写的是{clientid, dev-A001}这种固定值随机 ID 永远匹配不上你会以为是规则写错了其实是客户端 ID 没填对。这种情况我见过不止一次。4.2 订阅侧验证通配符、跨用户、共享订阅订阅侧的验证重点是要测越权和通配符这两类情况。第一组正常路径。用dev-A001订阅devices/A001/down应该成功。然后用collector往devices/A001/down发一条消息dev-A001应该能收到。这一步确认规则没有把该放行的挡住。第二组越权订阅。用dev-A001订阅devices/A002/down应该被拒。MQTT 5.0 下会在 SUBACK 里看到失败码MQTT 3.1.1 下看到0x80。注意有些客户端库对订阅失败是静默处理的界面上看起来订阅成功了实际服务器返回了失败码客户端没弹提示然后你就一直等不到消息。MQTTX 在这方面提示比较明确所以我推荐用它做验证。第三组通配符越权。用dev-A001订阅devices/#按道理应该被拒因为它的权限只覆盖devices/A001/#。这一组是很多规则写漏的地方——如果规则表里只写了{allow, ...}而没有兜底的 deny或者no_match还是默认的 allow这条订阅可能就被放行了。测出来放行说明兜底没配好。第四组共享订阅。如果你的系统用了$share/group/topic这种共享订阅格式要注意 ACL 检查时对$share前缀的处理方式。不同版本的行为不完全一致有的会把前缀剥掉再匹配主题有的会把整个字符串拿去匹配。我实测下来稳妥的做法是在规则里同时覆盖裸主题和带前缀的形式别赌版本行为。这一块一定要在自己用的版本上实测一遍别照搬别人的结论。还有个容易忽略的点订阅归订阅消息归消息。订阅权限通过之后服务器还要判断这条消息的实际主题是否在该客户端的订阅范围内。所以哪怕订阅成功了如果发布方的主题写得跟你的订阅过滤器对不上你还是收不到东西。排障的时候把这两件事分开看能省一半时间。4.3 发布侧验证QoS 0 与 QoS 1 的不同表现发布侧的验证比订阅侧更隐蔽因为默认的deny_action ignore会让被拒的消息悄无声息地消失。你从 MQTTX 发出去界面显示发送成功服务端一个字都没记订阅方什么也收不到。所以发布侧验证的第一步是把deny_action临时改成disconnect让拒绝行为可见客户端一发就被踢说明确实被拒了。或者把日志级别调到 debug观察授权判定的日志输出里面会带上 clientid、username、topic、action 和最终结果这是最准的判断依据。具体测这几组用dev-A001往devices/A001/up发一条QoS 1应该成功。同时在collector那侧订阅devices//up确认能收到。用dev-A001往devices/A002/up发一条QoS 1应该被拒。MQTT 5.0 下能看到PUBACK里的0x87MQTT 3.1.1 下可能只表现为发了但对方没收到。同样的越权发布换成 QoS 0 再发一次。QoS 0 没有回执被拒时客户端完全无感这是最容易误判成服务端有 bug的情况。如果你的业务里有 QoS 0 的越权发布只有看服务端日志才能发现问题。用collector往devices/A001/down发一条应该成功往cloud/xxx这种规则里完全没提到的主题发看兜底策略是放行还是拒绝用来验证no_match的实际取值。这套测完基本能确认规则表的方向是对的。4.4 集群与多节点下的验证要点单节点验证通过不等于集群验证通过这是很多人在扩容时踩的坑。几个要点内置数据库和外部数据源在集群里是统一的一般不需要额外操作规则会自动同步到各节点。但同步有延迟加完规则立刻验证可能还没下发完稍微等一下或者确认同步状态。文件方式的 acl.conf 不会自动同步。每个节点都有自己的配置文件你得保证所有节点的文件内容一致否则会出现连到 A 节点能发连到 B 节点被拒这种诡异现象。验证时要覆盖到每个节点。客户端连到哪个节点是由负载均衡决定的你只测了一台等于没测集群。我的做法是把负载均衡临时摘掉用每个节点的地址各连一次跑同一组用例。缓存会影响一致性。集群环境下缓存是各节点本地的改规则后不同节点的失效时间可能不同短时间内出现行为不一致是正常的别急着判定有 bug。5. 排障实录规则明明写了却不生效的七种原因5.1 订阅成功但收不到消息这是被问得最多的一类问题。原因通常不在订阅权限上而在别的地方。按这个顺序排查会比较快第一看发布的主题和订阅的过滤器是否真的匹配。sensor//temp匹配sensor/A/temp但不匹配sensor/A/B/temp也不匹配sensor/A/temp/x。这一层手动核一遍别凭印象。第二看发布方有没有发布成功。如果发布方的 ACL 拒绝了这次发布消息压根没进到 broker订阅方当然收不到。发布和订阅是两个独立的授权判断任何一个环节被拒链路上都收不到消息。第三看 QoS 和 retained 的语义。QoS 0 的消息在订阅方掉线期间会丢如果用 retained 消息来补历史得确认发布时真的设置了 retained 标志。第四如果用了共享订阅确认发布方和被订阅的主题在同一个共享组逻辑下。5.2 客户端被反复踢下线现象是客户端连上几秒就断日志里能看到大量断开和重连。这种基本都是deny_action disconnect加上客户端无脑重试导致的。要区分两种情况一种是客户端的行为本身越权了比如启动就订#那得改客户端或者补规则另一种是规则配置有误把合法请求判成了拒绝比如用户名占位符写错、deny 规则写得太宽那得修规则。排查手段很简单把日志级别调成 debug看每次断开前的授权判定记录里面会明确写清楚是哪个 topic、哪个 action、被哪条规则拒了。看到这条日志问题基本就定位了。5.3 规则命中但结果反了偶尔会遇到我明明写了 allow结果被拒了或者反过来。常见原因是 deny 与 allow 的优先级理解错了。前面说过deny 的优先级高于 allow只要有一条 deny 规则命中无论有多少 allow 命中结果都是拒绝。另一个原因是规则顺序导致的第一条命中即返回。虽然 deny 优先级更高但在某些数据源实现里第一条命中的规则就会决定结果后面的压根不参与。所以规则表的顺序要按从具体到宽泛来排最具体的规则放最上面allow all或者deny all这种兜底放最下面。还有一个很隐蔽的原因多个数据源同时开启时前一个数据源返回了不匹配后一个才继续查。如果你在文件里加了规则但内置数据库里有一条宽泛的allow all排在前面生效了那文件里的 deny 永远执行不到。排障时要先看清楚当前到底启用了哪几个数据源、顺序如何。5.4 常见问题速查表现象最可能的原因处理方向规则改了不生效授权结果缓存未过期等 ttl 或临时关闭缓存规则完全不生效用错版本语法user / username按大版本核对关键字Dashboard 规则不生效占位符用了%u而不是${username}换成对应语法订阅成功收不到消息发布侧被拒或主题不匹配分别验证发布与订阅客户端反复掉线deny_action disconnect加越权请求调日志看被拒原因换节点后行为不一致文件未同步或缓存未失效统一文件、等待缓存过期规则看不出问题但被拒兜底no_match或deny all命中检查兜底策略通配符订阅被拒ACL 判断的是覆盖面而非字符串收窄订阅范围或补规则6. 主题设计先行ACL 才写得干净6.1 主题命名的三条硬规矩ACL 写得痛苦八成是因为主题设计的时候就埋了雷。我的经验是主题设计要守三条规矩。第一条分层要有业务含义别用扁平结构。devices/A001/up比A001up好写规则得多因为前者可以在任意层级上做通配。建议的最少层次是业务域/设备类型/设备ID/方向比如factory/line1/dev-A001/telemetry。层次定好之后规则里factory/line1/${clientid}/#一行就够了。第二条设备 ID 要能直接对应到用户名或客户端 ID。因为 ACL 的占位符只能基于 username 和 clientid 取值如果你的主题里用的是设备序列号、数据库主键这类跟连接凭据对不上的标识占位符就用不了只能给每台设备写一条规则上千台设备就是上千条规则。这个约束在设计阶段考虑进去后面能省掉大量工作。第三条别让客户端有全量订阅的能力。#这种订阅一旦被允许等于把整个 broker 的消息开放了。规则里应该显式拒绝它同时业务上也要禁止客户端去订阅它。$SYS系列的主题更是要单独管控只给运维工具开。6.2 规则数量膨胀后怎么治理规则一旦超过几百条维护成本会陡增。几个实用做法用占位符替代重复规则。一千台设备各写一条是下策用${clientid}一条搞定是上策。判断标准是如果这些规则除了主体不同、其余完全一样那就应该合并成一条带占位符的规则。把角色抽象出来而不是按人/按设备写规则。系统里真正需要的规则数量是角色数 × 权限组合数不是设备数 × 权限数。设备侧一般就两三个角色只上报的传感器、双向的控制器、只下发的网关。把角色理清楚规则表会立刻瘦身。定期清理僵尸规则。设备的生命周期结束、账号停用之后对应的规则如果没删就会一直留在表里既增加匹配开销也增加审计难度。我习惯按季度过一遍规则表跟设备台账对一次。6.3 变更与审计权限是安全边界改动要走流程别在生产 Dashboard 上随手改。我现在的习惯是所有规则变更先在测试环境验证再导出成配置文件或 SQL 脚本走代码评审最后发布。这样一来每次改动都有记录出问题能回滚也能回答这条规则是谁什么时候加的这种问题。审计上要关注两件事一是被拒绝的请求量如果某个主题的拒绝量突然飙升可能是有人在扫主题也可能是某台设备配置错了二是规则表的变更记录尤其是 deny 规则的删除和 allow 规则的新增。这两类信息配合服务端日志一起看基本能覆盖权限侧的异常发现。还有个小细节值得单独提一下不要把 ACL 当成万能的隔离手段。ACL 管的是主题级别的访问控制它管不了同一个主题下的不同消息该给谁看这种更细的粒度。如果你的业务有按消息内容分权的需求得在应用层做别指望 ACL 解决。反过来说如果发现规则表里出现了大量基于具体主题路径的细碎规则那大概率是主题设计出了问题回头看看第 6.1 节那三条规矩。关于集群环境下文件规则的同步我还想补一句如果你的部署方式是容器化的最省事的做法是把 acl.conf 做成配置挂载或者打进镜像而不是进容器里手改。手工改的文件在 Pod 重建之后会消失而这个消失往往是在某次扩容之后才被发现那时候你已经不记得原来写了什么了。
返回列表