AWS IoTポリシーを変更したらpublishできなくなったので調査、修正してみた

サンプルでよく見るAWS IoTのポリシーを変更したところ正常にデータをpublishできなくなったので何が悪かったかを調査修正しました。
2020.03.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちはCX事業本部のさかじです。
サンプルでよく見るAWS IoTのポリシーを変更したところ正常にデータをpublishできなくなったので何が悪かったかを調査修正しました。

環境

  • MacBook Pro(macOS Mojave 10.14.6)

前提条件

$ python3 --version
Python 3.7.6
  • testThingという名称でモノ作成、同様に証明書もダウンロード済み

ソース

以下のように、カウントアップデータをpublish

import os
import os.path
import time
import json

class IotMqttClient:

    # AWS IOTと接続するときの設定
    ###############
    ### 要確認 ###
    ###############

    ROOT_CA_PATH = os.path.join(os.path.dirname(__file__), 'certs/rootCA.pem')
    CERTIFICAT_PATH = os.path.join(os.path.dirname(__file__), 'certs/cert.pem')
    PRIVATE_KEY_PATH = os.path.join(os.path.dirname(__file__), 'certs/private.key')
    IOT_HOST = 'a1caabg5j4yl8w.iot.ap-northeast-1.amazonaws.com'
    IOT_PORT = 8883
    IOT_CLIENT_ID = 'test_client'
    __my_iot_mqtt_client = None
    __is_connected = False

    def __init__(self):
        from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient

        # クライアントの設定
        self.__my_iot_mqtt_client = AWSIoTMQTTClient(self.IOT_CLIENT_ID)
        self.__my_iot_mqtt_client.configureEndpoint(self.IOT_HOST, self.IOT_PORT)
        self.__my_iot_mqtt_client.configureCredentials(self.ROOT_CA_PATH, self.PRIVATE_KEY_PATH, self.CERTIFICAT_PATH)

        # 接続情報の設定
        self.__my_iot_mqtt_client.configureAutoReconnectBackoffTime(1, 32, 20)
        self.__my_iot_mqtt_client.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
        self.__my_iot_mqtt_client.configureDrainingFrequency(2)  # Draining: 2 Hz
        self.__my_iot_mqtt_client.configureConnectDisconnectTimeout(10)  # 10 sec
        self.__my_iot_mqtt_client.configureMQTTOperationTimeout(5)  # 5 sec

        def on_offline(): self.__is_connected = False
        def on_online(): self.__is_connected = True

        self.__my_iot_mqtt_client.onOffline = on_offline
        self.__my_iot_mqtt_client.onOnline = on_online

        # 接続開始
        self.__my_iot_mqtt_client.connect()

    def publish(self, IOT_TOPIC, unpacked_data):
        if not self.__is_connected:
            self.__my_iot_mqtt_client.connect()
        self.__my_iot_mqtt_client.publishAsync(IOT_TOPIC, str(unpacked_data), 1, ackCallback=None)


def main():
    iot_mqtt_client = IotMqttClient()

    count = 1
    while True:
       print(count) 
       count += 1
       time.sleep(1)
       send_data = {'key':count}
       json_data = json.dumps(send_data)
       iot_mqtt_client.publish('test_topic', json_data)

if __name__ == '__main__':
    main()

よくサンプルで出てくるポリシー

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Subscribe",
        "iot:Connect",
        "iot:Receive"
      ],
      "Resource": "*"
    }
  ]
}

改善したい点

  • 機能別に許可するモノを分けたい
  • 今回はtestThingのPublishのみを許可したい

ポリシー例

開発者ガイド>パブリッシュ/サブスクライブポリシーの例参考に作成してみました

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:client/testThing"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish"
      ],
      "Resource": [
        "arn:aws:iot:ap-northeast-1:123456789012:topic/testThing/*"
      ]
    }
  ]
}

動かしてみた

$ ./shadow.py 
Connect timed out
Traceback (most recent call last):
  File "./shadow.py", line 74, in <module>
    main()
  File "./shadow.py", line 57, in main
    iot_mqtt_client = IotMqttClient()
  File "./shadow.py", line 46, in __init__
    self.__my_iot_mqtt_client.connect()
  File "/usr/local/lib/python3.7/site-packages/AWSIoTPythonSDK/MQTTLib.py", line 513, in connect
    return self._mqtt_core.connect(keepAliveIntervalSecond)
  File "/usr/local/lib/python3.7/site-packages/AWSIoTPythonSDK/core/protocol/mqtt_core.py", line 199, in connect
    raise connectTimeoutException()
AWSIoTPythonSDK.exception.AWSIoTExceptions.connectTimeoutException

接続できないので確認してみた

clientID - String that denotes the client identifier used to connect to AWS IoT. If empty string were provided, client id for this connection will be randomly generated n server side.

クライアントIDはAWS IoTへ接続するためのの文字列なのでIOT_CLIENT_ID = 'test_client'としているとtestThingを許可しているので接続できませんので修正しましょう。

    IOT_CLIENT_ID = 'testThing'

再度動かしてみた

1
2
3
4
5
6
Connect timed out
Traceback (most recent call last):
  File "./shadow.py", line 74, in <module>
    main()
  File "./shadow.py", line 67, in main
    iot_mqtt_client.publish('test_topic', json_data)
  File "./shadow.py", line 50, in publish
    self.__my_iot_mqtt_client.connect()
  File "/usr/local/lib/python3.7/site-packages/AWSIoTPythonSDK/MQTTLib.py", line 513, in connect
    return self._mqtt_core.connect(keepAliveIntervalSecond)
  File "/usr/local/lib/python3.7/site-packages/AWSIoTPythonSDK/core/protocol/mqtt_core.py", line 199, in connect
    raise connectTimeoutException()
AWSIoTPythonSDK.exception.AWSIoTExceptions.connectTimeoutException

少し動いたように見えてタイムアウトエラーします。実際AWSマネジメントコンソールからsubscribeしてもデータは取得できませんでした。

確認してみた

  • 一旦pubulish側の"Resouce"をワイルドカードにしてみたところpublishできるようになった
  • と言うことはpubulish側の"Resouce"に問題がある
  • プログラムのtopicの書式に間違いがある?
  • 以下の変更を加えた
       iot_mqtt_client.publish('testThing/test_topic', json_data)
  • 無事publish成功
    AWSマネジメントコンソールからsubscribeしてもデータは取得できることも確認できました。

参考サイト

https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/pub-sub-policy.html

最後に

ついついサンプルのままポリシーを使っているので、痛い目に合いそうなので修正したところ見事はハマってしまった内容でした。細かい記載ミスでしたので原因を見つけるのに時間がかかりましたが、同じような問題に対して参考になれば幸いです。