認証情報プロバイダーのポリシー変数を利用してIoTデバイスからAWSサービスの直接呼び出しをセキュアに制御する

2021.01.08

CX事業本部@大阪の岩田です。AWS IoTのAuthorizing Direct Callsを利用するとIoTデバイスに一時クレデンシャルを払い出して、直接AWSのサービスにアクセスさせることができます。

このブログではAuthorizing Direct Callsをよりセキュアに利用するためのポリシー変数についてご紹介します。

Authorizing Direct Callsの気になるところ

Authorizing Direct Callsは便利な仕組みなのですが、事前に作成したIAMロールの持つ権限を各IoTデバイスに渡してしまうのはセキュリティ面が気になります。例えばIoTデバイスから直接DynamoDBに対してPutItemさせるシナリオを考えてみましょう。

DynamoDBのテーブル定義は以下の定義とします。

  • パーティションキー: device_id
    • AWS IoTのモノの名前を設定
  • ソートキー: timestamp
    • デバイス側でデータを送信する際のタイムスタンプを設定

ロールエイリアスに紐付けるIAMロールにはテーブルAへのPutItemのみ許可する権限を付与しておきます。ポリシードキュメントはこんなイメージです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:ap-northeast-1:<AWSアカウントID>:table/<DynamoDBのテーブル名>"
        }
    ]
}

ここで

  • デバイスA
  • デバイスB

2台のデバイスがAuthorizing Direct Callsを利用するとします。2台のデバイスは同じIAMロールの権限を行使できることになるので、デバイスAがPutItemしたデータをデバイスBがPutItemすることで上書きできてしまいます。以下のようなイメージです。

  • デバイスAが以下のデータをPutItem
  {
    "device_id": "device_a",
    "timestamp": "2021-01-07 12:00:00.000001",
    "data": "some data"
  }
  • デバイスBが以下のデータをPutItem
  {
    "device_id": "device_a",
    "timestamp": "2021-01-07 12:00:00.000001",
    "data": "デバイスBが設定した悪意のあるデータ"
  }

デバイスBがPutItemした悪意のあるデータでデバイスAが最初にPutItemしたデータを上書きできてしまいます。このようなリスクを避けるためにうまく権限を制御したいところです。何かうまい方法は無いのでしょうか?

認証情報プロバイダーがサポートするポリシー変数

調べたところIot Coreの認証プロバイダーはIAMのポリシー変数をサポートしていることが分かりました。サポートしているポリシー変数は以下の3つです。

  • credentials-iot:ThingName
    • Authorizing Direct Callsを利用するモノの名前に解決されます
  • credentials-iot:ThingTypeName
    • Authorizing Direct Callsを利用するモノのタイプ名に解決されます
  • credentials-iot:AwsCertificateId
    • 認証情報プロバイダーへのトークン発行リクエストに利用されたIoT Coreの証明書IDに解決されます

このポリシー変数を利用し、IAMロールにアタッチするポリシードキュメントに

"Condition": {
		"ForAllValues:StringEquals": {
				"dynamodb:LeadingKeys": "${credentials-iot:ThingName}"
				}
}

という記述を追加すれば、パーティションキーに設定された値がモノの名前と一致する場合だけPutItemを許可するような制御ができそうです。

やってみる

実際にポリシー変数を利用したアクセス制御を確認してみます。必要な事前準備や手順は冒頭に紹介したブログを参照して下さい。

https://dev.classmethod.jp/articles/aws-iot-authorizing-direct-calls/

先程紹介した定義でDynamoDBのテーブルを作成し、IAMロールに付与するポリシーは以下のように設定しておきます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:ap-northeast-1:<AWSアカウントID>:table/<DynamoDBのテーブル名>",
            "Condition": {
                "ForAllValues:StringEquals": {
                    "dynamodb:LeadingKeys": "${credentials-iot:ThingName}"
                }
            }
        }
    ]
}

普通にAuthorizing Direct CallsでPutItemしてみる

まずはdevice_aという名前のモノからAuthorizing Direct CallsでDynamoDBにPutItemしてみましょう。ローカルのMACからコマンドを叩きながらデバイスの動作をシミュレーションします。

まずは一時クレデンシャルを取得して...

$ CREDENTIALS=$(curl --cert <クライアント証明書>  --key <秘密鍵>  -H "x-amzn-iot-thingname: device_a" https://<認証情報プロバイダーのエンドポイント>/role-aliases/<ロールエイリアス名>/credentials)

一時クレデンシャルを環境変数にセット

$ export AWS_ACCESS_KEY_ID="$(echo ${CREDENTIALS} | jq -r '.credentials.accessKeyId')"
$ export AWS_SECRET_ACCESS_KEY="$(echo ${CREDENTIALS} | jq -r '.credentials.secretAccessKey')"
$ export AWS_SESSION_TOKEN="$(echo ${CREDENTIALS} | jq -r '.credentials.sessionToken')"

セットした一時クレデンシャルを使用してDynamoDBにPutItem実行

$ aws dynamodb put-item --table-name <DynamoDBのテーブル名> --item {"device_id":{"S":"device_a"},"timestamp":{"S":"2021-01-07 12:00:00.000001"},"data":{"S":"hoge"}}

問題なく実行できます。

別のデバイスになりすましてPutItemしてみる

続いてdevice_bというデバイスから一時クレデンシャルを取得し、device_aのデータをPutItemしてみましょう。

device_bのクライアント証明書を使用して一時クレデンシャルを取得

$ CREDENTIALS=$(curl --cert <クライアント証明書>  --key <秘密鍵>  -H "x-amzn-iot-thingname: device_b" https://<認証情報プロバイダーのエンドポイント>/role-aliases/<ロールエイリアス名>/credentials)

環境変数にセット

$ export AWS_ACCESS_KEY_ID="$(echo ${CREDENTIALS} | jq -r '.credentials.accessKeyId')"
$ export AWS_SECRET_ACCESS_KEY="$(echo ${CREDENTIALS} | jq -r '.credentials.secretAccessKey')"
$ export AWS_SESSION_TOKEN="$(echo ${CREDENTIALS} | jq -r '.credentials.sessionToken')"

DynamoDBへのPutItem実施。ここでパーティションキーdevice_idの値としてdevice_aを指定して別デバイスのデータの上書きを試みます。

$ aws dynamodb put-item --table-name <DynamoDBのテーブル名> --item {"device_id":{"S":"device_a"},"timestamp":{"S":"2021-01-07 12:00:00.000001"},"data":{"S":"hoge"}}

エラーになりました!

An error occurred (AccessDeniedException) when calling the PutItem operation: User: arn:aws:sts::<AWSアカウントID>:assumed-role/<IAMロール名>/<証明書ID> is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:ap-northeast-1:<AWSアカウントID>:table/<DynamoDBのテーブル名>

これで別デバイスのデータを好き勝手に上書きされることは無さそうです。

まとめ

もともと以下ブログのようにCognitoのFederated Identitiesを利用してアクセス権を細かく制御するパターンを知っていたので、AWS IoTでもきっと似たようなことができるだろうと調べたところ、案の上ポリシー変数がサポートされていました。

今回はDynamoDBへのPutItemを紹介しましたが、S3にPutObjectさせる場合などもポリシー変数を活用することでよりセキュアな構成が組めます。Authorizing Direct Callsを利用する場合は、ポリシー変数を利用して権限を絞ると良いでしょう。

参考