この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
異なるAWSアカウントにあるDynamoDBテーブルに対して、Lambdaでアクセスする方法を試してみました。
おすすめの方
- 異なるAWSアカウントのリソースにLambdaでアクセスしたい
- CloudFormation、AWS SAMで上記の仕組みを構築したい
アクセスする側のデプロイ(LambdaとIAMロール)
まずは、DynamoDBテーブルにアクセスする側のLambdaとIAMロールをデプロイします。
SAM Init
sam init \
--runtime python3.8 \
--name Cross-Access-Lambda-Sample \
--app-template hello-world \
--package-type Zip
SAMテンプレート
Lambda用のIAMロールを作成しています。この時点では、アクセスされる側のIAMロールは未作成ですが、名前は事前に決めておきます。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
DstAwsAccountId:
Type: String
DstRoleName:
Type: String
TableName:
Type: String
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Timeout: 5
Environment:
Variables:
DST_AWS_ACCOUNT_ID: !Ref DstAwsAccountId
DST_ROLE_NAME: !Ref DstRoleName
TABLE_NAME: !Ref TableName
Role: !GetAtt HelloWorldFunctionRole.Arn
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
HelloWorldFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}
HelloWorldFunctionRole:
Type: AWS::IAM::Role
Properties:
RoleName: other-account-dynamodb-access-lambda-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: lambda.amazonaws.com
Policies:
- PolicyName: other-account-dynamodb-access-lambda-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- sts:AssumeRole
Resource: !Sub arn:aws:iam::${DstAwsAccountId}:role/${DstRoleName}
Outputs:
HelloWorldApi:
Value: !Sub https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/
Lambdaコード
アクセスされる側のIAMロールを指定し、AssumeRoleを行います。
boto3で「AssumeRoleの結果として得たAWSアクセスキー」を使って、DynamoDBテーブルにアクセスし、scan()
を実行しています。
app.py
import os
import json
import boto3
DST_AWS_ACCOUNT_ID = os.environ['DST_AWS_ACCOUNT_ID']
DST_ROLE_NAME = os.environ['DST_ROLE_NAME']
TABLE_NAME = os.environ['TABLE_NAME']
def lambda_handler(event, context):
sts = boto3.client('sts')
access_info = sts.assume_role(
RoleArn=f'arn:aws:iam::{DST_AWS_ACCOUNT_ID}:role/{DST_ROLE_NAME}',
RoleSessionName='cross_acct_lambda'
)
dynamodb = boto3.resource(
'dynamodb',
aws_access_key_id=access_info['Credentials']['AccessKeyId'],
aws_secret_access_key=access_info['Credentials']['SecretAccessKey'],
aws_session_token=access_info['Credentials']['SessionToken'],
)
table = dynamodb.Table(TABLE_NAME)
res = table.scan()
return {
'statusCode': 200,
'body': json.dumps(
res.get('Items', [])
),
}
デプロイ
デプロイします。アクセスされる側のAWSアカウントIDとIAMロール名もパラメータで付与しています。
sam build
sam package \
--output-template-file packaged.yaml \
--s3-bucket cm-fujii.genki-deploy
sam deploy \
--template-file packaged.yaml \
--stack-name Cross-Access-Lambda-Sample-Stack \
--s3-bucket cm-fujii.genki-deploy \
--parameter-overrides \
DstAwsAccountId=222222222222 \
DstRoleName=dynamodb-access-role \
TableName=access-sample-table \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
動作確認用にAPIエンドポイントを取得
aws cloudformation describe-stacks \
--stack-name Cross-Access-Lambda-Sample-Stack \
--query 'Stacks[].Outputs'
APIにアクセスすると、失敗する
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "Internal server error"}
CloudWatchのログを見ると、アクセスされる側のIAMロールに対して、AssumeRoleが失敗しています。 この時点では、まだ存在していないからです。
[ERROR] ClientError: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::111111111111:assumed-role/other-account-dynamodb-access-lambda-role/Cross-Access-Lambda-Sample-Stac-HelloWorldFunction-MoDfF3dqCFI3 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::222222222222:role/dynamodb-access-role
アクセスされる側のデプロイ
DynamoDBテーブルとIAMロールをデプロイします。
CloudFormationテンプレート
AssumeRole元として、アクセスする側のIAMロール(Lambdaが使うIAMロール)を指定します。 これによって、Lambdaが使うIAMロール以外はAssumeRoleができません。
dynamodb_and_iam.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: DynamoDB and IAM Role
Parameters:
SrcAwsAccountId:
Type: String
Resources:
AccessSampleTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: access-sample-table
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: deviceId
AttributeType: S
KeySchema:
- AttributeName: deviceId
KeyType: HASH
DynamodbAccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: dynamodb-access-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
AWS:
# AWSアカウントIDだけの指定も可能だが、セキュリティ的な意味でに強く非推奨
# - !Sub ${SrcAwsAccountId}
- !Sub arn:aws:iam::${SrcAwsAccountId}:role/other-account-dynamodb-access-lambda-role
Policies:
- PolicyName: dynamodb-access-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- dynamodb:Scan
Resource:
- !GetAtt AccessSampleTable.Arn
MaxSessionDuration: 3600
Outputs:
DynamodbAccessRoleArn:
Value: !GetAtt DynamodbAccessRole.Arn
デプロイ
アクセスする側のAWSアカウントIDをパラメータで付与しています。
aws cloudformation deploy \
--template-file dynamodb_and_iam.yaml \
--stack-name DynamoDB-and-IAM-Stack \
--parameter-overrides \
SrcAwsAccountId=111111111111 \
--capabilities CAPABILITY_NAMED_IAM
DynamoDBテーブルにデータを格納する
適当にデータを格納しました。
あらためてAPIアクセスし、異なるAWSアカウントのDynamoDBからデータを取得する
無事に取得できました!!
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
[{"name": "Fooooooo", "deviceId": "d0002"}, {"name": "This is a pen.", "deviceId": "d0001"}]
さいごに
異なるアカウントのリソースにアクセスする方法の助けになれば幸いです。