[AWS IoT Core] カスタム認証(ユーザー名・パスワード)を使用してMQTTでPublish/Subscribeしてみました

2021.08.21

1 はじめに

IoT事業部の平内(SIN)です。

AWS IoT Coreへのアクセスでは、カスタム認証によって、独自の認証認可を構築することができます。

上のように、前回、HTTPSでの接続を確認しましたが、今回は、MQTTを使用して、カスタム認証(ユーザー名・パスワード)でメッセージのパブリッシュ及び、サブスクライブをしてみました。

IoT Coreでサポートされているプロトコルの一覧の中では、5番目のものとなります。一覧からも分かる通り、ポート8883では、利用できないので注意が必要です。


Protocols, authentication, and port mappings

No. Protocol Operations supported Authentication Port ALPN protocol name
1 MQTT over WebSocket Publish, Subscribe Signature Version 4 443 N/A
2 MQTT over WebSocket Publish, Subscribe Custom authentication 443 N/A
3 MQTT Publish, Subscribe X.509 client certificate 443 x-amzn-mqtt-ca
4 MQTT Publish, Subscribe X.509 client certificate 8883 N/A
5 MQTT Publish, Subscribe Custom authentication 443 mqtt
6 HTTPS Publish only Signature Version 4 443 N/A
7 HTTPS Publish only X.509 client certificate 443 x-amzn-http-ca
8 HTTPS Publish only X.509 client certificate 8443 N/A
9 HTTPS Publish only Custom authentication 443 N/A


参考:How to Use Your Own Identity and Access Management Systems to Control Access to AWS IoT Resources

2 Lambda

MQTTでアクセスした場合、下記のようなリクエストがLambdaに到着します。

{
    "protocolData": {
        "tls": {
            "serverName": "xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com"
        },
        "mqtt": {
            "username": "user01?x-amz-customauthorizer-name=custom_auth_mqtt_2021_08_21",
            "password": "cGFzczAx",
            "clientId": "client_id"
        }
    },
    "protocols": [
        "tls",
        "mqtt"
    ],
    "signatureVerified": False,
    "connectionMetadata": {
        "id": "54300d4a-d787-bb33-bbab-e61eb9f07893"
    }
}

そして、Lambdaから返すのは、以下の内容になります。

  • IsAuthenticated: 認証の有効無効を返すブール値
  • PrincipalId: 識別子(1〜128文字)
  • PolicyDocuments: JSON形式のポリシー(各ポリシーは、2,048文字以内で、10個まで指定可能)
  • DisconnectAfterInSeconds: AWS IoT ゲートウェイへの接続期間(300〜86,400秒)
  • RefreshAfterInSeconds: ポリシーをキャッシュする期間(300〜86,400秒)

オーソライザーで「署名を有効」としていない場合、signatureVerifiedは、Falseとなっています。

この場合、Lambdaがコールされた時点で、認証は、完了していないので、送られてきた、ユーザー名、パスワード、クライアントIDなどを使用して認証するロジックをLambda内に記述する事になります。

下記のコードでは、ユーザー名が、user01で、パスワードがpass01だった場合、トピック(my-topic)へのPub/Subを許可しています。また、返されるポリシーは、client_idからのConnectのみ許可するものですので、クライアントIDもclient_idであることが必要です。

custom_auth_mqtt_2021_08_21

import json
import base64

policy = {
  "Version": "2012-10-17",
  "Statement": [
    {
        "Effect": "Allow",
        "Action": "iot:Connect",
        "Resource": "arn:aws:iot:ap-northeast-1:*:client/client_id"
    },
    {
        "Effect": "Allow",
        "Action": [
            "iot:Publish",
            "iot:Receive",
            ],
        "Resource": "arn:aws:iot:ap-northeast-1:*:topic/my-topic"
    },
    {
        "Effect": "Allow",
        "Action": "iot:Subscribe",
        "Resource": "arn:aws:iot:ap-northeast-1:*:topicfilter/my-topic"
    }
  ]
}

def lambda_handler(event, context):
    print(event)

    is_authenticated = False
    username = ""
    password = ""

    if "protocolData" in event:
        protocolData = event["protocolData"]
        if "mqtt" in protocolData:
            mqtt = protocolData["mqtt"]
            if "username" in mqtt:
                tmp = mqtt["username"].split("?")
                username = tmp[0]
            if "password" in mqtt:
                password = mqtt["password"]
                password = base64.b64decode(password).decode() #Base64デコード

    if(username == "user01" and password == "pass01"):
        is_authenticated = True

    principal_id = "CustomAuthId"
    disconnect = 84000
    refresh = 300
    
    return {
        "isAuthenticated": is_authenticated,
        "principalId": principal_id,
        "disconnectAfterInSeconds": disconnect,
        "refreshAfterInSeconds": refresh,
        "policyDocuments": [
            policy
        ]
    }

3 オーソライザー

IoT Coreのコンソールから安全性 - オーソライザーと辿り、新規にオーソライザーを作成します。 設定した内容は、以下の通りです

  • オーソライザーの名前: custom_auth_mqtt_2021_08_21としました
  • オーソライザー関数: custom_auth_mqtt_2021_08_21 上記で作成したもの
  • トークン署名を有効化: チェックなし
  • オーソライザーアプティブ: チェック

トークン署名の有効化については、後て変更はできず、もし、変えたい場合は、オーソライザー自体を改めて作成する必要があります。

4 動作確認

AWS IoT Device SDK v2 for Pythonで作成したクライアントのコードです。

接続後、トピック(my-topic)をSubscribeし、同トピックに3回メッセージをPublishして終了します。

from awscrt import io, mqtt
import json
import time

endpoint = "xxxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com"
root_ca = "./certs/root-CA.crt"
authorizer_name = "custom_auth_mqtt_2021_08_21"
topic = "my-topic"

client_id = "client_id"
username = "user01"
password = "pass01"

def on_message_received(topic, payload, **kwargs):
    print("received: {}".format(payload.decode()))

if __name__ == '__main__':
    event_loop_group = io.EventLoopGroup(1)
    host_resolver = io.DefaultHostResolver(event_loop_group)
    client_bootstrap = io.ClientBootstrap(event_loop_group, host_resolver)

    tls_options = io.TlsContextOptions()
    tls_options.alpn_list = ['mqtt']
    tls_options.override_default_trust_store_from_path(ca_dirpath=None, ca_filepath=root_ca)
    tls_ctx = io.ClientTlsContext(options=tls_options)
    client = mqtt.Client(client_bootstrap, tls_ctx)

    username += "?x-amz-customauthorizer-name={}".format(authorizer_name)

    mqtt_connection = mqtt.Connection(client=client,
        host_name=endpoint,
        port=443,
        client_id=client_id,
        clean_session=True,
        keep_alive_secs=6,
        username=username,
        password=password)

    connect_future = mqtt_connection.connect()
    connect_future.result()
    print("Connected!")

    # Subscribe
    subscribe_future, packet_id = mqtt_connection.subscribe(
        topic=topic,
        qos=mqtt.QoS.AT_LEAST_ONCE,
        callback=on_message_received)

    subscribe_future.result()

    for i in range(3):
        # Publish
        payload = {
            "counter": i
        }
        mqtt_connection.publish(
            topic=topic,
            payload=json.dumps(payload),
            qos=mqtt.QoS.AT_LEAST_ONCE)
        time.sleep(1)

    disconnect_future = mqtt_connection.disconnect()
    disconnect_future.result()

自分の送信したメッセージを受信できている様子です。

テストクライアントでも確認できています。

5 最後に

今回は、MQTTを使用して、ユーザー名とパスワードでメッセージブローカーへの接続を試してみました。

署名を使用しない場合、パスワード等だけの認証が可能にはなりますが、Lambdaのトリガーに制限が効かなくなるため、使用方法はよく検討する必要があるでしょう。


https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/custom-authorizer.html

MQTTでも、署名をユーザーフィールドで設定できるようにドキュメントには記載されていましたが、すいません、ちょっと手元ではうまくオーソライザーに届きませんでした。何かコツなどあれば、是非教えてやってください。


https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/custom-auth.html

username?x-amz-customauthorizer-name=${name}
&x-amz-customauthorizer-signature=${sign}&token-name=${token-value}

6 参考リンク


AWS IoT Enhanced Custom Authorizer Demo
Connecting to AWS IoT Core by using custom authentication
[AWS IoT Core] カスタム認証を使用してHTTPSでPublishしてみました
[AWS IoT Core] カスタム認証(ユーザー名・パスワード)を使用してMQTTでPublish/Subscribeしてみました
[AWS IoT Core] カスタム認証を使用してMQTT over WebSocketでPublish/Subscribeしてみました