Mosquittoを使ったデバイス<->AWS IoTのブリッジ接続に関する考察
はじめに
サーバーレス開発部@大阪の岩田です。
Mosquittoを使ったAWS IoTへのブリッジ接続について調査する機会があったので、環境構築手順や調査結果についてご紹介します。
環境
今回構築する環境です。
EC2にMosquittoをインストールし、デバイス <-> EC2間をMQTTで接続、Mosquitto <-> AWS IoT間はMQTTSで接続します。 今回はEC2上にMosquitto環境を構築しましたが、現実的なシナリオであればデバイスが設置されているエッジ側にブリッジサーバーを構築することになるでしょう。
OS、ミドルウェアのバージョンは下記の通りです。
- OS: Amazon Linux2(amzn2-ami-hvm-2.0.20190115-x86_64-gp2)
- Mosquitto: 1.5.5-1.2.x86_64
環境構築手順
実際に環境を構築していきます。 手順はAWSのブログHow to Bridge Mosquitto MQTT Broker to AWS IoTに従って行いました。
AWS IoTのセットアップ
MosquittoからAWS IoTに接続するために諸々の事前準備を行います。
モノの登録
$ aws iot create-thing --thing-name mosquitto_bridge { "thingName": "mosquitto_bridge", "thingArn": "arn:aws:iot:ap-northeast-1:123456789012:thing/mosquitto_bridge", "thingId": "xxxxxxxxxx" }
ポリシーの作成
今回はiot:*
の権限を付与しておきます。
$ aws iot create-policy --policy-name bridge --policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": "iot:*","Resource": "*"}]}' { "policyName": "bridge", "policyArn": "arn:aws:iot:ap-northeast-1:123456789012:policy/bridge", "policyDocument": "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Action\": \"iot:*\",\"Resource\": \"*\"}]}", "policyVersionId": "1" }
証明書の作成
$ aws iot create-keys-and-certificate --set-as-active --certificate-pem-outfile cert.crt --private-key-outfile private.key --public-key-outfile public.key { "certificateArn": "arn:aws:iot:ap-northeast-1:123456789012:cert/xxxxxxxxxx", "certificateId": "xxxxxxxxxx", "certificatePem": "-----BEGIN CERTIFICATE-----\n....\n-----END CERTIFICATE-----\n", "keyPair": { "PublicKey": "-----BEGIN PUBLIC KEY-----\n....\n-----END PUBLIC KEY-----\n", "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n" } }
証明書にポリシーをアタッチ
$ aws iot attach-principal-policy --policy-name bridge --principal <先ほど作成した証明書のARN>
ルートCA証明書のDL
AWS IoTのサーバ証明書を検証するためにルートCA証明書をDLします。 私のAWSアカウントではATS(Amazon Trust Services) エンドポイントに移行済みなので、AmazonルートCAの証明書をDLします。
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem
EC2のセットアップ
EC2をセットアップしていきます。 詳細は割愛しますが、インターネットに抜けられるようにセキュリティグループ等を適宜設定してください。 また、MQTTの接続用に1883ポートを解放しておいてください。
epelの有効化
$ sudo amazon-linux-extras install epel Installing epel-release Loaded plugins: extras_suggestions, langpacks, ... ...略
Mosquittoのインストール
$ sudo yum install mosquitto Loaded plugins: extras_suggestions, langpacks, priorities, update-motd ...略 Installed: mosquitto.x86_64 0:1.5.5-2.el7 Dependency Installed: libuv.x86_64 1:1.24.1-1.el7 libwebsockets.x86_64 0:3.0.1-2.el7 Complete!
証明書等の配置
準備しておいた証明書と秘密鍵をEC2上に配置します。 パスはそれぞれ下記の通りとします。
- AmazonRootCA1.pem -> /etc/mosquitto/certs/rootCA.pem
- private.key -> /etc/mosquitto/certs/private.key
- cert.crt -> /etc/mosquitto/certs/cert.crt
配置できたらパーミッションを設定しておきます。
$ chmod +r /etc/mosquitto/certs/cert.crt $ chmod +r /etc/mosquitto/certs/private.key
Mosquittoの設定ファイル作成
下記の通りMosquittoの設定ファイルを追加します。 ハイライト箇所は適宜変更してください。
#Copy paste the following in the nano editor: # ================================================================= # Bridges to AWS IOT # ================================================================= # AWS IoT endpoint, use AWS CLI 'aws iot describe-endpoint' connection awsiot address <自分のAWS IoTエンドポイント>:8883 # Specifying which topics are bridged topic awsiot_to_localgateway in 1 topic localgateway_to_awsiot out 1 topic both_directions both 1 # Setting protocol version explicitly bridge_protocol_version mqttv311 bridge_insecure false # Bridge connection name and MQTT client Id, # enabling the connection automatically when the broker starts. cleansession true clientid <Mosquitto用に登録しておいたモノの名前> start_type automatic notifications false log_type all # ================================================================= # Certificate based SSL/TLS support # ----------------------------------------------------------------- #Path to the rootCA bridge_cafile /etc/mosquitto/certs/rootCA.pem # Path to the PEM encoded client certificate bridge_certfile /etc/mosquitto/certs/cert.crt # Path to the PEM encoded client private key bridge_keyfile /etc/mosquitto/certs/private.key
やってみる
ここまでで準備完了です。 EC2上で下記のコマンドを実行し、Mosquittoを起動してみます。
$ mosquitto -c /etc/mosquitto/conf.d/bridge.conf -v 1550391545: mosquitto version 1.5.5 starting 1550391545: Config loaded from /etc/mosquitto/conf.d/bridge.conf. 1550391545: Opening ipv4 listen socket on port 1883. 1550391545: Opening ipv6 listen socket on port 1883. 1550391545: Bridge local.mosquitto_bridge doing local SUBSCRIBE on topic localgateway_to_awsiot 1550391545: Bridge local.mosquitto_bridge doing local SUBSCRIBE on topic both_directions 1550391545: Connecting bridge awsiot (xxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com:8883) 1550391545: Bridge mosquitto_bridge sending CONNECT 1550391545: Received CONNACK on connection local.mosquitto_bridge. 1550391545: Bridge local.mosquitto_bridge sending SUBSCRIBE (Mid: 1, Topic: awsiot_to_localgateway, QoS: 1) 1550391545: Bridge local.mosquitto_bridge sending UNSUBSCRIBE (Mid: 2, Topic: localgateway_to_awsiot) 1550391545: Bridge local.mosquitto_bridge sending SUBSCRIBE (Mid: 3, Topic: both_directions, QoS: 1) 1550391545: Bridge local.mosquitto_bridge doing local SUBSCRIBE on topic localgateway_to_awsiot 1550391545: Bridge local.mosquitto_bridge doing local SUBSCRIBE on topic both_directions 1550391545: Received SUBACK from local.mosquitto_bridge 1550391545: Received SUBACK from local.mosquitto_bridge 1550391545: Received UNSUBACK from local.mosquitto_bridge
AWS IoTのログレベルをデバッグにしておけば、この時点でCloudWatch logsにMosquittoからの接続ログが確認できます。
次にクライアントからのPublishを試してみましょう。 MQTTlensを使ってクライアントIDがclientid1,clientid2の接続を1つづつ確立します。
各接続から設定ファイルで指定したlocalgateway_to_awsiotというトピックに対してPublishします。
AWS IoT側でlocalgateway_to_awsiotをSubscribeしておけばMQTTlensからPublishしたメッセージが確認できます。
考察
とりあえずPublishができるところまでは確認できましたが、果たしてこのまま実戦投入しても大丈夫でしょうか? デメリットについて考えてみます。
全デバイスが同一のクライアントIDになる。
Mosquittoに接続するデバイスが増えても、Mosquitto <-> AWS IoTの接続は1本だけです。つまり、各デバイスはすべて同一のクライアントIDを利用することになります。
ブリッジを介さずにデバイスが直接AWS IoTに接続するパターンでは
- デバイスごとにクライアントID=モノの名前を割り当てる
- デバイスは自身のクライアントIDを含むトピック($aws/things/対象デバイスのクライアントID=モノの名前/等)にしかPub・Subできないように設定する
- ルールエンジンのクエリで
SELECT topic(2) as device_id...
のように指定することでAWS IoTの後続処理にクライアントID=モノの名前を引き渡す
といった設計が一般的かと思います。 ブリッジ接続の場合AWS IoT側からはデバイスAもデバイスBも同じクライアントIDに見えてしまうので、この設計が通用しません。 デバイスを識別するためにはMQTTのペイロード内にデバイスのIDを設定し、MQTTのペイロードからデバイスを判断するしかない(もしうまいやり方あれば教えてください。。。)と思います。
例えばこんな感じ
{ "client_id":"client1", "data":.... }
AWS IoT側ではペイロード内のクライアントIDを検証する手段がないので、セキュリティレベルは低下することになります。また、MQTTのペイロードがこのような設計になると、ブリッジ接続無しで直接AWS IoTに接続できるデバイスもブリッジ接続しか使えないデバイスに引っ張られてセキュリティレベルを下げざるを得なくなります。
スケールが煩雑
デバイスの台数が増えて1台のEC2では捌き切れなくなるケースを考えてみます。この場合、EC2を追加してスケールアウトしたいところですが、1つのクライアントIDからAWS IoTに対して複数の接続張ることはできないので、2台目のEC2は1台目のEC2と同一のクライアントIDを利用することができません。 EC2を追加する度に新たにクライアントIDを採番して・・・という作業が必要になり、管理コストが上がります。
AWS IoTの認証認可の仕組みが使えない
デバイス <-> Mosquittoの接続がMQTTとなるため、AWS IoTが備えるクライアント証明書を用いた認証・認可の仕組みが使えません。 Mosquitto側でユーザー名、パスワードによる認証とACLファイルによる認可を利用することも可能ですが、管理が煩雑になります。
MQTTは暗号化されない
デバイス<->Mosquitto間の接続が暗号化されないため、盗聴された場合は中身が丸見えになります。
これに関してはMosquitto側でMQTTSを有効にすれば対策できるはず(今回の調査ではMQTT接続が要件だったのでMosquittoへのMQTTS接続は試しませんでした)ですが、証明書を管理するための運用コストが増加します。
まとめ
Mosquittoを使ったAWS IoTへのブリッジ接続についてご紹介しました。 AWS IoTに直接接続できないデバイスからデータを収集するためにはMosquittoによるブリッジ接続が選択肢に上がりそうです。 一方でブリッジ接続には諸々の考慮事項が残り、ブリッジ接続が有効な選択肢となる状況は限られそうです。 場合によってはNginxのStreamモジュールを利用して
デバイス <-> Nginx(with stream module) <-> AWS Iot
という構成を取る方がベターかもしれません。 インフラに求められる要件を把握した上で、ベストな選択をしていければと思います。