電通総研 テックブログ

電通総研が運営する技術ブログ

DynamoDB から取得できるアイテムの属性を IAM ポリシーで制限する方法

こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。

Amazon DynamoDB を利用する時、取得できる属性を特定の属性のみに制限したいことがあったため、IAM ポリシーを利用して実現する方法をまとめておきます。
ユースケースとしては、複数のアプリが同じ DynamoDB テーブルにアクセスするような構成において、特定のアプリには一部の属性しか見せたくないような場合です。

ユースケース

(あまり現実的ではないですが)簡単な例として、図のようにユーザー情報を保持する DynamoDB テーブルを「メール送信アプリ」と「データ分析アプリ」が利用しているとします。「メール送信アプリ」はユーザー名とメールアドレスだけを利用するのでそれ以外の属性は取得できないように制限をかけたいです。一方で「データ分析アプリ」は住所とジェンダーだけを利用するので、それ以外の属性は取得できないように制限をかけたいです。
これはアプリに許可を与える IAM ポリシーの Condition 句を利用することで実現できます。

DynamoDB テーブルの作成

まずは準備として図のように DynamoDB テーブルを作成します。パーティションキーとして user_id を単独のプライマリーキーとし、サンプルデータを追加しておきます。

サンプルテーブル

全属性の取得を許可する IAM ポリシーの付与

以下の IAM ポリシーでアプリケーションに GetItemScan の操作を許可します。取得できる属性を制限していないので、この状態では全属性を取得できるはずです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan"
            ],
            "Resource": "arn:aws:dynamodb:*:*:table/users"
        }
    ]
}

データ取得の確認1

AWS SDK for JavaScript によるデータ取得のコードサンプルです。まずは特定のアイテムを取得する GetItem 操作です。

import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({});
const command = new GetItemCommand({
  TableName: "users",
  Key: { user_id: { S: "1ab24x" } },
});
const result = await client.send(command);
console.log(result.Item);

アイテムの全属性を取得できました。

{
  user_id: { S: '1ab24x' },
  address: { S: '不思議の国X市' },
  email: { S: 'alice@wonderland.com' },
  user_name: { S: 'Alice' },
  gender: { S: 'F' }
}

次に、全アイテムを取得する Scan 操作です。

import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({});
const command = new ScanCommand({
  TableName: "users",
});
const result = await client.send(command);
console.log(result.Items);

こちらもアイテムの全属性を取得できました。

[
  {
    user_id: { S: 'k4p1c3' },
    address: { S: '鏡の国Y町' },
    email: { S: 'bob@wonderland.com' },
    user_name: { S: 'Bob' },
    gender: { S: 'M' }
  },
  {
    user_id: { S: '1ab24x' },
    address: { S: '不思議の国X市' },
    email: { S: 'alice@wonderland.com' },
    user_name: { S: 'Alice' },
    gender: { S: 'F' }
  }
]

(メイン)取得できる属性を制限する IAM ポリシーに修正

IAM ポリシーを修正し、明示的に許可した属性しか取得できないようにしていきます。以下のドキュメントを参考にします。
詳細に設定されたアクセスコントロールのための IAM ポリシー条件の使用

Condition 句に dynamodb:Attributesdynamodb:Select を追加します。dynamodb:Attributes には取得を許可したい属性名を配列で指定します。これにはプライマリーキーが含まれている必要があります。dynamodb:Select には SPECIFIC_ATTRIBUTES と記載します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan"
            ],
            "Resource": "arn:aws:dynamodb:*:*:table/users",
            "Condition": {
                "ForAllValues:StringEquals": {
                    "dynamodb:Attributes": [
                        "user_id",
                        "user_name",
                        "email"
                    ]
                },
                "StringEqualsIfExists": {
                    "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
                }
            }
        }
    ]
}

データ取得の確認2

IAM ポリシーを変更した状態で前と同じ GetItem 操作を行うと、次のようなエラーになりました。

AccessDeniedException: User: arn:aws:iam::111122223333:user/<ユーザ名> is not authorized to perform: dynamodb:GetItem on resource: arn:aws:dynamodb:ap-northeast-1:111122223333:table/users because no identity-based policy allows the dynamodb:GetItem action

dynamodb:GetItem が許可されていないというメッセージですが、実際には許可されていない属性を取得しようとしているためエラーが発生した状況です。次のようにプロジェクション式を利用して、許可された属性のみを取得するようにコマンドを変更します。

import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({});
const command = new GetItemCommand({
  TableName: "users",
  Key: { user_id: { S: "1ab24x" } },
  ProjectionExpression: "user_id, user_name, email", // これを追加
});
const result = await client.send(command);
console.log(result.Item);

こうするとエラーが解消し、結果も許可された属性のみが返却されていることがわかります。

{
  user_id: { S: '1ab24x' },
  email: { S: 'alice@wonderland.com' },
  user_name: { S: 'Alice' }
}

プロジェクション式に許可されていない属性(address など)が含まれていると、前述と同じエラーが発生します。

Scan 操作についても、プロジェクション式を追加して取得する属性を明示的に指定することで、エラーなくスキャン操作ができます。

import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({});
const command = new ScanCommand({
  TableName: "users",
  ProjectionExpression: "user_id, user_name, email", // これを追加
});
const result = await client.send(command);
console.log(result.Items);

許可された属性のみが返却されます。

[
  {
    user_id: { S: 'k4p1c3' },
    email: { S: 'bob@wonderland.com' },
    user_name: { S: 'Bob' }
  },
  {
    user_id: { S: '1ab24x' },
    email: { S: 'alice@wonderland.com' },
    user_name: { S: 'Alice' }
  }
]

まとめ

Condition 句に dynamodb:Attributesdynamodb:Select を利用することで、特定の属性のみ取得できるような IAM ポリシーの作り方をまとめました。アイテムを取得する際にはプロジェクション式を利用する必要があるので、どの属性の取得が許可されているのかアプリ開発側でも知っておく必要があります。

執筆:@kou.kinyo、レビュー:@handa.kenta
Shodoで執筆されました