AWS IoT CoreにVPCエンドポイント経由でアクセスしてみた

AWS IoT CoreデータプレーンにはVPCエンドポイント経由でアクセスできます。通常のインターフェース型VPCエンドポイントと異なり、Route 53のプライベートホストゾーンで名前解決したり、エンドポイント・ポリシーが使えないなど、独特の制限があります。
2024.01.04

AWS IoT Coreデータプレーンのエンドポイントはインターフェース型VPCエンドポイント経由でプライベートアクセスできます。

このエンドポイントは、Route 53のプライベートホストゾーンで名前解決が必要だったりと、独特の注意点があります。

本記事では、AWSコンソールからエンドポイントを構築し、EC2からmosquittoクライアントから疎通する方法を紹介します。

AWS CDKを使った構築手順は次の過去記事を参照ください。

[AWS IoT Core] Route53のプライベートホストゾーンを使用して IoT CoreのVPCエンドポイントの名前解決ができるVPCをCDKで作ってみました | DevelopersIO

AWS IoT Coreをオンプレミス環境から使う際のアーキテクチャ

IoTの場合、データプレーンの呼び出し元(ゲートウェイ)はAWSのネットワーク外にあるでしょう。

2021年9月時点でのこのような前提でのインターネット経由、VPCエンドポイント経由といった構成が次のスライドで整理されています。

https://pages.awscloud.com/rs/112-TZM-766/images/EV_aws-iot-deep-dive-5-915-topic3_Sep-2021.pdf

Direct ConnectでオンプレミスとAWSをつなぎ、VPCエンドポイント経由でデータ通信する構成例が下図です。

しかしながら、構成が複雑になり、考慮点も多いことから、パブリックエンドポイントを利用することが推奨されています。

パブリックエンドポイントを利用した基本構成例が下図です。

シンプルの極みですね。 可能な限りこの基本構成に誘導するのがおすすめな前提で、以降ではVPCエンドポイントを利用した通信手順を紹介します。

やってみた

最終的には以下の構成を作成します。

過去ブログ『AWS IoT Core が VPC エンドポイントをサポートしました! | DevelopersIO』 にRoute 53プライベートゾーンの手順を追加しています。

前提

  • 東京リージョン
  • クライアントは mosquitto_pub on EC2 Ubuntu 22.04
  • クイック接続でThing/認証情報を払い出し

VPCエンドポイント用セキュリティグループの作成

IoTデータプレーンのVPCエンドポイントはインターフェース型のため、エンドポイントにアタッチするセキュリティグループを作成します。

このセキュリティグループのインバウンドルールで、このVPC内のCIDRから8883ポートへのTCP通信を許可します。

VPCエンドポイントの作成

IoT CoreデータプレーンのVPCエンドポイントを作成します。

サービス名は「com.amazonaws.リージョン1.iot.data」です。

「追加設定」で「DNS名を有効化」をデフォルト設定のままチェックしないようにしてください。Route 53プライベートホストゾーンで名前解決します。

また、先程作成したセキュリティグループを紐づけます。

このVPCエンドポイントは特定のAZにのみ利用可能です。

次のEC2::DescribeVpcEndpointServices APIで確認しておきましょう。

$ REGION=ap-northeast-1
$ aws ec2 describe-vpc-endpoint-services \
  --service-names com.amazonaws.$REGION.iot.data \
  --region $REGION
{
    "ServiceDetails": [
        {
            "ServiceName": "com.amazonaws.ap-northeast-1.iot.data",
            "ServiceId": "vpce-svc-1234",
            "ServiceType": [
                {
                    "ServiceType": "Interface"
                }
            ],
            "AvailabilityZones": [
                "ap-northeast-1a",
                "ap-northeast-1c"
            ],
            "Owner": "amazon",
            "BaseEndpointDnsNames": [
                "data.iot.ap-northeast-1.vpce.amazonaws.com"
            ],
            "VpcEndpointPolicySupported": false,
            "AcceptanceRequired": false,
            "ManagesVpcEndpoints": false,
            "Tags": [],
            "SupportedIpAddressTypes": [
                "ipv4"
            ]
        }
    ],
    "ServiceNames": [
        "com.amazonaws.ap-northeast-1.iot.data"
    ]
}

$ REGION=us-east-1
$ aws ec2 describe-vpc-endpoint-services \
  --service-names com.amazonaws.$REGION.iot.data \
  --region $REGION
{
    "ServiceDetails": [
        {
            "ServiceName": "com.amazonaws.us-east-1.iot.data",
            ...
            "AvailabilityZones": [
                "us-east-1a",
                "us-east-1c",
                "us-east-1d"
            ],
            ...
}

Route 53 プライベートホストゾーンの作成

IoT CoreデータプレーンのエンドポイントはRoute 53プライベートホストゾーンで解決します。

まず、データエンドポイントを次のIoT::DescribeEndpoit APIで確認します。

$ aws iot describe-endpoint --endpoint-type iot:Data-ATS
{
    "endpointAddress": "123-ats.iot.ap-northeast-1.amazonaws.com"
}

ATSはAmazon Trust Servicesのことで、このエンドポイントはATS認証局が発行したクライアント証明書で認証します。

次に、ドメイン名が「iot.リージョン.amazonaws.com」のプライベートホストゾーンを作成し、VPCと関連付けます。

このゾーンに、データプレーンエンドポイントをVPCエンドポイントで解決する以下のエイリアス・レコードを追加します。

キー バリュー
レコード名 123-ats.iot.ap-northeast-1.amazonaws.com
レコードタイプ A
トラフィックのルーティング VPCエンドポイントへのエイリアス
vpce-xxx.data.iot.ap-northeast-1.vpce.amazonaws.com.
ルーティングポリシー Simple

VPCエンドポイントのDNSはAZ情報を含むものと含まないものがあります。AZ情報を含まない名前を利用してください。

  • vpce-XXX.data.iot.ap-northeast-1.vpce.amazonaws.com # AZ情報無し
  • vpce-XXX-ap-northeast-1a.data.iot.ap-northeast-1.vpce.amazonaws.com # AZ 1a
  • vpce-XXX-ap-northeast-1c.data.iot.ap-northeast-1.vpce.amazonaws.com # AZ 1c

EC2から名前解決

データエンドポイントがVPCのプライベートIPアドレスで解決されることを確認します。

$ dig +short xxx-ats.iot.ap-northeast-1.amazonaws.com
10.0.6.105
10.0.20.107

VPC外から名前解決した場合、以下の通りでした。

$ dig +short xxx-ats.iot.ap-northeast-1.amazonaws.com
35.72.127.146
54.168.193.76
35.72.70.240
52.69.36.252
54.238.138.112
35.72.204.130
18.180.217.41
3.114.189.83

EC2からmosquitto_pubでMQTT通信

クライアントプログラムは $ sudo apt install mosquitto-clients でインストールします。

AWS IoTのクイック接続でThing及び認証情報を払い出し、mosquitto_pub から MQTT 通信します。

$ HOST=xxx-ats.iot.ap-northeast-1.amazonaws.com
$ mosquitto_pub -d \
  --host $HOST \
  --cafile root-CA.crt \
  --cert TutorialTestThing.cert.pem \
  --key TutorialTestThing.private.key \
  --id basicPubSub \
  --topic "sdk/test/python" \
  --message '{  "message": "Hello from mosquitto_pub"}'

Client basicPubSub sending CONNECT
Client basicPubSub received CONNACK (0)
Client basicPubSub sending PUBLISH (d0, q0, r0, m1, 'sdk/test/python', ... (41 bytes))
Client basicPubSub sending DISCONNECT

クイック接続を利用したPython SDKおよびmosquitto_pubからのメッセージ送信手順については、次の過去ブログを参照ください

AWS IoTのクイック接続でPythonからIoT Coreと通信しつつMosquittoからもMQTT通信してみた | DevelopersIO

tcpdumpで通信を確認

tcpdump により、 ap-northeast-1c にあるプライベートIPアドレスが10.0.25.247のEC2から 10.0.6.105(ap-northeast-1a)/10.0.20.107(ap-northeast-1c)のVPCエンドポイントに通信できていることを確認できました。

$ sudo tcpdump -n -vv dst port 8883

tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

06:42:19.771027 IP (tos 0x0, ttl 64, id 65147, offset 0, flags [DF], proto TCP (6), length 60)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [S], cksum 0x4290 (incorrect -> 0x03b8), seq 3251971622, win 62727, options [mss 8961,sackOK,TS val 2710405658 ecr 0,nop,wscale 7], length 0
06:42:19.774071 IP (tos 0x0, ttl 64, id 65148, offset 0, flags [DF], proto TCP (6), length 52)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [.], cksum 0x4288 (incorrect -> 0xc757), seq 3251971623, ack 301642995, win 491, options [nop,nop,TS val 2710405661 ecr 3115711199], length 0
06:42:19.779744 IP (tos 0x0, ttl 64, id 65149, offset 0, flags [DF], proto TCP (6), length 405)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [P.], cksum 0x43e9 (incorrect -> 0xa39e), seq 0:353, ack 1, win 491, options [nop,nop,TS val 2710405667 ecr 3115711199], length 353
06:42:19.780313 IP (tos 0x0, ttl 64, id 65150, offset 0, flags [DF], proto TCP (6), length 52)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [.], cksum 0x4288 (incorrect -> 0xc583), seq 353, ack 101, win 491, options [nop,nop,TS val 2710405668 ecr 3115711207], length 0
06:42:19.780456 IP (tos 0x0, ttl 64, id 65151, offset 0, flags [DF], proto TCP (6), length 52)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [.], cksum 0x4288 (incorrect -> 0xb20b), seq 353, ack 5124, win 452, options [nop,nop,TS val 2710405668 ecr 3115711207], length 0
06:42:19.781352 IP (tos 0x0, ttl 64, id 65152, offset 0, flags [DF], proto TCP (6), length 52)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [.], cksum 0x4288 (incorrect -> 0xb0b9), seq 353, ack 5462, win 450, options [nop,nop,TS val 2710405669 ecr 3115711208], length 0
06:42:19.781365 IP (tos 0x0, ttl 64, id 65153, offset 0, flags [DF], proto TCP (6), length 52)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [.], cksum 0x4288 (incorrect -> 0xb08b), seq 353, ack 5508, win 450, options [nop,nop,TS val 2710405669 ecr 3115711208], length 0
06:42:19.785543 IP (tos 0x0, ttl 64, id 65154, offset 0, flags [DF], proto TCP (6), length 1324)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [P.], cksum 0x4780 (incorrect -> 0xd9c8), seq 353:1625, ack 5508, win 450, options [nop,nop,TS val 2710405673 ecr 3115711208], length 1272
06:42:19.786410 IP (tos 0x0, ttl 64, id 65155, offset 0, flags [DF], proto TCP (6), length 52)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [.], cksum 0x4288 (incorrect -> 0xab56), seq 1625, ack 5559, win 450, options [nop,nop,TS val 2710405674 ecr 3115711213], length 0
06:42:19.786471 IP (tos 0x0, ttl 64, id 65156, offset 0, flags [DF], proto TCP (6), length 106)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [P.], cksum 0x42be (incorrect -> 0xbd45), seq 1625:1679, ack 5559, win 450, options [nop,nop,TS val 2710405674 ecr 3115711213], length 54
06:42:19.851391 IP (tos 0x0, ttl 64, id 65157, offset 0, flags [DF], proto TCP (6), length 141)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [P.], cksum 0x42e1 (incorrect -> 0xcf60), seq 1679:1768, ack 5592, win 450, options [nop,nop,TS val 2710405739 ecr 3115711277], length 89
06:42:19.851564 IP (tos 0x0, ttl 64, id 65158, offset 0, flags [DF], proto TCP (6), length 114)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [FP.], cksum 0x42c6 (incorrect -> 0x6960), seq 1768:1830, ack 5592, win 450, options [nop,nop,TS val 2710405739 ecr 3115711277], length 62
06:42:19.852065 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [R], cksum 0x9a6d (correct), seq 3251973454, win 0, length 0
06:42:19.852082 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.25.247.49466 > 10.0.20.107.8883: Flags [R], cksum 0x9a6d (correct), seq 3251973454, win 0, length 0

IoT CoreのVPCエンドポイントの制限

IoT CoreデータのVPCエンドポイントの制限を確認します。

  • MQTT キープアライブ期間は 230 秒に制限
  • 各 VPC エンドポイントは、合計で 100,000 台の同時接続デバイスをサポート
  • VPC エンドポイントは IPv4 トラフィックのみをサポート
  • カスタムドメインを除き、ATS(Amazon Trust Services)証明書のみに対応
  • VPCエンドポイントポリシーは未対応
  • プライベートホストゾーンで名前解決させる必要がある

詳細は公式ドキュメントをご確認ください。

最後に

VPCエンドポイント経由でAWS IoT Coreデータプレーンと通信する方法を紹介しました。

冒頭でお伝えした通り、強い要件がない限り、VPCエンドポイントを介さずにインターネット経由でパブリックエンドポイントと通信する構成をまずはご検討ください。

参考