AWS IoTのデバイスシャドウにCognito認証済みユーザのIdentityでアクセスした際に403エラーになる

AWS IoTのデバイスシャドウにCognito認証済みユーザのIdentityでアクセスした際に403エラーになる
目次

AWS IoT。便利ですよね。遠隔からでもデバイスの状態管理や操作ができます。 一方、AWS IoTを使いこなすにはAWS IoTサービス全体の理解が必要で、操作性が良いとは言えないサービスコンソールを使いこなし、 少しトリッキーな設定を施すこともあります。今回はAWS IoTの小ネタを紹介します。

やりたいこと

  • AWS IoTのデバイスシャドウをモバイルアプリで取得する
  • 認証済みのユーザのみがデバイスシャドウにアクセスできる
  • 認証にはAWS Amplifyで作成されたCognitoのUserPoolとIdentity Poolを使う

環境情報

  • Android
    • aws-android-sdk-iot: 2.23.0

デバイスシャドウ取得時に403エラーが発生する

Androidにてデバイスシャドウの取得を行います。以下はAWS IoT-Data クライアントを使った簡単なスニペットになります。

1val dataClient = AWSIotDataClient(AWSMobileClient.getInstance().credentials)
2dataClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
3
4val deviceShadowsResult = dataClient.getThingShadow(
5  GetThingShadowRequest().withThingName(thingName)
6)

すると、以下のようなエラーが出力されました。

E/AndroidRuntime(15973): Caused by: com.amazonaws.AmazonServiceException: null (Service: AWSIotData; Status Code: 403; Error Code: ForbiddenException

これを解決する必要がありそうです。403エラーなので権限が足りないのでしょうか。

AWSの公式ドキュメント から引用すると、

認証済み ID を使用する場合、ID プールにアタッチされた IAM ポリシーに加えて、AttachPolicy API を使用して AWS IoT ポリシーを Amazon Cognito ID にアタッチし、AWS IoT アプリケーションの個々のユーザーにきめ細かいアクセス許可を与えることができます。このように、特定のお客様とそのデバイスの間のアクセス許可を割り当てることができます。Amazon Cognito ID のポリシーの作成の詳細については、パブリッシュ/サブスクライブポリシーの例 を参照してください。

とあります。さっそくやっていきましょう。

Cognito認証済みのIAM RoleにIAM Policyをアタッチする

まず、Cognitoにおける認証済みユーザに割り当てられるIAM Roleに対してIAM ポリシーを追加します。

AWS Amplifyで作成したCognitoを使っている場合には xxxxx-authRole という名前のIAM Roleが該当します。

このRoleに AWSIotDataAccess ポリシーと AWSIoTConfigAccess をアタッチします。

適用範囲としては少し広いため適宜調整が必要ですが、アタッチしたポリシーの中でも今回必要となる権限は以下のアクションとなります。

"iot:Connect",
"iot:Publish",
"iot:Subscribe",
"iot:Receive",
"iot:GetThingShadow",
"iot:UpdateThingShadow",
"iot:DeleteThingShadow",
"iot:ListNamedShadowsForThing",
"iot:AttachPolicy",
"iot:AttachPrincipalPolicy",
"iot:AttachThingPrincipal",

AWS IoT Policyの作成

次に AWS IoTポリシー を作成します。これはAWS IoTのデータプレーンにアクセスするために必要となります。 フォーマットはおなじみのAWS IAM ポリシードキュメントと同じです。

ここでは HogeIoTPolicy という名前で作成します。

 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4    {
 5      "Action": [
 6        "iot:Connect",
 7        "iot:Publish",
 8        "iot:Subscribe",
 9        "iot:Receive",
10        "iot:GetThingShadow",
11        "iot:UpdateThingShadow",
12        "iot:DeleteThingShadow",
13        "iot:ListNamedShadowsForThing"
14      ],
15      "Resource": "*",
16      "Effect": "Allow",
17      "Sid": "HogeIoTPolicy"
18    }
19  ]
20}

IoT Attach Policyを行う

さらに、認証済みのユーザに対してIoT Policyをアタッチします。 IoT PolicyのアタッチはIdentity IDに対して個別に適用する必要がある点に注意が必要です。

ユーザの増減を考慮するのであれば、以下のようにモバイルアプリ内でアタッチしてしまうのが良いでしょう。

1val identityId = AWSMobileClient.getInstance().identityId
2val iotClient = AWSIotClient(AWSMobileClient.getInstance().credentials)
3iotClient.attachPolicy(AttachPolicyRequest().withPolicyName("HogeIoTPolicy").withTarget(identityId))
1val thingName = "HogeThing"
2val identityId = AWSMobileClient.getInstance().identityId
3val iotClient = AWSIotClient(AWSMobileClient.getInstance().credentials)
4iotClient.attachThingPrincipal(AttachThingPrincipalRequest().withPrincipal(identityId).withThingName(thingName))

デバイスシャドウを取得する

ここまで設定して、ようやくデバイスシャドウにアクセスができます。

改めて、冒頭の GetThingShadowRequest が成功します。

1val dataClient = AWSIotDataClient(AWSMobileClient.getInstance().credentials)
2dataClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
3
4val deviceShadowsResult = dataClient.getThingShadow(
5  GetThingShadowRequest().withThingName(thingName)
6)

参考にさせていただいたサイト