【Mosquitto】MQTTブローカの構築・動作確認 (ユーザ認証・TLS暗号化まで)

# はじめに
- Ubuntu にて MQTTブローカの構築手順を記載する。
- ユーザ認証やMQTTSでの暗号化対応までを記載し、実用に耐えるブローカ構築を目指す。

# 動作環境
- Ubuntu 22.4 LTS
- AWS EC2 の Ubuntu 22.4 AMI を適用した起動直後のインスタンスを使用

# mosquittoの導入
はじめにmosquitto本体を導入する。

- まずは、EC2起動後のおまじないを実行
``` bash
sudo apt update -y
sudo apt upgrade -y
```

- mosquitto および mosquitto-clients を導入
``` bash
sudo apt install -y mosquitto mosquitto-clients
```

- mosquitto の起動
``` bash
sudo systemctl start mosquitto
sudo systemctl status mosquitto
```

※以下の応答が返れば正常に起動している
``` bash
● mosquitto.service - Mosquitto MQTT Broker
     Loaded: loaded (/lib/systemd/system/mosquitto.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-05-18 07:44:19 UTC; 1min 57s ago
       Docs: man:mosquitto.conf(5)
             man:mosquitto(8)
    Process: 423 ExecStartPre=/bin/mkdir -m 740 -p /var/log/mosquitto (code=exited, status=0/SUCCESS)
    Process: 432 ExecStartPre=/bin/chown mosquitto /var/log/mosquitto (code=exited, status=0/SUCCESS)
    Process: 434 ExecStartPre=/bin/mkdir -m 740 -p /run/mosquitto (code=exited, status=0/SUCCESS)
    Process: 437 ExecStartPre=/bin/chown mosquitto /run/mosquitto (code=exited, status=0/SUCCESS)
   Main PID: 445 (mosquitto)
```

- 再起動後も自動起動が必要な場合は以下を実行する。
``` bash
sudo systemctl enable mosquitto
```

この状態ではクライアント認証なし、暗号化なしとザルな状態。

# ユーザ認証の導入
接続するユーザを認証する機能を導入する。
クライアント証明書は使用せず、ユーザ名・パスワードによる認証とする。
後ほどパスワードファイルに記載されたユーザのみが接続を許可する設定を追加する。

- はじめてユーザを追加する場合は以下のコマンドを実行する。
- コマンド中のtestuserは任意のユーザ名に変更する。
- パスワードを求められるので入力する。
``` bash
sudo mosquitto_passwd -c /etc/mosquitto/pwfile testuser
```

※-c オプションはファイルの新規作成を示す。ユーザを追加する場合は -c オプションを抜く。

``` bash
sudo mosquitto_passwd /etc/mosquitto/pwfile testuser2
```

- ファイルを確認する。
``` bash
cat /etc/mosquitto/pwfile
```
※パスワードはハッシュ値で表示される。
```
testuser:$7$101$MH2V8Vy9uKESnZYk$hCJvrCRWU8LrSrsPXkjZFdLZHjcwBsieF6dsn+y2/lmcEFxDQxZH9KY+yNdxs/pAjkH2NKakIwXrg9judeB6ow==
```

以下は余談です。
- ハッシュ値は入力(今回はパスワード)に対して不可逆的に生成される。
- ハッシュ値で保存することでパスワードが漏れることを防止する。
- 同じ入力には同じハッシュ値が生成されるため、ハッシュ値の一致を検証することとなる。

# TLSの対応 (openssl を用いた証明書作成)
TLS対応を実施する。MQTTSでの通信が可能なブローカに仕上げていく。
本手順ではTLS対応に必要な、**ルートCA証明書**、**サーバ証明書**、**サーバ秘密鍵**を作成する。
今回はテスト用なのでルートCAは自前で開局する。

- 初めにディレクトリを切って、ルートCAの秘密鍵(ca.key)を作成する。
``` bash
mkdir for_cert
cd for_cert
openssl genrsa -des3 -out ca.key 2048
```
※パスフレーズを求められる。設定しないと次のコマンドが通らないので注意。
``` bash
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
```

- 証明書作成に必要な ca.csr ファイルを作成する。
``` bash
openssl req -new -key ca.key -out ca.csr -sha256
```
- 所属などを聞かれる。
- 基本は何を入れても良いが Common Name には注意が必要。
- CA側Common Nameと後述のサーバ側Common Nameが一緒だとpahoでの動作確認時に self-signed が理由で接続できない。
``` bash
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
```


- **ルートCA証明書(CA.crt)** を生成する
- -days後ろには証明書が有効な日数を打ち込む(今回は100年)
- ca.csrを入力し、ca.keyで署名して、ca.crtを出力している
``` bash
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt -days 36500 -sha256
```

- 同様に **サーバの暗号鍵(server.key)** と 対応する server.csr を生成する。
``` bash
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -sha256
```
- こちらも同様に所属などを聞かれる。
- Common Name はルートCA側とは異なる値を入力する。
- 今回は動作確認も同じサーバで実施するため localhost とする。
- なお、実際のCommon Name はドメイン名を入れる必要がある。(例：*.example.com)
``` bash
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

```

- 最後に **サーバ証明書(server.crt)** を作成する。
- ここでは server.csr を入力し、ca.keyでサインし、server.crt を出力する。
``` bash
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 36500
```

- 生成したファイルの権限を確認する
``` bash
ls -lh
```
※server.key の権限が `-rw-------` の場合は権限変更しないとmosquitto が立ち上がらない。 
``` bash
total 24K
-rw-rw-r-- 1 ubuntu ubuntu 1.2K May 18 08:11 ca.crt
-rw-rw-r-- 1 ubuntu ubuntu  985 May 18 08:11 ca.csr
-rw------- 1 ubuntu ubuntu 1.9K May 18 08:11 ca.key
-rw-rw-r-- 1 ubuntu ubuntu 1.2K May 18 08:32 server.crt
-rw-rw-r-- 1 ubuntu ubuntu  985 May 18 08:12 server.csr
-rw------- 1 ubuntu ubuntu 1.7K May 18 08:11 server.key
```
権限変更は以下で実施する。
``` bash
chmod 644 server.key
```

- 作成したファイルを mosquitto 関連のファイルを格納するフォルダへとコピーする。
``` bash
sudo cp ca.crt server.crt server.key /etc/mosquitto/certs/
```

これで必要なファイルの生成が完了した。

以下は余談です。
##### ルートCA証明書
- ルートCA証明書はそのサーバを信頼したCA(認証局)を示す。
- 今回はオレオレ証明書だが、本運用では公的機関や正式な認証局からサインをもらう必要がある。

##### サーバ証明書
- サーバ証明書は接続するサーバ=MQTTブローカ が本来意図した接続先であることを示す。
- サーバ証明書はサーバ=MQTTブローカ が接続時にクライアントへ送信され、中には接続先のドメインの情報と公開鍵が含まれる。
- ドメインが一致しない場合や、サインしているルートCAが信頼に足らない場合はクライアントはサーバに接続しない。

##### サーバ秘密鍵
- サーバ証明書に含まれる公開鍵とセットとなる秘密鍵である。
- セットである公開鍵で暗号化されたデータを復号できる(原理上)唯一の鍵である。
- サーバ秘密鍵が漏れない限り、前記サーバ証明書を信頼して接続するサーバとの接続は安心と言える。

# mosquittoの設定・立ち上げ
ここでは mosquitto の設定ファイルを書き換え、ユーザ認証とTLS対応を有効にした上で、再立ち上げを行う。
- mosquitto の設定ファイルを編集する。
```
sudo vi /etc/mosquitto/mosquitto.conf
```
- コメント以降の部分を追記する。
``` bash
# Place your local configuration in /etc/mosquitto/conf.d/
#
# A full description of the configuration file is at
# /usr/share/doc/mosquitto/examples/mosquitto.conf.example

pid_file /run/mosquitto/mosquitto.pid

persistence true
persistence_location /var/lib/mosquitto/

log_dest file /var/log/mosquitto/mosquitto.log

include_dir /etc/mosquitto/conf.d

# ***ここから追加部分***

# ユーザ認証の有効化
allow_anonymous false # 誰でも接続できるを無効化
password_file /etc/mosquitto/pwfile # パスワードファイルを指定

# TLSの有効化
listener 8883 # 8883で待ち受け(MQTTSのデフォルト)
# ファイルパスの指定
cafile    /etc/mosquitto/certs/ca.crt
certfile  /etc/mosquitto/certs/server.crt
keyfile   /etc/mosquitto/certs/server.key
```

- mosquitto を再起動する
``` bash
sudo systemctl restart mosquitto
sudo systemctl status mosquitto
```

※mosquitto がうまく立ち上がらない場合は log ファイルを確認するのが良い。
``` bash
sudo cat /var/log/mosquitto/mosquitto.log
```
- 権限によりserver.keyが読み込めない場合の表示
``` bash
1684400892: Error: Unable to load server key file "/etc/mosquitto/certs/server.key". Check keyfile.
1684400892: OpenSSL Error[0]: error:8000000D:system library::Permission denied
1684400892: OpenSSL Error[1]: error:10080002:BIO routines::system lib
1684400892: OpenSSL Error[2]: error:0A080002:SSL routines::system lib
```
- 指定したディレクトリに cs.crt が存在しない場合の表示
``` bash
1684401890: Error: Unable to load CA certificates. Check cafile "/etc/mosquitto/certs/ca.crt".
1684401890: OpenSSL Error[0]: error:80000002:system library::No such file or directory
1684401890: OpenSSL Error[1]: error:10000080:BIO routines::no such file
1684401890: OpenSSL Error[2]: error:05880002:x509 certificate routines::system lib
```

# mqtt-pahoを用いた動作確認
ここでは python および mqtt-paho を活用して動作確認を行う。

- paho-mqttをインストールする
```
sudo apt install python3-pip -y
pip install paho-mqtt
```

- 以下の pub/sub.py を作成する。

``` python:pub.py
import paho.mqtt.client as mqtt
import time
import random
import ssl

# MQTT Broker
HOST = 'localhost'
PORT = 8883
KEEP_ALIVE = 60
TOPIC = 'test'
USER = 'testuser'
PASSWORD = 'passwordを入力'
CA_CERT = '/etc/mosquitto/certs/ca.crt'

def on_connect(mqttc, obj, flags, rc):
    print('connected: ',rc)

# Broker 接続
mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.tls_set(ca_certs=CA_CERT)
mqttc.tls_insecure_set(True)
mqttc.username_pw_set(username=USER, password=PASSWORD)
mqttc.connect(HOST,PORT,KEEP_ALIVE)

while True:
    test_data1 = random.randint(0, 100)
    test_data2 = random.randint(0, 100)
    test_data3 = random.randint(0, 100)
    mqttc.publish(TOPIC, f'{{"test_data1": {test_data1}, "test_data2": {test_data2}, "test_data3": {test_data3},}}')
    print('publish')
    time.sleep(3)
```

``` python:sub.py
import paho.mqtt.client as mqtt
import ssl

# MQTT Broker
HOST = 'localhost'
PORT = 8883
KEEP_ALIVE = 60
TOPIC = 'test'
USER = 'testuser'
PASSWORD = 'passwordを入力'
CA_CERT = '/etc/mosquitto/certs/ca.crt'

def on_connect(mqttc, obj, flags, rc):
    print('connected: ',rc)

def on_message(mqttc, obj, msg):
    print(msg.topic + ' ' + str(msg.qos) + ' ' + str(msg.payload.decode('utf-8')))

# Broker 接続
mqttc = mqtt.Client(protocol=mqtt.MQTTv311)
mqttc.on_connect = on_connect
mqttc.on_message = on_message

mqttc.tls_set(ca_certs=CA_CERT)
mqttc.username_pw_set(username=USER, password=PASSWORD)
mqttc.tls_insecure_set(True)

mqttc.connect(HOST,PORT,KEEP_ALIVE)
mqttc.subscribe(TOPIC)
mqttc.loop_forever()
```

- pub/sub.py を実行する。

``` bash:pub.pyの実行
python3 pub.py 
publish
publish
publish
publish
```

``` bash:sub.pyの実行
python3 sub.py
connected:  0
test 0 {"test_data1": 91, "test_data2": 72, "test_data3": 12,}
test 0 {"test_data1": 69, "test_data2": 18, "test_data3": 63,}
test 0 {"test_data1": 27, "test_data2": 4, "test_data3": 66,}
test 0 {"test_data1": 83, "test_data2": 98, "test_data3": 86,}
```

上記が表示されれば問題なく動作している。

初投稿なのに記事が長くなってしまった。
お疲れさまでした。お付き合いいただきありがとうございました。