ACL(Access Control List)访问控制
上一章介绍的认证(认证鉴权)用于控制用户是否可以登录 EMQ X 服务器;而本章则介绍利用 ACL 用户控制用户的权限:EMQ X 支持限定客户端可以使用的主题,从而实现设备权限的管理。
ACL 访问控制
EMQ X 默认开启 ACL 白名单,允许不在 ACL 列表中的发布订阅行为,具体配置在 etc/emqx.conf
中:
## ACL nomatch
mqtt.acl_nomatch = allow
## Default ACL File
## etc/acl.conf 文件中配置了基础的 ACL 规则
mqtt.acl_file = etc/acl.conf
ACL 访问控制规则定义规则如下:
允许 (Allow)|拒绝 (Deny) 谁(Who) 订阅 (Subscribe)|发布 (Publish) 主题列表 (Topics)
EMQ X 接收到 MQTT 客户端发布 (PUBLISH) 或订阅 (SUBSCRIBE) 请求时,会逐条匹配 ACL 访问控制规则,直到匹配成功返回 allow 或 deny。
- ACL 可以设置超级用户,如果是超级用户客户端,可以进行任意发布 / 订阅操作
- ACL 控制与认证用的是同一个配置文件
plugins/emqx_auth_xxx.conf
,但并不是所有的插件都支持 ACL。
ACL 缓存
## 是否缓存 ACL 规则,设定了缓存之后,可以加快获取 ACL 记录的速度
mqtt.cache_acl = true
ACL 规则在命中后,会在内存中有缓存,避免下次需要验证 ACL 的时候访问外部存储设备,加快访问的速度。ACL 在内存中的缓存只有在连接建立和存在的时间段内有效,如果连接断开,该连接对应的 ACL 信息会被删除;用户可以通过 EMQ X 提供的 REST API 来删除 ACL 信息。
{
"name": "clean_acl_cache",
"method": "DELETE",
"path": "/connections/:clientid/acl/:topic",
"descr": "Clean ACL cache of a connection"
}
配置文件访问控制
ACL 配置
准备访问控制数据
设定如下的访问规则。
- 设定所有用户不可以订阅系统主题,除了从特定机器
10.211.55.10
发起的连接除外; - 应用的主题设计为
/smarthome/$clientId/temperature
,设定一条规则只允许相同的clientId
的设备才可以对它自己的主题进行发布消息操作
打开访问控制的配置文件 /etc/emqx/acl.conf
,配置文件内容如下。
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
{allow, all, publish, ["/smarthome/%c/temperature"]}.
{allow, {ipaddr, "10.211.55.10"}, pubsub, ["$SYS/#", "#"]}.
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
修改配置文件
打开配置文件 /etc/emqx/emqx.conf
,将 ACL 的规则匹配变为:不匹配则不允许。
mqtt.acl_nomatch = deny
打开配置文件 /etc/emqx/plugins/emqx_auth_username.conf
,加入以下的认证用户。
auth.user.1.username = userid_001
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 后台日志才可以进行判断。
mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 128
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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
订阅系统主题,成功收到所有的系统消息。
# mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 0
Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
emqx@127.0.0.1
Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
2.4.3
Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
EMQ X Broker
Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/uptime', ... (22 bytes))
17 minutes, 14 seconds
Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/datetime', ... (19 bytes))
2018-11-13 02:14:03
测试设备操作自己的主题
订阅失败,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息。
# mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/user1/temperature" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/user1/temperature, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 128
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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 的出错信息表示订阅成功。
# mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/test_username1/temperature" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/test_username1/temperature, QoS: 0)
Client test_username1 received SUBACK
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
文件,配置相关规则:
## 配置超级用户 ACL 地址,这里指定相关的服务器地址和端口,路径可以按照你自己的实现来指定
auth.http.super_req = http://$server:$port/mqtt/admin
auth.http.super_req.method = post
auth.http.super_req.params = clientid=%c,username=%u
## 配置 ACL URL 地址,
auth.http.acl_req = http://$server:$port/mqtt/acl
auth.http.acl_req.method = get
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 不作要求。
配置中的详细占位符定义请见页底。
如下所示为超级用户的实现代码,这里判断传入的 clientId
为 sysadmin
的时候就返回200,认为该用户为超级用户;否则返回 SC_FORBIDDEN,表示不允许访问。超级用户不受 ACL 的控制。
package io.emqx;
import java.io.IOException;
import java.text.MessageFormat;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/admin")
public class AdminServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public AdminServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String clientId = request.getParameter("clientid");
System.out.println(MessageFormat.format("clientid: {0}", clientId));
if(clientId == null || "".equals(clientId.trim()) ) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("Invalid request contents.");
return;
}
if(clientId.equals("sysadmin")) {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("OK");
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().println("Not admin");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
下列代码是验证 ACL 逻辑的示例,判断传入的 clientId
以及操作的 topic
之间的关系,如果 topic
以 clientId
结尾,那么返回200,表示可以操作;否则返回403(Forbidden),表示不可以操作。
package io.emqx;
import java.io.IOException;
import java.text.MessageFormat;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/acl")
public class AclServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public AclServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String clientId = request.getParameter("clientid");
String username = request.getParameter("username");
String access = request.getParameter("access");
String topic = request.getParameter("topic");
String ipaddr = request.getParameter("ipaddr");
System.out.println(MessageFormat.format("clientid: {0}, username: {1}, access: {2}, topic: {3}, ipaddr: {4}", clientId, username, access, topic, ipaddr));
if(clientId == null || "".equals(clientId.trim()) || topic == null || "".equals(topic.trim())) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().println("Invalid request contents.");
return;
}
if(topic.endsWith(clientId)) {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("OK");
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().println("Not allowed");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
客户端通过 mosquitto_pub
发出消息,但是由于我们设定的规则不允许该用户往该主题发送消息,前端看起来都正常,如下图所示。
# mosquitto_pub -h 10.211.55.10 -u user1 -i id1 -P passwd -t /devices/001/temp -m "hello" -d
Client id1 sending CONNECT
Client id1 received CONNACK
Client id1 sending PUBLISH (d0, q0, r0, m1, '/devices/001/temp', ... (5 bytes))
Client id1 sending DISCONNECT
在后端的日志中,会报出一条错误,提示该客户端不允许往该主题上发送消息。
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
使用如下所示的命令,发送消息就正常。
# mosquitto_pub -h 10.211.55.10 -u user1 -i id1 -P passwd -t /devices/001/temp/id1 -m "hello"
但是如果是超级用户登录的话,就不会出现 ACL Deny
的错误消息,可以往任意主题发送、订阅消息。
# mosquitto_sub -h 10.211.55.10 -u sysadmin -i sysadmin -P sysadmin -t /devices/001/temp
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 的控制。
auth.mysql.super_query = SELECT is_superuser FROM mqtt_user WHERE username = '%u' LIMIT 1
ACL 配置
创建数据库
如果读者在阅读过 MySQL/PostgreSQL 认证部分,已经创建过数据库的话,可以跳过这部分。
读者可以使用任何自己喜欢的 mysql 客户端,创建好相应的数据库。这里用的是 MySQL 自带的命令行客户端,打开 MySQL 的控制台,如下所示,创建一个名为 emqx
的认证数据库,并切换到 emqx
数据库。
mysql> create database emqx;
Query OK, 1 row affected (0.00 sec)
mysql> use emqx;
Database changed
创建表
建议的表结构如下,其中,
allow:禁止(0);或则允许(1)。
ipaddr:设置 IP 地址。
username:连接客户端的用户名,此处的值如果设置为
$all
表示该规则适用于所有的用户。- clientid:连接客户端的 clientId。
- access:允许的操作。订阅(1);发布(2);订阅和发布都可以(3)。
- topic:控制的主题名。主题可以使用通配符;并且可以在主题中加入占位符
%c
, 来匹配带客户端 ID 的主题,例如/smarthome/$clientId/temperature
。
CREATE TABLE `mqtt_acl` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`allow` int(1) DEFAULT 1 COMMENT '0: deny, 1: allow',
`ipaddr` varchar(60) DEFAULT NULL COMMENT 'IpAddress',
`username` varchar(100) DEFAULT NULL COMMENT 'Username',
`clientid` varchar(100) DEFAULT NULL COMMENT 'ClientId',
`access` int(2) NOT NULL COMMENT '1: subscribe, 2: publish, 3: pubsub',
`topic` varchar(100) NOT NULL DEFAULT '' COMMENT 'Topic Filter',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建成功后,查看一下表结构,
mysql> desc mqtt_acl;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| allow | int(1) | YES | | 1 | |
| ipaddr | varchar(60) | YES | | NULL | |
| username | varchar(100) | YES | | NULL | |
| clientid | varchar(100) | YES | | NULL | |
| access | int(2) | NO | | NULL | |
| topic | varchar(100) | NO | | | |
+----------+------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
准备访问控制数据
现在要制定以下规则:
- 设定所有用户不可以订阅系统主题,除了从特定机器
10.211.55.10
发起的连接除外; - 应用的主题设计为
/smarthome/$clientId/temperature
,设定一条规则只允许相同的clientId
的设备才可以对它自己的主题进行发布消息操作
# 所有用户不可以订阅系统主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (0, NULL, '$all', NULL, 1, '$SYS/#');
# 允许10.211.55.10上发起的连接订阅系统主题
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, '10.211.55.10', NULL, NULL, 1, '$SYS/#');
# 允许设备只对自己的主题进行发布消息
INSERT INTO mqtt_acl (allow, ipaddr, username, clientid, access, topic) VALUES (1, NULL, NULL, NULL, 1, '/smarthome/%c/temperature');
修改配置
打开配置文件 /etc/emqx/emqx.conf
,将 ACL 的规则匹配变为:不匹配则不允许。
mqtt.acl_nomatch = deny
打开配置文件 /etc/emqx/plugins/emqx_auth_mysql.conf
,设置 SQL 语句如下,
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 后台日志才可以进行判断。
mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 128
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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
订阅系统主题,成功收到所有的系统消息。
# mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "\$SYS/#" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 0
Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
emqx@127.0.0.1
Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
2.4.3
Client test_username1 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
EMQ X Broker
Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/uptime', ... (22 bytes))
17 minutes, 14 seconds
Client test_username1 received PUBLISH (d0, q0, r0, m0, '$SYS/brokers/emqx@127.0.0.1/datetime', ... (19 bytes))
2018-11-13 02:14:03
测试设备操作自己的主题
订阅失败,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息。
# mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/user1/temperature" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/user1/temperature, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 128
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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 的出错信息表示订阅成功。
# mosquitto_sub -h 10.211.55.10 -u test_username1 -i test_username1 -P test_password -t "/smarthome/test_username1/temperature" -d
Client test_username1 sending CONNECT
Client test_username1 received CONNACK
Client test_username1 sending SUBSCRIBE (Mid: 1, Topic: /smarthome/test_username1/temperature, QoS: 0)
Client test_username1 received SUBACK
Subscribed (mid: 1): 0
Redis 访问控制
emqx_auth_redis 插件为基于 Redis 数据库的访问控制插件。Redis 里现在只支持白名单配置,就是只有在 Redis 中列出的规则才可以对某主题具有访问权限。
用户认证(鉴权)和访问控制中用的是同一个配置文件
超级用户配置
如果按照在认证中的配置,超级用户是通过获取保存在 Redis 数据库中 mqtt_user
数据结构的 is_superuser
字段中的值判断该用户是否为超级用户。打开 etc/plugins/emqx_auth_redis.conf
,配置 super 查询。超级用户不受 ACL 的控制。
## Super Redis command
auth.redis.super_cmd = HGET mqtt_user:%u is_superuser
ACL 配置
准备 ACL 数据
采用 Redis 的 Hash 来存储 ACL 信息,格式如下:
- username:用户名,表示客户端的用户名称
- topicname:主题名
- [1|2|3]:1为订阅;2为发布;3为订阅和发布
HSET mqtt_acl:username topicname [1|2|3]
如下所示,为用户名为 user1
的客户端设定可以订阅系统主题 $SYS/#
。
## 设定主题 $SYS/# 的权限为可以订阅
127.0.0.1:6379[2]> HMSET mqtt_acl:userid_001 $SYS/# 1
OK
## 取出主题 $SYS/# 的权限
127.0.0.1:6379> HMGET mqtt_acl:userid_001 $SYS/#
1) "1"
## 设定主题 $SYS/# 的权限为可以发布
127.0.0.1:6379> HMSET mqtt_acl:userid_001 /smarthome/%c/temperature 2
OK
## 取出主题 $SYS/# 的权限
127.0.0.1:6379> HMGET mqtt_acl:userid_001 /smarthome/%c/temperature
1) "2"
修改配置文件
打开配置文件 /etc/emqx/emqx.conf
,将 ACL 的规则匹配变为:不匹配则不允许。
mqtt.acl_nomatch = deny
打开 etc/plugins/emqx_auth_redis.conf
,配置 ACL 查询:
## %u: 用户名
## %c: 客户端ID
auth.redis.acl_cmd = HGETALL mqtt_acl:%u
保存配置文件后,激活 emqx_auth_redis插件,并重启 EMQ X 服务。
测试系统主题
请注意订阅系统主题的时候,在 mosquitto 客户端需要对主题的字符$
前加入转义符 \
,变成 \$SYS/#
,命令如下所示。订阅成功后能接收到系统主题上的消息。
# mosquitto_sub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "\$SYS/#" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client userid_001 received SUBACK
Subscribed (mid: 1): 0
Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
emqx@127.0.0.1
Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
2.4.3
Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
EMQ X Broker
在 Redis 中把该用户对系统主题的权限设置为只能发布。
127.0.0.1:6379> HMSET mqtt_acl:userid_001 $SYS/# 2
OK
订阅失败,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息。
# mosquitto_sub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "\$SYS/#" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client userid_001 received SUBACK
Subscribed (mid: 1): 128
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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 禁止的消息
# mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_002/temperature" -m "hello" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_002/temperature', ... (5 bytes))
Client userid_001 sending DISCONNECT
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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 出错信息,发送消息成功。
# mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_001/temperature" -m "hello" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_001/temperature', ... (5 bytes))
Client userid_001 sending DISCONNECT
MongoDB 访问控制
emqx_auth_mongo 插件为基于 Mongo 数据库的访问控制插件。MongoDB 里现在只支持白名单配置,就是只有在 MongoDB 中列出的规则才可以对某主题具有访问权限。
用户认证(鉴权)和访问控制中用的是同一个配置文件
超级用户配置
打开 etc/plugins/emqx_auth_mongo.conf
,配置 super 查询:
## 是否开启
auth.mongo.super_query = on
## super 信息所在集合
auth.mongo.super_query.collection = mqtt_user
## super 字段
auth.mongo.super_query.super_field = is_superuser
## 查询指令
auth.mongo.super_query.selector = username=%u
当查询结果中的 is_superuser
(super_field 字段)为 true
时,表示当前用户为。
ACL 配置
准备 ACL 数据
MongoDB 中定义的 ACL 数据结构如下,
- username:登录的用户名称
- clientid:登录的客户端id
- publish:数组格式,该用户允许的发布的消息主题名称列表,如果没有可以忽略该字段。
- subscribe:数组格式,该用户允许的订阅的消息主题名称列表,如果没有可以忽略该字段。
- pubsub:数组格式,该用户允许的既可以发布、也可以订阅的消息主题名称列表,如果没有可以忽略该字段。
{
username: "username",
clientid: "clientid",
publish: ["topic1", "topic2", ...],
subscribe: ["subtop1", "subtop2", ...],
pubsub: ["topic/#", "topic1", ...]
}
在 MongoDB 中插入以下 ACL 信息,
- 用户名和客户端 ID 为 userid_001 的用户,可以在自己的主题
/smarthome/%c/temperature
上进行发布操作;也可以在系统主题上进行订阅操作。
> use mqtt
switched to db mqtt
> db.mqtt_acl.insert({ username: 'userid_001', clientid: "userid_001", publish:["/smarthome/%c/temperature"], subscribe: ["$SYS/#"], pubsub: [] })
WriteResult({ "nInserted" : 1 })
> db.mqtt_acl.findOne({ username: 'userid_001' })
{
"_id" : ObjectId("5bea67bb1d2e8f30aa829072"),
"username" : "userid_001",
"clientid" : "userid_001",
"publish" : [
"/smarthome/%c/temperature"
],
"subscribe" : [
"$SYS/#"
],
"pubsub" : [ ]
}
修改配置文件
打开配置文件 /etc/emqx/emqx.conf
,将 ACL 的规则匹配变为:不匹配则不允许。
mqtt.acl_nomatch = deny
打开 etc/plugins/emqx_auth_mongo.conf
,配置 ACL 查询,
## 是否开启 ACL 控制
auth.mongo.acl_query = on
## ACL 信息所在集合
auth.mongo.acl_query.collection = mqtt_acl
## 查询指令
auth.mongo.acl_query.selector = username=%u
保存配置文件后,激活 emqx_auth_mongodb 插件,并重启 EMQ X 服务。
测试系统主题
请注意订阅系统主题的时候,在 mosquitto 客户端需要对主题的字符$
前加入转义符 \
,变成 \$SYS/#
,命令如下所示。订阅成功后能接收到系统主题上的消息。
# mosquitto_sub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "\$SYS/#" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending SUBSCRIBE (Mid: 1, Topic: $SYS/#, QoS: 0)
Client userid_001 received SUBACK
Subscribed (mid: 1): 0
Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers', ... (14 bytes))
emqx@127.0.0.1
Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/version', ... (5 bytes))
2.4.3
Client userid_001 received PUBLISH (d0, q0, r1, m0, '$SYS/brokers/emqx@127.0.0.1/sysdescr', ... (12 bytes))
EMQ X Broker
测试设备操作自己的主题
用户 userid_001
向不是自己的主题 /smarthome/userid_002/temperature
发送消息,结合 EMQ X 的后台日志可以得知 ACL 禁止的消息
# mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_002/temperature" -m "hello" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_002/temperature', ... (5 bytes))
Client userid_001 sending DISCONNECT
EMQ X 后台日志(/var/log/emqx/error.log
)出错信息。
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 出错信息,发送消息成功。
# mosquitto_pub -h 10.211.55.10 -u userid_001 -i userid_001 -P public -t "/smarthome/userid_001/temperature" -m "hello" -d
Client userid_001 sending CONNECT
Client userid_001 received CONNACK
Client userid_001 sending PUBLISH (d0, q0, r0, m1, '/smarthome/userid_001/temperature', ... (5 bytes))
Client userid_001 sending DISCONNECT
附录:认证/访问控制占位符对照表
占位符 | 对照参数 |
---|---|
%c | MQTT clientid |
%u | MQTT username |
%p | MQTT password |
%a | ACL IP 地址 |
%A | ACL access 方式,1: 发布 2:订阅 3:发布/订阅 |
%t | ACL 中 MQTT topic |