AWS IoT のエンドポイントで独自ドメインを使う


2020/03/28現在この機能はパブリックプレビューの状態です。 us-east-1でのみ試せます。

いつ必要?

  • MQTTブローカーを提供するB2Bサービスを行う際の、エンドポイント名を独自ドメインにしたい場合
  • すでに運用中のMQTTサーバーを、クライアント側の変更無しでAWS IoTに置き換える場合(カスタムオーサライザと一緒に使う)

基本的にこのスライドの流れに沿って実現します。
https://d1.awsstatic.com/events/reinvent/2019/REPEAT_1_Simplify_the_migration_of_IoT_applications_to_AWS_IoT_IOT341-R1.pdf

ACMで証明書取得

設定したいドメイン ここではiot.oxoxo.me に対して証明書を取得します。

aws acm request-certificate \
--domain-name iot.oxoxo.me \
--validation-method DNS \
--idempotency-token 1234 \
--subject-alternative-names iot.oxoxo.me \
--options CertificateTransparencyLoggingPreference=ENABLED

{
    "CertificateArn": "arn:aws:acm:us-east-1:<account-id>:certificate/92a2a701-ad12-4a2f-ab0a-b378721126e8"
}

AWS IoT でのドメイン設定

aws iot create-domain-configuration  \ 
--domain-configuration-name "myDomainConfigurationName" \ 
--service-type "DATA"  \ 
--domain-name "iot.oxoxo.me" \ 
--server-certificate-arns arn:aws:acm:us-east-1:<account-id>:certificate/92a2a701-ad12-4a2f-ab0a-b378721126e8 \ 
--region us-east-1

{
    "domainConfigurationName": "myDomainConfigurationName",
    "domainConfigurationArn": "arn:aws:iot:us-east-1:<account-id>:domainconfiguration/myDomainConfigurationName/t9tri"
}

aws iot describe-endpoint --endpoint-type iot:Data-Beta --region us-east-1
{
    "endpointAddress": "<random-id>-beta.iot.us-east-1.amazonaws.com"
}

CNAMEの設定

上記で表示されたエンドポイントを向くCNAMEを作成する。
自分の環境ではDNSにRoute53を使っているので以下のような形になります。

test

以上で設定完了です。
クライアント側を作って試していきます。この記事で作った環境を流用します。

実行のところで、自分のエンドポイントを設定します。

export ENDPOINT=iot.oxoxo.me

以上です。

参考までに、v1のSDKで試すと以下のようなエラーがでました。

ssl.SSLError: ('Certificate subject does not match remote hostname.',)

原因が追えてませんが、内部的に443/ALPNを使用する前提でしょうか。
Pythonのコードで、Endpoint のポートを443に変更することで(WebSocket使用)回避できています。

(追記)クライアント証明書無しのMQTT通信をカスタムオーサライザで試す

非常に非力なIoTデバイスではTLSハンドシェイクを行うスペックを持ち合わせてないことがあります。一方で、クライアント証明書を使わずともマネージドなMQTTブローカとしてAWS IoTを使いたいといったケースもあるかもしれません。
ということで、カスタムオーサライザ機能でそれを実現してみます。ID,パスワードの認証もありですが、ここでは振り切って 認証なし にチャレンジします。
セキュリティ的には全くおすすめ出来ないですが、デモ等だと証明書管理が不要で便利かもしれません。

カスタムオーサライザ機能では、指定したLambda上で認証を行います。
以下でLambdaを書いていますが、潔く全部パスするようにしています。

IoTCustomAuthorizer
import json

def lambda_handler(event, context):

    ret = {
        "isAuthenticated": True,
        "principalId": "anonymous",
        "disconnectAfterInSeconds": 86400,
        "refreshAfterInSeconds": 300,
        "policyDocuments": [
            "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"iot:*\",\"Effect\":\"Allow\",\"Resource\":\"*\"}]}"
        ],
        "context": {
        }
    }
    return ret

以下でオーサライザを作ります。

aws iot create-authorizer --authorizer-name test --authorizer-function-arn arn:aws:lambda:us-east-1:<account-id>:function:IoTCustomAuthorizer --signing-disabled --status ACTIVE --region us-east-1

{
    "authorizerName": "test",
    "authorizerArn": "arn:aws:iot:us-east-1:<account-id>:authorizer/test"
}

次にAWS IoT service にLambdaを叩く許可を与えます。


aws lambda add-permission --function-name IoTCustomAuthorizer \
--principal iot.amazonaws.com \
--source-arn arn:aws:iot:us-east-1:<account-id>:authorizer/test \
--statement-id Id-123 \
--action "lambda:InvokeFunction" \
--region us-east-1

{
    "Statement": "{\"Sid\":\"Id-123\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"iot.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:us-east-1:<account-id>:function:IoTCustomAuthorizer\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:iot:us-east-1:<account-id>:authorizer/test\"}}}"
}

#AuthorizerをDomain Configurationに紐付ける
aws iot update-domain-configuration --domain-configuration-name myDomainConfigurationName --region us-east-1 --authorizer-config defaultAuthorizerName=test,allowAuthorizerOverride=true

# Lambdaが正しく動作するか確認
aws iot test-invoke-authorizer --authorizer-name test --region us-east-1 --http-context '{}'

MQTT Clientから確認

探すとブラウザからMQTTを試せるサイトが色々ありました。
こちらがシンプルでしたので今回はこちらを使いました。
Address欄に、 wss://iot.oxoxo.me/mqtt を入れるとIDやパスワードなどを入れずにPub/Subができました。