Lambdaから別アカウントのリソースにアクセスしてみる

2020.10.25

こんにちは、Lambdaから別アカウントのリソースにアクセスする際にはAssumeRoleを行い一時的な権限を取得する必要があります。こちらの実装時にハマりましたので実装した手順をまとめたいと思います。

実装前にこちらのサイトを参考にEC2から他リソースのS3にアクセスする内容を試しました。とても分かりやすかったのでおすすめです。

別アカウントのS3バケットを利用する手順

手順

実装する内容は、図のようにアカウントAのLambdaからアカウントBのリソース(DynamoDBとS3)の読み込みを行うことです。

実装時にはこちらのリンクを参考に設定事項を確認しました。

別の AWS アカウントからロールを引き受けるように Lambda 関数を設定するにはどうすれば良いですか?

アカウントBの設定

まずはアカウントBに必要な設定を行います。ここでは以下の3つを作成します。

  • アカウントAでリソースに接続するrole
  • DynamoDBテーブル作成
  • S3バケット作成

アカウントAでリソースに接続するrole

アカウントAのLambdaから認証情報を確認するroleを作成します。IAMのコンソールからロールを選択し、ロールの作成を行います。

図のように、別のAWSアカウントを選択しアカウントIDには、アカウントAのIDを記入します。

今回はS3とDynamoDBの読み取りのみを行うので以下の2つのポリシーを選択します。

  • AmazonDynamoDBReadOnlyAccess
  • AmazonS3ReadOnlyAccess

role作成後は、ARNを控えます。

DynamoDBテーブル作成

DynamoDBテーブル作成とテストデータの投入を行うCLIコマンドをシェルファイル(make_put_dynamo.sh)にまとめます。

make_put_dynamo.sh

#!/bin/bash
profile=プロファイル名
dynamodb_name=作成するDynamoDBのテーブル名

## DynamoDBテーブルを作成
make_dynamo=`aws dynamodb create-table --table-name ${dynamodb_name} --profile ${profile} \
    --attribute-definitions AttributeName=Username,AttributeType=S AttributeName=date,AttributeType=S \
    --key-schema AttributeName=Username,KeyType=HASH  AttributeName=date,KeyType=RANGE \
    --provisioned-throughput ReadCapacityUnits=2,WriteCapacityUnits=2 \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES`
## テーブル作成まで待つ
wait_db=`aws dynamodb wait table-exists --table-name ${dynamodb_name} --profile ${profile}`
## 2件のデータを投入
put1=`aws dynamodb put-item --table-name ${dynamodb_name} --item '{ "date": {"S": "2020-10-17"}, "Username": {"S": "taro"}}' --profile ${profile}`
put2=`aws dynamodb put-item --table-name ${dynamodb_name} --item '{ "date": {"S": "2020-10-17"}, "Username": {"S": "hanako"}}' --profile ${profile}`

では上記のコードを実行しテーブルを作成します。

$ bash make_put_dynamo.sh

S3バケット作成

S3バケットを作成し、test.txtを作成したバケットにコピーします。

$ aws s3 mb s3://バケット名 --profile プロファイル名
$ aws s3 cp test.txt s3://バケット名/ --profile プロファイル名

アカウントAの設定

アカウントAでは、AssumeRoleできるポリシーとLambda関数の作成を行います。そして作成したLambda関数のroleにpolicyをアタッチします。

policyの作成

IAMのコンソール画面のポリシー項目を選択します。ポリシーの作成を選択後、JSON項目を選び下記内容で作成します。ResourceにはアカウントBで作成したroleのARNを書きます。ポリシー名はroleA-policyとします。

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::************:role/role-b-201020"
    }
}

Lambda関数の作成

関数名(accountA-lambda-201020)とランタイム(Python 3.7)を選択し関数の作成を行います。デフォルト設定で作成を行うと新規にroleが作成されていますので作成されたroleのリンクに飛びます。

ここで先ほど作成したポリシー(roleA-policy)をこのroleにアタッチします。

これでlambdaの設定が完了しました。

実装

アカウントA、Bの準備が整いましたのでアカウントAのLambdaからアカウントBのリソースへアクセスします。

アカウントAからBのリソースを読み込む際は、AWS Security Token Service (AWS STS) の AssumeRole API 呼び出しを行いサービスクライアントの作成に使用できる一連の資格情報を取得します。これにより指定したS3とDynamoDBへの読み込みが可能になります。

資格情報を取得しDynamoDBテーブルのデータ読み込みと、S3のバケット内のファイルを確認するコードを記載します(実行時は片方ずつ行います)。

import boto3

# アカウントBで作成したroleのARN
ROLE_ARN="arn:aws:iam::************:role/role-b-201020"

def lambda_handler(context, event):
    # 一時情報の取得
    sts_connection = boto3.client('sts')
    acct_b = sts_connection.assume_role(
        RoleArn=os.environ["my_account_role"],
        RoleSessionName="cross_acct_lambda"
    )
    
    ACCESS_KEY = acct_b['Credentials']['AccessKeyId']
    SECRET_KEY = acct_b['Credentials']['SecretAccessKey']
    SESSION_TOKEN = acct_b['Credentials']['SessionToken']
    
    # S3バケット内のオブジェクト読み取り
    s3 = boto3.resource('s3',
        aws_access_key_id=ACCESS_KEY,
        aws_secret_access_key=SECRET_KEY,
        aws_session_token=SESSION_TOKEN,)
    bucket = s3.Bucket("test-bucket-2009")
    print(bucket.name)
    print([obj_summary.key for obj_summary in bucket.objects.all()])

    # DynamoDBテーブルレコードの取得
    dynamo = boto3.client('dynamodb',
        aws_access_key_id=ACCESS_KEY,
        aws_secret_access_key=SECRET_KEY,
        aws_session_token=SESSION_TOKEN,
    )
    get_data = dynamo.scan(TableName="test_dynamo_2010")
    print(get_data)

S3読み込みの結果になります。バケット名とコピーしたファイル名が取得できました。

...
test-bucket-2009
['test.txt']
...

DynamoDB読み込みの結果です。こちらも、insertした2件のデータを確認することができました。

...
{'Items': [{'date': {'S': '2020-10-17'}, 'Username': {'S': 'hanako'}}, {'date': {'S': '2020-10-17'}, 'Username': {'S': 'taro'}}], 'Count': 2, 'ScannedCount': 2, 'ResponseMetadata': {'RequestId': '5R7D69ID2P4M19VHV9TGGQCIP3VV4KQNSO5AEMVJF66Q9ASUAAJG', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'Server', 'date': 'Sat, 24 Oct 2020 00:59:58 GMT', 'content-type': 'application/x-amz-json-1.0', 'content-length': '144', 'connection': 'keep-alive', 'x-amzn-requestid': '5R7D69ID2P4M19VHV9TGGQCIP3VV4KQNSO5AEMVJF66Q9ASUAAJG', 'x-amz-crc32': '530526495'}, 'RetryAttempts': 0}}
...

まとめ

別アカウントのリソースにアクセスするには、AssumeRoleを行い一時的な権限を取得してから操作する手順を実装できました!作業するまでは理解が不十分でしたが実装してみて少しだけ理解できました。別リソースへのアクセスの設定は難しいですがとても重要だと思いました。この記事がどなたかの助けになれば幸いです。

参考リンク