「WebブラウザでAWS IoT Coreに接続してMQTTでPub/Sub」する場合の流れをまとめておく。
具体的なソースコードとかは載せない。「やってみたいけれどどうすればいいのかわからなくて途方にくれている」「情報が多すぎて、実際にどれとどれをやれば良いのかがわからない」「心が折れそうだ」という場合のひとつの指針として役立つのではないかと思ってここに書いておく。さまざまな技術的アプローチがあるのだが、「こんな方法もあんな方法もあるよ」と話を広げず、「この手順でやればひとまず出来る」という流れをできるだけ「一本道」で書く。
できるかできないか? → できる
まず、「WebブラウザでAWS IoT CoreにMQTTでPub/Subできるか」という疑問に対しての答えは「できる」。具体的な流れはこんなふうになる。
- Custom Authorizationを自前で用意し (Lambdaを作った上で色々設定する必要がある)
- MQTT over WebSocketで
- ポート443に接続して
- Custom Authorizationで認証し
- あとは普通にPub/Subする
やるべきこと
- MQTT over WebSocketを使う。ライブラリはいくつか存在する。私はMQTT.jsを使ってうまくいった。
- 接続ポートは443を使う。8883は駄目なので注意。
- 認証方法は色々あるが、カスタム認証 (クライアントからの接続リクエストを自前のLambdaに飛ばして認証を行う) を使ってうまくいった。
- ちなみにSignature Version 4でも出来そう……なのだが、私は今の所うまくいってない。うまくいったらまた別途記事を書くかも。
- カスタム認証を自分で頑張って作る。手順は次の通り。
- まず公式デベロッパーガイドの「カスタム認証」の記事を読んで流れを把握する 。
- IoT Coreの Security > Authorizers でAuthorizerを作成し、Lambdaの関数と関連付ける。
- Token validationは実際の運用時には有ったほうがいいのだが「とにかく試したい」という場合にハマりどころになりがちなポイントなので、最初は省略してもいいと思う。
- HTTP authorization cachingも本来は使ったほうがLamdaの呼び出しを抑えられるのだが、開発時にうまくいくまではoffにしておいてもいいんじゃないかな。
- Lambdaに関数を用意する
- パーミッション設定をする必要がある。Configuration > Resource-based policyでパーミッションを追加し、AWS IoTからのLambdaの呼び出しを許可する。
- Lambdaの関数でやるべきことは以下の通り。
- 接続を試みているクライアントからのリクエストが渡されてくるので
- その内容を何らかの形でチェックし
- 認証結果を返す
- 認証OKかNGか?
- OKの場合、何を許可するか。iot:Connect と、Publishするなら iot:Publish、Subscribeするには iot:Subscribe と iot:Receive を許可する。
- clientId は event.protocolData.mqtt.clientId で、username は event.protocolData.mqtt.username で取れる。password はAWS側でBase64エンコードされるので
Buffer.from(event.protocolData.mqtt.password, 'base64').toString('ascii')
という具合にデコードする。
- 接続URLは次のような形になる。
- wss://{aws_iot_core_endpoint}:443?x-amz-customauthorizer-name={authorizer_name}
- URLパラメタの x-amz-customauthorizer-name={authorizer_name} で、使用するCustom Authorizerを指定する。
- デフォルトのAuthorizerを指定すれば省略できる。
- wss://{aws_iot_core_endpoint}:443?x-amz-customauthorizer-name={authorizer_name}
- MQTTのCONNECTリクエストを送る際に次の3つを送る。
- username & password
- これを使ってどう認証するかは実装する者に任されている。
- clientId
- このclientIdに対して iot:Connect が許可されなければならない。 “Resource”: “arn:aws:iot:{region}:{aws_account_id}:client/{clientId}” にマッチするかどうかで接続の可否が判断される。
- username & password
- 無事に接続してCONNACKが返ってきたらPub/Subをしてみる。
ポイント
- ブラウザでWebSocketを使用する場合、基本的にリクエストヘッダをいじることはできない。AWS IoTの認証方式のうち、「ヘッダに何か情報を載せる」タイプのものは採用できないと思ってよい。
- 証明書は使用しなくてよい。
- AWS SDKがにAWS IoT関係のAPIがたくさん揃っているので使えるものはないだろうか……と思ったが、直接サポートしているのはHTTPSを使ったPublishのみ。Subscribeするには上記のようなやり方が必要になる。
- AWS IoTのサーバからSUBACKが送られてきているのに当該トピックにPublishしたメッセージが届かない場合、カスタム認証で iot:Receive をAllowしているかどうか確認すること。iot:Subscribeだけでは駄目なので注意。
困った時は
- Custom Authorizerまわりで困ったらとりあえず公式ドキュメントの「オーソライザーのトラブルシューティング」を読むとけっこうなんとかなる 。
- MQTT自体についての認識が間違ってるんじゃないか? という時点で自信がない場合、いきなりAWS IoTにつなごうとすると課題が多すぎて大変なので、ローカルでMosquittoを動かしてちょっと遊んでみることをおすすめする。
- MosquittoもMQTT over WebSocketに対応している。
- Wiresharkで生のパケットの中身を見てみるのも良い。通信内容をブラウザのデバッグツール (たとえばChromeの開発者ツールのNetworkタブが便利) で覗いて比較すると問題の解決に役立つことがある。
- カスタム認証のLambdaのレスポンスに含まれるpolicyDocumentsのResourceはけっこう間違えやすい。
- iot:Connect : MQTTのCONNECTリクエストに “clientId” を含める。arn:aws:iot:{region}:{aws_account_id}:client/{clientId} がマッチするかどうかで接続の可否が決まる。
- iot:Publish : Publish対象のトピックは “arn:aws:iot:{region}:{aws_account_id}:topic/{topic}” という形で指定する。”topic/” というところを抜かさないように。
- Subscribe : Subscribeを許可する対象は”arn:aws:iot:{region}:{aws_account_id}:topicfilter/{topic_filter}” という形で指定する。topicじゃないよtopicfilterだよ!
- iot:Receive : SUBSCRIBEリクエストを送って、SUBACKは返ってきているのになぜかメッセージが飛んでこない場合はここをチェックすること。
それでは。グッドラック。