この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
IoT事業部の平内(SIN)です。
AWS IoT Coreへのアクセスでは、カスタム認証によって、独自の認証認可を構築することができます。
ここまで、HTTPSとMQTTについて確認してきました。
今回は、最後の1つとなるMQTT over WebSocketを使用して、カスタム認証でメッセージのパブリッシュ及び、サブスクライブをしてみました。
IoT Coreでサポートされているプロトコルの一覧の中では、2番目のものとなります。
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 over WebSocketでアクセスした場合、下記のようなリクエストがLambdaに到着します。
{
"protocolData": {
"tls": {
"serverName": "xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com"
},
"mqtt": {
"clientId": "client_id"
},
"http": {
"headers": {
"x-amz-customauthorizer-signature": "xxxxxxx",
"content-length": "0",
"sec-websocket-version": "13",
"upgrade": "websocket",
"connection": "Upgrade",
"host": "xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com",
"x-amz-customauthorizer-name": "custom_auth_websocket_2021_08_21",
"sec-websocket-protocol": "mqtt",
"sec-websocket-key": "NpKWpnqzZX0A+6KPH7rqrQ==",
"my-token": "sample-token"
},
"queryString": ""
}
},
"protocols": [
"tls",
"http",
"mqtt"
],
"token": "sample-token",
"signatureVerified": True,
"connectionMetadata": {
"id": "58c0d540-bc32-924e-26a7-c6aaf62a60b7"
}
}
そして、Lambdaから返すのは、以下の内容になります。
- IsAuthenticated: 認証の有効無効を返すブール値
- PrincipalId: 識別子(1〜128文字)
- PolicyDocuments: JSON形式のポリシー(各ポリシーは、2,048文字以内で、10個まで指定可能)
- DisconnectAfterInSeconds: AWS IoT ゲートウェイへの接続期間(300〜86,400秒)
- RefreshAfterInSeconds: ポリシーをキャッシュする期間(300〜86,400秒)
オーソライザーで署名を有効としている場合、署名の検証に成功した場合(signatureVerifiedは、Trueとなっています)だけ、Lambdaがコールされるので、ここでは、下記のようなコードで、client_idからの接続と、トピック(my-topic)にPublish/Subscribeできる権限を返すことにします。
custom_auth_websocket_2021_08_21
import json
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("eventy: {}".format(json.dumps(event)))
is_authenticated = False
if "signatureVerified" in event:
is_authenticated = event["signatureVerified"]
principal_id = "CustomAuthId"
disconnect = 84000
refresh = 300
return {
"isAuthenticated": is_authenticated,
"principalId": principal_id,
"disconnectAfterInSeconds": disconnect,
"refreshAfterInSeconds": refresh,
"policyDocuments": [
policy
]
}
3 秘密鍵・公開鍵の作成
署名が有効なオーソライザを作成するためには、公開鍵の登録が必要です。
下記では、opensslを使用して、秘密鍵(id_rsa)と公開鍵(id_rsa.pub)を作成しています。
% openssl genrsa -out id_rsa 4096
% openssl rsa -in id_rsa -pubout -out id_rsa.pub
% cat id_rsa.pub
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxHb3PH7Mmfufj8pKhX78
・・・略・・・
e6iQM2IL41oq1bTk8McSG2ECAwEAAQ==
-----END PUBLIC KEY-----
4 オーソライザー
IoT Coreのコンソールから安全性 - オーソライザーと辿り、新規にオーソライザーを作成します。 設定した内容は、以下の通りです
- オーソライザーの名前: custom_auth_websocket_2021_08_21としました
- オーソライザー関数: custom_auth_websocket_2021_08_21 上記で作成したもの
- トークン署名を有効化: チェック(デフォルト値)
- トークンヘッダ名: my-token としました
- キー名: device-001としました
- 値: 上記で作成した公開鍵
- オーソライザーアプティブ: チェック
「オーソライザーの名前」「トークンヘッダ名」は、アクセス時に必要なので、名前を控えておきます。
5 動作確認
動作確認のために、トークンに署名を行います。トークンは、何でも構いませんが、ここでは、sample-tokenとしました。
% echo -n "sample-token" | openssl dgst -sha256 -sign id_rsa | openssl base64 -A
AWS IoT Device SDK v2 for Pythonで作成したクライアントのコードです。
接続後、トピック(my-topic)をSubscribeし、同トピックに3回メッセージをPublishして終了します。
from __future__ import absolute_import
from __future__ import print_function
from awscrt import io, mqtt
import json
import time
endpoint = "xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com"
root_ca = "./certs/root-CA.crt"
topic = "my-topic"
client_id = "client_id"
authorizer_name = "custom_auth_websocket_2021_08_21"
signature = "xxxxxxxxxxx" # openssl dgstで生成したシグレチャー
token_name = "my-token" # オーサライザーで設定、トークン名
token = "sample-token" # シグネチャを生成したトークン
def on_message_received(topic, payload, **kwargs):
print("received: {}".format(payload.decode()))
def add_headers(transform_args):
transform_args.http_request.headers.add(
'x-amz-customauthorizer-name', authorizer_name)
transform_args.http_request.headers.add(
'x-amz-customauthorizer-signature', signature)
transform_args.http_request.headers.add(token_name, token)
transform_args.set_done()
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()
socket_options = io.SocketOptions()
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)
mqtt_connection = mqtt.Connection(client=client,
host_name=endpoint,
port=443,
client_id=client_id,
clean_session=True,
keep_alive_secs=30,
socket_options=socket_options,
use_websockets=True,
websocket_handshake_transform=add_headers
)
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()
自分の送信したメッセージを受信できている様子です。
テストクライアントでも、到着しているメッセージが確認できます。
6 最後に
今回は、MQTT over WebSocketを使用して、カスタム認証でメッセージをPublish/Subscribeしてみました。
認証のための署名は、MQTT over WebSocketでは、リクエストヘッダに入れる形で、HTTPSと同じ感じです。 MQTTでは、ユーザーフィールドでうまく動作できませんでしたが、こちらでは、全く問題なく利用できそうです。
7 参考リンク
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してみました