ACL(Access Control List)访问控制

上一章介绍的认证(认证鉴权)用于控制用户是否可以登录 EMQ X 服务器;而本章则介绍利用 ACL 用户控制用户的权限:EMQ X 支持限定客户端可以使用的主题,从而实现设备权限的管理。

ACL 访问控制

EMQ X 默认开启 ACL 白名单,允许不在 ACL 列表中的发布订阅行为,具体配置在 etc/emqx.conf 中:

  1. ## ACL nomatch
  2. mqtt.acl_nomatch = allow
  3. ## Default ACL File
  4. ## etc/acl.conf 文件中配置了基础的 ACL 规则
  5. mqtt.acl_file = etc/acl.conf

ACL 访问控制规则定义规则如下:

  1. 允许 (Allow)|拒绝 (Deny) 谁(Who) 订阅 (Subscribe)|发布 (Publish) 主题列表 (Topics)

EMQ X 接收到 MQTT 客户端发布 (PUBLISH) 或订阅 (SUBSCRIBE) 请求时,会逐条匹配 ACL 访问控制规则,直到匹配成功返回 allow 或 deny。

  • ACL 可以设置超级用户,如果是超级用户客户端,可以进行任意发布 / 订阅操作
  • ACL 控制与认证用的是同一个配置文件plugins/emqx_auth_xxx.conf,但并不是所有的插件都支持 ACL。

ACL 缓存

  1. ## 是否缓存 ACL 规则,设定了缓存之后,可以加快获取 ACL 记录的速度
  2. mqtt.cache_acl = true

ACL 规则在命中后,会在内存中有缓存,避免下次需要验证 ACL 的时候访问外部存储设备,加快访问的速度。ACL 在内存中的缓存只有在连接建立和存在的时间段内有效,如果连接断开,该连接对应的 ACL 信息会被删除;用户可以通过 EMQ X 提供的 REST API 来删除 ACL 信息。

  1. {
  2. "name": "clean_acl_cache",
  3. "method": "DELETE",
  4. "path": "/connections/:clientid/acl/:topic",
  5. "descr": "Clean ACL cache of a connection"
  6. }

配置文件访问控制

ACL 配置

准备访问控制数据

设定如下的访问规则。

  1. 设定所有用户不可以订阅系统主题,除了从特定机器 10.211.55.10 发起的连接除外;
  2. 应用的主题设计为/smarthome/$clientId/temperature,设定一条规则只允许相同的 clientId 的设备才可以对它自己的主题进行发布消息操作

打开访问控制的配置文件 /etc/emqx/acl.conf ,配置文件内容如下。

  1. {allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
  2. {allow, all, publish, ["/smarthome/%c/temperature"]}.
  3. {allow, {ipaddr, "10.211.55.10"}, pubsub, ["$SYS/#", "#"]}.
  4. {deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.

修改配置文件

打开配置文件 /etc/emqx/emqx.conf ,将 ACL 的规则匹配变为:不匹配则不允许。

  1. mqtt.acl_nomatch = deny

打开配置文件 /etc/emqx/plugins/emqx_auth_username.conf,加入以下的认证用户。

  1. auth.user.1.username = userid_001
  2. auth.user.1.password = public

使用命令行 emqx_ctl plugins load emqx_auth_username 激活 emqx_auth_username 插件,然后重启 EMQ X 服务。

测试系统主题

在机器 10.211.55.6 订阅系统主题,请注意订阅系统主题的时候,在 mosquitto 客户端需要对主题的字符$前加入转义符 \,变成 \$SYS/# ,命令如下所示。目前版本无法在前端知道是否订阅失败,需要结合EMQ X 后台日志才可以进行判断。

  1. mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 128

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 02:12:43.866 [error] <0.1993.0>@emqx_protocol:process:294 Client(test_username1@10.211.55.6:57612): Cannot SUBSCRIBE [{<<"$SYS/#">>,[{qos,0}]}] for ACL Deny

在机器 10.211.55.10 订阅系统主题,成功收到所有的系统消息。

  1. # mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 0
  7. Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
  8. emqx@127.0.0.1
  9. Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
  10. 2.4.3
  11. Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
  12. EMQ X Broker
  13. Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/uptime', ... (22 bytes))
  14. 17 minutes, 14 seconds
  15. Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/datetime', ... (19 bytes))
  16. 2018-11-13 02:14:03

测试设备操作自己的主题

订阅失败,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息。

  1. # mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/user1/temperature" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/user1/temperature, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 128

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 02:16:56.118 [error] <0.2001.0>@emqx_protocol:process:294 Client(test_username1@10.211.55.6:57676): Cannot SUBSCRIBE [{<<"/smarthome/user1/temperature">>,[{qos,0}]}] for ACL Deny

成功的订阅:EMQ X 后台日志(/var/log/emqx/error.log)如果没有打印 ACL 的出错信息表示订阅成功。

  1. # mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/test_username1/temperature" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/test_username1/temperature, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 0

HTTP 访问控制

HTTP 访问控制使用 HTTP API 实现ACL 控制。

实现原理

EMQ X 在发布/订阅的时候使用当前客户端相关信息作为参数,发起请求查询设备权限,通过 HTTP 响应状态码 (HTTP Status) 来处理事件。

  • ACL 成功,API 返回 200 状态码

  • ACL 失败,API 返回 4xx 状态码

使用方式

打开 etc/plugins/emqx_auth_http.conf 文件,配置相关规则:

  1. ## 配置超级用户 ACL 地址,这里指定相关的服务器地址和端口,路径可以按照你自己的实现来指定
  2. auth.http.super_req = http://$server:$port/mqtt/admin
  3. auth.http.super_req.method = post
  4. auth.http.super_req.params = clientid=%c,username=%u
  5. ## 配置 ACL URL 地址,
  6. auth.http.acl_req = http://$server:$port/mqtt/acl
  7. auth.http.acl_req.method = get
  8. auth.http.acl_req.params = access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t

装载插件后重新启动 EMQ X,EMQ X 将通过指定的 ACL 地址进行检查,Web 服务器获取到 EMQ X 提交的参数并执行相关逻辑后返回相应的 HTTP 响应状态码。但是具体返回内容视你自己需求而定,EMQ X 不作要求。

配置中的详细占位符定义请见页底。

如下所示为超级用户的实现代码,这里判断传入的 clientIdsysadmin 的时候就返回200,认为该用户为超级用户;否则返回 SC_FORBIDDEN,表示不允许访问。超级用户不受 ACL 的控制。

  1. package io.emqx;
  2. import java.io.IOException;
  3. import java.text.MessageFormat;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. @WebServlet("/admin")
  10. public class AdminServlet extends HttpServlet {
  11. private static final long serialVersionUID = 1L;
  12. public AdminServlet() {
  13. super();
  14. }
  15. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  16. String clientId = request.getParameter("clientid");
  17. System.out.println(MessageFormat.format("clientid: {0}", clientId));
  18. if(clientId == null || "".equals(clientId.trim()) ) {
  19. response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  20. response.getWriter().println("Invalid request contents.");
  21. return;
  22. }
  23. if(clientId.equals("sysadmin")) {
  24. response.setStatus(HttpServletResponse.SC_OK);
  25. response.getWriter().println("OK");
  26. } else {
  27. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  28. response.getWriter().println("Not admin");
  29. }
  30. }
  31. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  32. doGet(request, response);
  33. }
  34. }

下列代码是验证 ACL 逻辑的示例,判断传入的 clientId 以及操作的 topic 之间的关系,如果 topicclientId 结尾,那么返回200,表示可以操作;否则返回403(Forbidden),表示不可以操作。

  1. package io.emqx;
  2. import java.io.IOException;
  3. import java.text.MessageFormat;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. @WebServlet("/acl")
  10. public class AclServlet extends HttpServlet {
  11. private static final long serialVersionUID = 1L;
  12. public AclServlet() {
  13. super();
  14. }
  15. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  16. String clientId = request.getParameter("clientid");
  17. String username = request.getParameter("username");
  18. String access = request.getParameter("access");
  19. String topic = request.getParameter("topic");
  20. String ipaddr = request.getParameter("ipaddr");
  21. System.out.println(MessageFormat.format("clientid: {0}, username: {1}, access: {2}, topic: {3}, ipaddr: {4}", clientId, username, access, topic, ipaddr));
  22. if(clientId == null || "".equals(clientId.trim()) || topic == null || "".equals(topic.trim())) {
  23. response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  24. response.getWriter().println("Invalid request contents.");
  25. return;
  26. }
  27. if(topic.endsWith(clientId)) {
  28. response.setStatus(HttpServletResponse.SC_OK);
  29. response.getWriter().println("OK");
  30. } else {
  31. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  32. response.getWriter().println("Not allowed");
  33. }
  34. }
  35. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  36. doGet(request, response);
  37. }
  38. }

客户端通过 mosquitto_pub 发出消息,但是由于我们设定的规则不允许该用户往该主题发送消息,前端看起来都正常,如下图所示。

  1. # mosquitto_pub -h 10.211.55.10 -u user1 -i id1 -P passwd -t /devices/001/temp -m "hello" -d
  2. Client id1 sending CONNECT
  3. Client id1 received CONNACK
  4. Client id1 sending PUBLISH (d0, q0, r0, m1, '/devices/001/temp', ... (5 bytes))
  5. Client id1 sending DISCONNECT

在后端的日志中,会报出一条错误,提示该客户端不允许往该主题上发送消息。

  1. 2018-11-12 14:46:02.773 [error] <0.2004.0>@emqx_protocol:process:257 Client(id1@10.211.55.6:41367): Cannot publish to /devices/001/temp for ACL Deny

使用如下所示的命令,发送消息就正常。

  1. # mosquitto_pub -h 10.211.55.10 -u user1 -i id1 -P passwd -t /devices/001/temp/id1 -m "hello"

但是如果是超级用户登录的话,就不会出现 ACL Deny 的错误消息,可以往任意主题发送、订阅消息。

  1. # mosquitto_sub -h 10.211.55.10 -u sysadmin -i sysadmin -P sysadmin -t /devices/001/temp
  2. hello

MySQL/PostgreSQL 访问控制

emqx_auth_mysql / emqx_auth_pgsql 插件分别为基于 MySQL、PostgreSQL 数据库的访问控制插件。

用户认证(鉴权)和访问控制用的是同一个配置文件

超级用户配置

超级用户的设置是通过用户认证表的数据验证来实现的。如下所示,打开配置文件 emqx_auth_mysql.conf / emqx_auth_pgsql.conf 比较查询结果中的 is_superuser 字段是否为 true,”true”、true、’1’、’2’、’3’… 等程序意义上的为真值均可。超级用户不受 ACL 的控制。

  1. auth.mysql.super_query = SELECT is_superuser FROM mqtt_user WHERE username = '%u' LIMIT 1

ACL 配置

创建数据库

如果读者在阅读过 MySQL/PostgreSQL 认证部分,已经创建过数据库的话,可以跳过这部分。

读者可以使用任何自己喜欢的 mysql 客户端,创建好相应的数据库。这里用的是 MySQL 自带的命令行客户端,打开 MySQL 的控制台,如下所示,创建一个名为 emqx 的认证数据库,并切换到 emqx 数据库。

  1. mysql> create database emqx;
  2. Query OK, 1 row affected (0.00 sec)
  3. mysql> use emqx;
  4. Database changed

创建表

建议的表结构如下,其中,

  • allow:禁止(0);或则允许(1)。

  • ipaddr:设置 IP 地址。

  • username:连接客户端的用户名,此处的值如果设置为 $all 表示该规则适用于所有的用户。

  • clientid:连接客户端的 clientId。
  • access:允许的操作。订阅(1);发布(2);订阅和发布都可以(3)。
  • topic:控制的主题名。主题可以使用通配符;并且可以在主题中加入占位符 %c , 来匹配带客户端 ID 的主题,例如 /smarthome/$clientId/temperature
  1. CREATE TABLE `mqtt_acl` (
  2. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  3. `allow` int(1) DEFAULT 1 COMMENT '0: deny, 1: allow',
  4. `ipaddr` varchar(60) DEFAULT NULL COMMENT 'IpAddress',
  5. `username` varchar(100) DEFAULT NULL COMMENT 'Username',
  6. `clientid` varchar(100) DEFAULT NULL COMMENT 'ClientId',
  7. `access` int(2) NOT NULL COMMENT '1: subscribe, 2: publish, 3: pubsub',
  8. `topic` varchar(100) NOT NULL DEFAULT '' COMMENT 'Topic Filter',
  9. PRIMARY KEY (`id`)
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建成功后,查看一下表结构,

  1. mysql> desc mqtt_acl;
  2. +----------+------------------+------+-----+---------+----------------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +----------+------------------+------+-----+---------+----------------+
  5. | id | int(11) unsigned | NO | PRI | NULL | auto_increment |
  6. | allow | int(1) | YES | | 1 | |
  7. | ipaddr | varchar(60) | YES | | NULL | |
  8. | username | varchar(100) | YES | | NULL | |
  9. | clientid | varchar(100) | YES | | NULL | |
  10. | access | int(2) | NO | | NULL | |
  11. | topic | varchar(100) | NO | | | |
  12. +----------+------------------+------+-----+---------+----------------+
  13. 7 rows in set (0.00 sec)

准备访问控制数据

现在要制定以下规则:

  1. 设定所有用户不可以订阅系统主题,除了从特定机器 10.211.55.10 发起的连接除外;
  2. 应用的主题设计为/smarthome/$clientId/temperature,设定一条规则只允许相同的 clientId 的设备才可以对它自己的主题进行发布消息操作
  1. # 所有用户不可以订阅系统主题
  2. INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (0, NULL, '$all', NULL, 1, '$SYS/#');
  3. # 允许10.211.55.10上发起的连接订阅系统主题
  4. INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, '10.211.55.10', NULL, NULL, 1, '$SYS/#');
  5. # 允许设备只对自己的主题进行发布消息
  6. INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, NULL, NULL, NULL, 1, '/smarthome/%c/temperature');

修改配置

打开配置文件 /etc/emqx/emqx.conf ,将 ACL 的规则匹配变为:不匹配则不允许。

  1. mqtt.acl_nomatch = deny

打开配置文件 /etc/emqx/plugins/emqx_auth_mysql.conf,设置 SQL 语句如下,

  1. auth.mysql.acl_query = select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'

保存配置文件后,激活 emqx_auth_mysql 和 emqx_auth_pgsql 插件,并重启 EMQ X 服务。

测试系统主题

在机器 10.211.55.6 订阅系统主题,请注意订阅系统主题的时候,在 mosquitto 客户端需要对主题的字符$前加入转义符 \,变成 \$SYS/# ,命令如下所示。目前版本无法在前端知道是否订阅失败,需要结合EMQ X 后台日志才可以进行判断。

  1. mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 128

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 02:12:43.866 [error] <0.1993.0>@emqx_protocol:process:294 Client(test_username1@10.211.55.6:57612): Cannot SUBSCRIBE [{<<"$SYS/#">>,[{qos,0}]}] for ACL Deny

在机器 10.211.55.10 订阅系统主题,成功收到所有的系统消息。

  1. # mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 0
  7. Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
  8. emqx@127.0.0.1
  9. Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
  10. 2.4.3
  11. Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
  12. EMQ X Broker
  13. Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/uptime', ... (22 bytes))
  14. 17 minutes, 14 seconds
  15. Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/datetime', ... (19 bytes))
  16. 2018-11-13 02:14:03

测试设备操作自己的主题

订阅失败,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息。

  1. # mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/user1/temperature" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/user1/temperature, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 128

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 02:16:56.118 [error] <0.2001.0>@emqx_protocol:process:294 Client(test_username1@10.211.55.6:57676): Cannot SUBSCRIBE [{<<"/smarthome/user1/temperature">>,[{qos,0}]}] for ACL Deny

成功的订阅:EMQ X 后台日志(/var/log/emqx/error.log)如果没有打印 ACL 的出错信息表示订阅成功。

  1. # mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/test_username1/temperature" -d
  2. Client test_username1 sending CONNECT
  3. Client test_username1 received CONNACK
  4. Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/test_username1/temperature, QoS: 0)
  5. Client test_username1 received SUBACK
  6. Subscribed (mid: 1): 0

Redis 访问控制

emqx_auth_redis 插件为基于 Redis 数据库的访问控制插件。Redis 里现在只支持白名单配置,就是只有在 Redis 中列出的规则才可以对某主题具有访问权限。

用户认证(鉴权)和访问控制中用的是同一个配置文件

超级用户配置

如果按照在认证中的配置,超级用户是通过获取保存在 Redis 数据库中 mqtt_user 数据结构的 is_superuser 字段中的值判断该用户是否为超级用户。打开 etc/plugins/emqx_auth_redis.conf,配置 super 查询。超级用户不受 ACL 的控制。

  1. ## Super Redis command
  2. auth.redis.super_cmd = HGET mqtt_user:%u is_superuser

ACL 配置

准备 ACL 数据

采用 Redis 的 Hash 来存储 ACL 信息,格式如下:

  • username:用户名,表示客户端的用户名称
  • topicname:主题名
  • [1|2|3]:1为订阅;2为发布;3为订阅和发布
  1. HSET mqtt_acl:username topicname [1|2|3]

如下所示,为用户名为 user1 的客户端设定可以订阅系统主题 $SYS/#

  1. ## 设定主题 $SYS/# 的权限为可以订阅
  2. 127.0.0.1:6379[2]> HMSET mqtt_acl:userid_001 $SYS/# 1
  3. OK
  4. ## 取出主题 $SYS/# 的权限
  5. 127.0.0.1:6379> HMGET mqtt_acl:userid_001 $SYS/#
  6. 1) "1"
  7. ## 设定主题 $SYS/# 的权限为可以发布
  8. 127.0.0.1:6379> HMSET mqtt_acl:userid_001 /smarthome/%c/temperature 2
  9. OK
  10. ## 取出主题 $SYS/# 的权限
  11. 127.0.0.1:6379> HMGET mqtt_acl:userid_001 /smarthome/%c/temperature
  12. 1) "2"

修改配置文件

打开配置文件 /etc/emqx/emqx.conf ,将 ACL 的规则匹配变为:不匹配则不允许。

  1. mqtt.acl_nomatch = deny

打开 etc/plugins/emqx_auth_redis.conf,配置 ACL 查询:

  1. ## %u: 用户名
  2. ## %c: 客户端ID
  3. auth.redis.acl_cmd = HGETALL mqtt_acl:%u

保存配置文件后,激活 emqx_auth_redis插件,并重启 EMQ X 服务。

测试系统主题

请注意订阅系统主题的时候,在 mosquitto 客户端需要对主题的字符$前加入转义符 \,变成 \$SYS/# ,命令如下所示。订阅成功后能接收到系统主题上的消息。

  1. # mosquitto_sub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "\$SYS/#" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client userid_001 received SUBACK
  6. Subscribed (mid: 1): 0
  7. Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
  8. emqx@127.0.0.1
  9. Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
  10. 2.4.3
  11. Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
  12. EMQ X Broker

在 Redis 中把该用户对系统主题的权限设置为只能发布。

  1. 127.0.0.1:6379> HMSET mqtt_acl:userid_001 $SYS/# 2
  2. OK

订阅失败,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息。

  1. # mosquitto_sub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "\$SYS/#" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client userid_001 received SUBACK
  6. Subscribed (mid: 1): 128

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 12:08:48.178 [error] <0.1958.0>@emqx_protocol:process:294 Client(userid_001@10.211.55.6:43337): Cannot SUBSCRIBE [{<<"$SYS/#">>,[{qos,0}]}] for ACL Deny

测试设备操作自己的主题

用户 userid_001 向不是自己的主题 /smarthome/userid_002/temperature 发送消息,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息

  1. # mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_002/temperature" -m "hello" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_002/temperature', ... (5 bytes))
  5. Client userid_001 sending DISCONNECT

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 12:11:33.785 [error] <0.1966.0>@emqx_protocol:process:257 Client(userid_001@10.211.55.6:43448): Cannot publish to /smarthome/userid_002/temperature for ACL Deny

用户 userid_001 向自己的主题 /smarthome/userid_001/temperature 发送消息,后台无 ACL 出错信息,发送消息成功。

  1. # mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_001/temperature" -m "hello" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_001/temperature', ... (5 bytes))
  5. Client userid_001 sending DISCONNECT

MongoDB 访问控制

emqx_auth_mongo 插件为基于 Mongo 数据库的访问控制插件。MongoDB 里现在只支持白名单配置,就是只有在 MongoDB 中列出的规则才可以对某主题具有访问权限。

用户认证(鉴权)和访问控制中用的是同一个配置文件

超级用户配置

打开 etc/plugins/emqx_auth_mongo.conf,配置 super 查询:

  1. ## 是否开启
  2. auth.mongo.super_query = on
  3. ## super 信息所在集合
  4. auth.mongo.super_query.collection = mqtt_user
  5. ## super 字段
  6. auth.mongo.super_query.super_field = is_superuser
  7. ## 查询指令
  8. auth.mongo.super_query.selector = username=%u

当查询结果中的 is_superuser(super_field 字段)为 true 时,表示当前用户为。

ACL 配置

准备 ACL 数据

MongoDB 中定义的 ACL 数据结构如下,

  • username:登录的用户名称
  • clientid:登录的客户端id
  • publish:数组格式,该用户允许的发布的消息主题名称列表,如果没有可以忽略该字段。
  • subscribe:数组格式,该用户允许的订阅的消息主题名称列表,如果没有可以忽略该字段。
  • pubsub:数组格式,该用户允许的既可以发布、也可以订阅的消息主题名称列表,如果没有可以忽略该字段。
  1. {
  2. username: "username",
  3. clientid: "clientid",
  4. publish: ["topic1", "topic2", ...],
  5. subscribe: ["subtop1", "subtop2", ...],
  6. pubsub: ["topic/#", "topic1", ...]
  7. }

在 MongoDB 中插入以下 ACL 信息,

  • 用户名和客户端 ID 为 userid_001 的用户,可以在自己的主题 /smarthome/%c/temperature 上进行发布操作;也可以在系统主题上进行订阅操作。
  1. > use mqtt
  2. switched to db mqtt
  3. > db.mqtt_acl.insert({ username: 'userid_001', clientid: "userid_001", publish:["/smarthome/%c/temperature"], subscribe: ["$SYS/#"], pubsub: [] })
  4. WriteResult({ "nInserted" : 1 })
  5. > db.mqtt_acl.findOne({ username: 'userid_001' })
  6. {
  7. "_id" : ObjectId("5bea67bb1d2e8f30aa829072"),
  8. "username" : "userid_001",
  9. "clientid" : "userid_001",
  10. "publish" : [
  11. "/smarthome/%c/temperature"
  12. ],
  13. "subscribe" : [
  14. "$SYS/#"
  15. ],
  16. "pubsub" : [ ]
  17. }

修改配置文件

打开配置文件 /etc/emqx/emqx.conf ,将 ACL 的规则匹配变为:不匹配则不允许。

  1. mqtt.acl_nomatch = deny

打开 etc/plugins/emqx_auth_mongo.conf,配置 ACL 查询,

  1. ## 是否开启 ACL 控制
  2. auth.mongo.acl_query = on
  3. ## ACL 信息所在集合
  4. auth.mongo.acl_query.collection = mqtt_acl
  5. ## 查询指令
  6. auth.mongo.acl_query.selector = username=%u

保存配置文件后,激活 emqx_auth_mongodb 插件,并重启 EMQ X 服务。

测试系统主题

请注意订阅系统主题的时候,在 mosquitto 客户端需要对主题的字符$前加入转义符 \,变成 \$SYS/# ,命令如下所示。订阅成功后能接收到系统主题上的消息。

  1. # mosquitto_sub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "\$SYS/#" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
  5. Client userid_001 received SUBACK
  6. Subscribed (mid: 1): 0
  7. Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
  8. emqx@127.0.0.1
  9. Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
  10. 2.4.3
  11. Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
  12. EMQ X Broker

测试设备操作自己的主题

用户 userid_001 向不是自己的主题 /smarthome/userid_002/temperature 发送消息,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息

  1. # mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_002/temperature" -m "hello" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_002/temperature', ... (5 bytes))
  5. Client userid_001 sending DISCONNECT

EMQ X 后台日志(/var/log/emqx/error.log)出错信息。

  1. 2018-11-13 21:44:44.161 [error] <0.2700.0>@emqx_protocol:process:257 Client(userid_001@10.211.55.6:56967): Cannot publish to /smarthome/userid_002/temperature for ACL Deny

用户 userid_001 向自己的主题 /smarthome/userid_001/temperature 发送消息,后台无 ACL 出错信息,发送消息成功。

  1. # mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_001/temperature" -m "hello" -d
  2. Client userid_001 sending CONNECT
  3. Client userid_001 received CONNACK
  4. Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_001/temperature', ... (5 bytes))
  5. Client userid_001 sending DISCONNECT

附录:认证/访问控制占位符对照表

占位符对照参数
%cMQTT clientid
%uMQTT username
%pMQTT password
%aACL IP 地址
%AACL access 方式,1: 发布 2:订阅 3:发布/订阅
%tACL 中 MQTT topic