📝

AppSync で Lambda をデータソースとして DynamoDB にクロスアカウントアクセスしてみた

に公開

手順概要

  1. アカウント A で DynamoDB テーブルを作成
  2. アカウント B で Lambda 関数を作成
  3. アカウント B で GraphQL API を作成
  4. 動作確認

1. アカウント A で DynamoDB テーブルを作成

1-1. アクセス先となるアカウント A に DynamoDB テーブルを作成します。

  • パーティションキー: id (文字列)
  • ソートキー: なし
  • テーブル設定: デフォルト

1-2. テーブルに値を追加します。

  • id: 1
  • name: test (文字列)


1-3. テーブルにリソースベースのポリシーを定義します。
以下のようなポリシーを定義します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Statement1",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:role/LambdaBasicExecutionRole"
      },
      "Action": "dynamodb:*",
      "Resource": "arn:aws:dynamodb:ap-northeast-1:222222222222:table/table-name"
    }
  ]
}
  • Principal: アクセス元となる Lambda の IAM ロール
    • Lambda 関数作成後に置換してください
  • Resource: 作成した DynamoDB テーブルの ARN です。

今回は検証用のため Action を dynamodb:* で指定していますが、適宜最小限のポリシーに変更してください。

2. アカウント B で Lambda 関数を作成

2-1. アクセス元となるアカウント B に Lambda 関数を作成します。

  • ランタイム: Node.js 22.x (CommonJS module handler)
  • タイムアウト: 5 秒
  • IAM ロールの権限: AdministratorAccess ポリシーを付与
    • 適宜最小限のポリシーに変更してください
  • コードは以下の通り
index.js
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');

exports.handler = async function (event) {

  const dynamodb = DynamoDBDocumentClient.from(new DynamoDBClient());

  const params = {
    TableName: "arn:aws:dynamodb:ap-northeast-1:222222222222:table/table-name",
    Key: { id: event.arguments.id }
  };

  const response = await dynamodb.send(new GetCommand(params));

  return {
    id: response.Item.id,
    name: response.Item.name
  }
};
  • TableName はアカウント A の DynamoDB テーブル ARN です
    • 今回はハードコードしていますが適宜環境変数なども利用してください
  • DynamoDB に対して id が 1 のアイテムを返すようにクエリを実行しています

3-2. DynamoDB のリソーベースポリシーを更新
Lambda 関数作成後、Lambda 関数にアタッチされている IAM ロールを確認し、アカウント A の DynamoDB のリソーベースポリシーの Principal に設定してください。

3. アカウント B で GraphQL API を作成

3-1.AppSync コンソールで GraphQL API を作成します。

  • API タイプ: GraphQL API
  • GraphQL API データソース: Design from scratch
  • API 名: 任意の名称
  • GraphQL タイプを作成: 後で GraphQL リソースを作成

3-2. データソースを作成します。

  • データソース名: 任意の名称
  • データソースタイプ: AWS_Lambda
  • リージョン: Lambda 関数を作成したリージョン
  • 関数: 2 で作成した関数
  • 既存のロールを作成または使用する: 新しいロール

3-3. スキーマを作成します。
以下のスキーマを定義しました。

type user {
	id: String!
	name: String
}

type Query {
	getUser(id: String!): user!
}

スキーマの定義後に必ず保存ボタンをクリックしましょう。

3-4. リゾルバーを設定します。
スキーマの画面でリゾルバーの getUser(...): user! の「アタッチ」をクリックします。

  • リゾルバータイプ: ユニットリゾルバー
  • リゾルバータイプ: AppSync JavaScript (APPSYNC_JS)
  • データソース: 3-2 で作成したデータソース
  • リゾルバーコードは以下の通り
import { util } from '@aws-appsync/utils';

/**
 * Sends a request to a Lambda function. Passes all information about the request from the `info` object.
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {import('@aws-appsync/utils').LambdaRequest} the request
 */
export function request(ctx) {
    return {
        operation: 'Invoke',
        payload: {
            fieldName: ctx.info.fieldName,
            parentTypeName: ctx.info.parentTypeName,
            variables: ctx.info.variables,
            selectionSetList: ctx.info.selectionSetList,
            selectionSetGraphQL: ctx.info.selectionSetGraphQL,
            arguments: ctx.arguments,
        },
    };
}

/**
 * Process a Lambda function response
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the Lambda function response
 */
export function response(ctx) {
    const { result, error } = ctx;
    if (error) {
        util.error(error.message, error.type, result);
    }
    return result;
}

ほぼデフォルトの Lambda の呼び出しコードサンプルのままですが、arguments を追加してクエリから Lambda 関数に値を渡せるようにしました。

リゾルバーの定義後に必ず保存ボタンをクリックしましょう。

4. 動作確認

4-1. AppSync コンソールから以下のクエリを実行します。

query MyQuery {
  getUser(id: "1") {
    id
    name
  }
}

4-2. 以下の結果を取得できれば成功です。

{
  "data": {
    "getUser": {
      "id": "1",
      "name": "test"
    }
  }
}

まとめ

今回は AppSync で Lambda をデータソースとして DynamoDB にクロスアカウントアクセスしてみました。
どなたかの参考になれば幸いです。

参考資料

Discussion