简介
MQTT是一个在机器和机器之间传递消息的协议,为实现物联网设备间轻量级的发布/订阅通信而设计。广泛应用于汽车地理位置追踪,智能家居自动化,环境传感网络,以及各项公共事业的数据收集。
Mosquitto是一个流行的MQTT服务器(在MQTT协议中叫中继器),拥有强大的社区支持,易于安装、配置。
在此教程中,我们将安装Mosquito,从Let‘s Encrypt获取证书,搭建我们自己的中继器,采用密码认证,采用SSL来确保MQTT通信安全。
前提条件
在开始这个教程之前,你需要:
- 一个Ubuntu16.04服务器,非root并且开启了sudo的用户,设置了基本的防火墙,具体请参考这个Ubuntu16.04服务器搭建教程。
- 一个指向了你服务器的域名,可以参照如何在DigitalOcean上创建一个域名。这篇教程将使用
mqtt.example.com
代替。
步骤1 安装Mosquitto
Ubuntu16.04默认软件仓库的Mosquitto版本就挺新的。用你的非root用户登录并使用 apt-get
安装Mosquitto.
$ sudo apt-get install mosquitto mosquitto-clients
默认情况下,Ubuntu会在安装后启动Mosquitto服务。我们先测试一下默认配置。使用刚安装的Mosquitto客户端订阅中继器上的一个主题。
主题是发送和订阅消息的标签。它们按层次设置,比如你可以将主题设置为sensors/outside/temp
、sensors/outside/humidity
。如何设置主题取决于的你需要。教程通篇将用一个简单的主题test
来测试配置变化。
再打开一个服务器命令窗口,这样你就有两个并列的窗口了。在新的命令行中,使用mosquitto_sub
来订阅test
主题:
mosquitto_sub -h localhost -t test
-h
用于指定MQTT服务器的主机名,-t
用于指定主题。当按下回车键之后,没有任何输出,这是因为mosquitto_sub
正在等待消息送达。回到另外一个命令行,发布一条消息:
mosquitto_pub -h localhost -t test -m "Hello world"
mosquitto_pub
的选项和mosquitto_sub
的一样,除了这次我们用了一个-m
选项来指定消息内容。按下回车,将会看到Hello world在另外一个命令行中出现。你刚刚发送了你的第一条MQTT消息。
在第二个命令行输入CTRL + C
,退出mosquitto_sub
, 但保持和服务器的连接。在步骤五将再次使用到它。
接下来,我们将用通过Certbot的SSL来确保我们Mosquitto的安全,Certbot是Let‘s Encrypt的新版客户端。
步骤2 为了获得Let‘s Encrypt证书,安装Certbot
Let‘s Encrypt是一个通过自动化的API,提供免费的SSL证书的服务。很多客户端可以调用这个API。Ubuntu的默认仓库包含了官方的客户端,但有点过时了,并且缺少了一个我们需要的新特性。
因此,我们从一个Ubuntu PPA(Personal Package Archive)安装官方的客户端。很多替代仓库的打包了更新的版本,或者是小众软件。首先,添加仓库:
sudo add-apt-respository ppa:certbot/certbot
需要敲一下回车来接受安装。接下来,更新包列表来获取新仓库的包信息。
sudo apt-get update
最后,安装Let‘s Encrypt的官方客户端,叫做certbot
。
sudo apt-get install certbot
certbot
已经安装完成,接下来运行它来获得我们所需的证书。
步骤3 运行Certbot
certbot
需要响应一个Let‘s Encrypt发出的密码质询,用来证明我们对这个域名有控制权。它使用端口80
(HTTP) 且/或 443
(HTTPS)来完成。我们将仅使用端口80
,所以我们需要在这个端口上允许入网流量。
$ sudo ufw allow http
Rule added
现在我们可以运行Certbot来获得证书。使用--standalone
选项告诉Certbot,让它自己完成HTTP质询请求, --standalone-supported-challenges http-01
限制了通讯端口为80
。-d
用来制定要认证的域名,certonly
告诉Certbot只需获取证书,无需进行其他配置步骤。
sudo certbot certonly --standalone --standalone-supported-challenges http-01 -d mqtt.example.com
运行上述命令时,命令行会提示你输入一个邮箱地址,并同意服务条款。再之后,你将会看到一条消息,告诉你处理成功,并且告知你证书的存放位置。
我们已经获得了证书,现在我们需要确保Certot在证书快过期时自动地更新他们。
步骤4 创建Certbot的自动更新策略
Let‘s Encrypt的证书有效期仅为90天。这是为了鼓励用户将他们的证书更新流程自动化。我们将创建一个定期运行的命令,检查证书过期情况并自动更新它们。
我们将通过cron
每天运行更新检查。这是运行周期性工作的一个系统服务。打开并编辑一个叫做crontab
的文件,来指定cron
要完成的工作。
sudo crontab -e
你将会看到选择一个文本编辑器的提示。选一个你喜欢的,然后将会看到默认的corontab
, 并且附带一个帮助说明。在文件的最后加入下面这一行,保存并关闭文件。
15 3 * * * certbot renew --noninteractive --post-hook "systemctl restart mosquitto"
15 3 * * *
表示每天上午3点15运行后面的命令。Certbot的renew
命令会检查服务器上安装的所有证书,并更新那些过期时间不足30天的。--noninteractive
告诉Certbot不要等待用户输入。
--post-hook "systemctl restart mosquitto"
将会重启Mosquitto,以采用最新的证书,但这条命令只会在证书被更新后才执行。这个post-hook
特性是旧版的Let‘s Encrypt客户端没有的。这也是我们从PPA而不是默认的Ubuntu库安装的原因。没有这一项,即使没有证书更新,我们也需要每天重启Mosquitto。
尽管MQTT客户端需要设置了自动重连,但避免每天无端地打断他们是非常明智的。
既然自动地更新证书已经配置妥当,我们就回过头来配置Mosquitto,让他更加安全。
步骤5 配置MQTT密码
接下来配置Mosquitto,启用密码。Mosquitto包含了一个工具,用来生成一个特殊的密码文件,叫做mosquitto_passwd
。这个命令将提示你为指定的用户名输入密码,并把结果保存在/etc/mosquitto/passwd
中。
sudo mosquitto_passwd -c /etc/mosquitto/passwd sammy
接下来,为Mosquitto打开一个新的配置文件,指定所有连接登录时所需的密码文件。
sudo nano /etc/mosquitto/conf.d/default.conf
上述命令会打开一个新的配置文件。把下面的内容粘贴进去:
allow_anonymous false
password_file /etc/mosquitto/passwd
allow_anonymous false
将禁用未认证的链接,password_file
一行指定了Mosquitto从何处获取用户和密码信息。保存并退出文件。
接下来,重启Mosquitto并测试变化。
sudo systemctl restart mosquitto
试着在不带密码的情况下发布一条消息:
mosquitto_pub -h localhost -t "test" -m "hello world"
这条消息会被拒绝:
Output
Connection Refused: not authorised.
Error: The connection was refused.
在用密码进行尝试之前,转到另一个命令行,使用用户名和密码订阅test
主题。
mosquitto_sub -h localhost -t test -u "sammy" -P "password"
连接将会建立,并等待消息到达。保持这个命令行处于打开状态, 因为我们将周期性地向它发送测试消息。
使用用户名和密码再次尝试发布消息:
mosquitto_pub -h localhost -t "test" -m "hello world" -u "sammy" -P "password"
这条消息将和步骤1中一样,成功发送。我们成功地给Mosquitto增加了密码保护功能。不幸的是,我们发送密码的时候未进行加密。我们接下来将通过增加SSL加密来解决这一问题。
步骤6 配置MQTT的SSL
为开启SSL加密,需要在Mosquitto配置里制定证书存放的位置。打开刚才开始的配置文件:
sudo nano /etc/mosquitto/conf.d/default.conf
在文件末尾粘贴下面诸行,保留已经添加的两行:
listener 1883 localhost
listener 8883
certfile /etc/letsencrypt/live/mqtt.example.com/cert.pem
cafile /etc/letsencrypt/live/mqtt.example.com/chain.pem
keyfile /etc/letsencrypt/live/mqtt.example.com/privkey.pem
我们在配置中添加了两个独立的listener
块。 第一块,listener 1883 localhost
,更新了处于端口1883
上的默认MQTT监听器,我们一直以来用的就是这一个。1883
是标准的非加密MQTT端口。这一行的localhost
部分表明Mosquitto只会把这个端口绑定到localhost接口上,所以无法从外部访问。外部请求应该已经被防火墙阻挡了,接下来会说明一下。
listener 8883
在端口8883建立了一个加密的监听器。这是MQTT+SSL(通常指MQTTS)的标准端口。接下来三行,certfile
,cafile
,keyfile
,都是为了将Mosquitto指向特定的Let‘s Encrypt文件,这些文件将被用于创建加密的连接。
保存并退出文件,重启Mosquitto以更新配置:
sudo systemctl restart mosquitto
更新防火墙,使其允许连接到端口8883。
sudo ufw allow 8883
接下来,使用mosquitto_pub
, 附带几个SSL的特定选项:
mosquitto_pub -h mqtt.example.com -t test -m "hello again" -p 8883 --capath /etc/ssl/certs/ -u "sammy" -P "password"
注意,这次使用了完整的主机名,而不是localhost。因为SSL证书是对mqtt.example.com发放的,如果我们尝试对localhost
创建一个安全链接,将会收到一条消息,说当前主机名和证书的主机名不一致。即便他们都指向了同一个Mosquitto服务器。
--capath /etc/ssl/certs
为mosquitto_pub
启动了SSL,并指定了去何处寻找根证书。这些证书被你的操作系统安装在特定的位置,所以这个地址在MacOS、Windows等系统上是不一样的。mosquitto_pub
采用根证书来来检查Mosquitto服务器的证书是否是否被Let‘s Encrypt的证书授权机构认证过。有一点需要注意的是,没有这个选项,mosquitto_pub
和mosquitto_sub
是不会尝试使用SSL链接的,即使你尝试连接到一个标准的安全端口8883
。一个类似的选项为--cafile
。
如果测试顺利的话,会看到hello again出现在命令行中。到这里,意味着已经完成了服务器搭建!如果你对拓展MQTT协议,使其可以基于websocket协议工作的话,请参考下面最终一步。
步骤7 配置通过WebSockets来使用MQTT(可选)
为了在web浏览器中通过javascript调用MQTT,协议通过适配,可以支持标准的WebSockets。如果你不需要这项功能,可以跳过此步骤。
我们需要在配置文件添加一个新的listener
块:
sudo nano /etc/mosquitto/conf.d/default.conf
在文件末尾,添加如下内容:
listener 8083
protocol websockets
certfile /etc/letsencrypt/live/mqtt.example.com/cert.pem
cafile /etc/letsencrypt/live/mqtt.example.com/chain.pem
keyfile /etc/letsencrypt/live/mqtt.example.com/privkey.pem
这和前面的块一致,除了端口号和protocol websockets
那一行。对于通过websockets方式提供的MQTT服务,没有官方的标准。但8083
是最常用的。
保存并退出这个文件,然后重启Mosquitto。
sudo systemctl restart mosquitto
接下来,在防火墙开启8083端口。
sudo ufw allow 8083
为测试此功能,需要用到一个公开的,基于浏览器的MQTT客户端。这种客户端有很多,但mqtt-admin
是最简单直接的。在浏览器打开mqtt-admin。将会看到设置窗口。
填入如下连接信息:
- Protocol选择wss(websocket secure)。
- Host填入Mosquitto服务器的域名。
- Port填入
8083
。 - User填入Mosquitto的用户名。
- Password填入你设置的密码。
- ClientID保持默认。
点击保存设置之后,mqtt-admin
会连接到Mosquitto服务器。接下来,在Topic处填入test
,在Payload处填入任意信息,然后点击Publish。将会在mosquitto_sub
命令行显示消息。
结语
到现在为止,我们已经建立起了一个安全,有密码保护机制的MQTT服务器,并设置了从Let‘s Encrypt服务自动更新SSL证书的方法。这对于你未来的任何项目都能够提供稳健和安全的消息平台。一些流行的软件和硬件在MQTT协议下运转良好:
- OwnTracks, 是一个开源的地理位置追踪APP。OwnTracks会定期向服务器报告位置信息。随后你可以存储并将这些信息展示在底图上,创建警报器,基于地理位置激活你的物联网设备。
- Node-RED, 一个基于浏览器的图形界面,用于将物联网设备连接起来。可以将一个节点的输出连接到另一个节点的输入上,并可以通过过滤器在不同的协议之间路由信息。存入数据库等等。
- ESP8266是一个便宜的WiFi微控制器,并具备MQTT能力。可以通过它发布温度信息到一个主题上,或者可以订阅大气压主题再或者当暴风雨来临时让蜂鸣器响起来。
这仅是MQTT生态系统的一部分知名案例。有更多的一些硬件或者软件采用了这种协议。如果你已经有了一个自己喜爱的硬件平台,或者软件编程语言,它可能就已经具备了MQTT能力。在让你的“物”彼此通信的过程中享受乐趣吧。