mosquitto认证模块编译

因为公司项目要用到MQTT进行通信,试了几个比较出名的Broker之后,最后还是决定选择开源的mosquitto,但是因为mosquitto本身不带MySQL认证功能,期间踩了些坑,留个编译过程记录一下。

写在开始

其实我们的业务需求也不算复杂,因为机器可能不止少数几个,不能采用写死配置文件的形式进行认证,想找个可以支持第三方认证的MQTT Broker,比如MySQL或者Redis,这样可以动态管理连接到Broker的用户。

在决定使用mosquitto之前,我也试过了国内比较有名的emqx,因为看他们官网文档,操作起来方便,功能还强大,安装也容易,想试试这个对我们来说新出的产品,但是在配置好emqx以及相关的MySQL数据库及对应的表之后发现,无论怎么设置都无法连接到Broker,查看日志出现如下问题:

2019-01-22 15:36:49.837 [error] 0823d407-4506-4ad8-91a3-c9459ec9a1fe1548126026622@192.168.0.167:52079 [MQTT] Username '45360' login failed for auth_modules_not_found

而手动加载认证插件又出现这个提示:

[root@LocalServer ~]# emqx_ctl plugins load emqx_auth_mysql
load plugin error: already_started
=ERROR REPORT==== 22-Jan-2019::15:37:51.609510 ===
[Plugins] Plugin emqx_auth_mysql is already started

不管是目前最新版的emqx 3.0还是之前的emqttd 2.x,遇到几个bug在各个搜索引擎都找不到解决方案,这都算了,官网留的几个QQ群,加入之后,里面是有官方人员在,但是并不会回答群友的问题,除了github也找不到对应的论坛,出于长远考虑,怕以后遇到问题解决起来成本太高,还是决定用回开源的mosquitto,而CentOS7自带的mosquitto并没有包含auth插件,于是才有了这篇编译mosquitto的mosquitto-auth-plug流水账。

编译前准备

我们正式服务器打算采用CentOS7,所以本地测试服务器也是采用CentOS7,本篇编译所涉及的操作也是基于CentOS7进行。

为了方便,我们可以先行添加官方的CentOS7源:

vim /etc/yum.repos.d/mqtt.repo

[home_oojah_mqtt]
name=mqtt (CentOS_CentOS-7)
type=rpm-md
baseurl=http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-7/
gpgcheck=1
gpgkey=http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-7/repodata/repomd.xml.key
enabled=1

安装官方的mosquitto:

yum install mosquitto

CentOS7安装编译环境,已经安装了的可以忽略:

yum install gcc automake autoconf libtool make 

下载mosquitto源码,下载好解压就行,没什么说的:
http://mosquitto.org/download/

mosquitto-auth-plug源码:

git clone https://github.com/jpmens/mosquitto-auth-plug

因为我是打算使用MySQL作为认证源,编译mosquitto-auth-plug需要依赖MySQL的开发库,而我们使用的是MySQL官方提供的MySQL社区版(相关文章:CentOS7安装配置MySQL),安装MySQL开发库文件:

yum install mysql-community-devel

然后是安装mosquitto相关的依赖:

yum install libmosquitto-devel.x86_64

配置及编译mosquitto-auth-plug

其实配置这个很简单,只需要修改其中一个文件就行:

cp config.mk.in config.mk

vim config.mk

配置文件默认选择的就是MySQL,所以上方的数据库信息不需要改,要改的只是MOSQUITTO_SRCOPENSSLDIR就行,前者复制上方下载解压后的mosquitto源码目录就行,后者可以通过openssl version -a得到,CentOS7默认在/etc/pki/tls

修改好之后在mosquitto-auth-plug执行make就行,如果没有意外,能在目录下方得到一个auth-plug.so,这就是我们需要的插件文件。

将得到的文件复制到mosquitto相关目录:

cp auth-plug.so /var/lib/mosquitto

mosquitto MySQL认证配置

如果是按上方步骤安装的官方的mosquitto的话,mosquitto的默认配置文件在是/etc/mosquitto/mosquitto.conf,但是看配置文件推荐我们最好是在/etc/mosquitto/conf.d/目录下定义我们的配置,所以:

vim /etc/mosquitto/conf.d/auth.conf

auth_plugin /var/lib/musquitto/auth-plug.so

auth_opt_backends mysql

auth_opt_host 192.168.0.129
auth_opt_port 3306
auth_opt_dbname dbname
auth_opt_user dbuser
auth_opt_pass dbpassword

auth_opt_userquery SELECT pw FROM mqtt_users WHERE username = '%s'
auth_opt_superquery SELECT COUNT(*) FROM mqtt_users WHERE username = '%s' AND super = 1
auth_opt_aclquery SELECT topic FROM acls WHERE (username = '%s') AND (rw >= %d)
auth_opt_anonusername AnonymouS

上面数据库部分根据实际情况修改即可。

而涉及到的两个表,结构及初始数据可以在这里找到:https://github.com/jpmens/mosquitto-auth-plug/blob/master/examples/mysql.sql

这个插件密码使用的处理方式不是普通的md5或者bcrypt,它用的是一种我以前都没见过的叫做PBKDF2的算法,如果只是简单的使用很简单,只要执行插件源码目录下面的np程序即可,比如说加密123456:

[root@LocalServer mosquitto-auth-plug]# ./np
Enter password:
Re-enter same password:
PBKDF2$sha256$901$xKEbqtQj/1A8jlrW$Lo84WQjDJRlgUpH0VgrQSwluo3Yh4brl

只要配合别的字段插入到数据库就行了。

mosquitto-auth-plug的acls表踩的坑

网上各种教程以及插件官方文档也是说只需要建立acls表以及将表自带的数据插入进去就可以,比如说:

INSERT INTO acls (username, topic, rw) VALUES ('jjolie', 'loc/jjolie', 1);
INSERT INTO acls (username, topic, rw) VALUES ('jjolie', 'loc/ro', 1);
INSERT INTO acls (username, topic, rw) VALUES ('jjolie', 'loc/rw', 2);

但是,我实际操作之后发现,如果将acls表的rw字段无论是设置成1还是2,登录之后的用户不管怎样都无法订阅自己发布的主题,而命令行输出是这样的:

[root@LocalServer conf.d]# mosquitto -c /etc/mosquitto/mosquitto.conf
1548226914: mosquitto version 1.5.5 starting
1548226914: Config loaded from /etc/mosquitto/mosquitto.conf.
1548226914: |-- *** auth-plug: startup
1548226914: |-- ** Configured order: mysql

1548226914: |-- }}}} MYSQL
1548226914: |-- SSL is disabled
1548226914: Opening ipv4 listen socket on port 1883.
1548226914: Opening ipv6 listen socket on port 1883.
1548226915: New connection from 192.168.0.167 on port 1883.
1548226915: |-- mosquitto_auth_unpwd_check(test)
1548226915: |-- ** checking backend mysql
1548226915: |-- getuser(test) AUTHENTICATED=1 by mysql
1548226915: New client connected from 192.168.0.167 as df340202-7f22-429d-b69f-10ae7f4ec4371548226911318 (c1, k10, u'test').
1548226917: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226917: |-- aclcheck(test, a, 4) AUTHORIZED=0 by none
1548226917: |--  Cached  [0F3AC5804940450E6618E24965E0D9366D04F994] for (client id not available,test,4)
1548226918: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226918: |--   mysql: topic_matches(a, a) == 1
1548226918: |-- aclcheck(test, a, 2) trying to acl with mysql
1548226918: |-- aclcheck(test, a, 2) AUTHORIZED=1 by mysql
1548226918: |--  Cached  [159AEA2A69EF3800BD6FC08E9217324386EAECA0] for (client id not available,test,2)
1548226918: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226918: |-- aclcheck(test, a, 2) CACHEDAUTH: 0
1548226919: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226919: |-- aclcheck(test, a, 2) CACHEDAUTH: 0
1548226919: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226919: |-- aclcheck(test, a, 2) CACHEDAUTH: 0
1548226919: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226919: |-- aclcheck(test, a, 2) CACHEDAUTH: 0
1548226919: |-- mosquitto_auth_acl_check(..., client id not available, test, a, MOSQ_ACL_WRITE)
1548226919: |-- aclcheck(test, a, 2) CACHEDAUTH: 0
^C1548227016: mosquitto version 1.5.5 terminating
1548227016: Saving in-memory database to /var/lib/mosquitto/mosquitto.db.

明明是能正确登录到Broker,但是就是收不到自己发布的消息,查了好久才找到一个问题的解答说,是因为mosquitto 1.5之后MOSQ_ACL_SUBSCRIBE有了变化,可选的值不再只有以前的1和2,变成了0-7,这问题整整花费了我3天的时间,我也是呵呵了。

这是新版的ALC策略:

0: no access
1: read
2: write
3: read and write
4: subscribe
5: read & subscribe
6: write & subscribe
7: read, write and subscribe

如果和我一样,各项检查都没问题,而就是无法subscribe topic的朋友,可以试着更改下上面的rw值。
来源:https://stackoverflow.com/a/54120778/10954188

mosquitto-auth-plug配合Laravel使用PHP生成密码

上面说了,因为mosquitto-auth-plug这个插件不是使用普通的md5等函数进行处理密码,这就导致我们操作数据库等需要按照这个插件的方式来处理。

说实话,我也找了很久要怎么用PHP来处理PBKDF2这种格式,网上找了个库发现生成的密码格式都和官方预设的那几个不一样,放狗搜了一把之后发现,其实插件作者在插件源码目录就有各种语言的加密方式的实现,如下:

cd mosquitto-auth-plug/contrib

[root@LocalServer contrib]# ls
C#  genhash.php  golang  java  nodejs  python  python3  ruby  tinycdb-0.78

还是PHP简单粗暴,一个文件就解决了,准确的说是两个函数就搞定了。如果想要整合进Laravel的话,只需要将genhash.php里面的内容想办法添加到Laravel框架能加载到的文件就行。

总结

mosquitto还是挺强大的,配置MySQL认证虽然过程有点曲折,但解决问题的过程中也学到了不少东西,也算是对得起花费的时间吧。