Lambdaから別のアカウントのLambdaを呼び出すCDK構成
はじめに
Lambdaから別のアカウントのLambdaを呼び出す場合、2つの方法があります。
- IAMポリシーを使った呼び出し
- リソースポリシーを使った呼び出し
それぞれの方法のメリット・デメリットは以下の記事が参考になります。
今回は2つの方法をCDK(v2)で実装する方法を紹介します。
対象読者
- CDKとLambdaはTypeScriptを利用
- CDKはv2を利用(v1でも参考になるとは思います)
環境情報
ツール/パッケージ | バージョン |
---|---|
Node.js | 16.3.0 |
aws-cdk-lib | 2.8.0 |
aws-cdk | 2.8.0 |
aws-sdk | 2.1062.0 |
esbuild | 0.14.12 |
TypeScript | 3.9.7 |
構成
構成としては、以下の図のようになります。以後呼び出し元Lambdaをcaller、呼び出し先Lambdaをcalleeと表記します。
IAMポリシーを利用した呼び出し方法
callerの実装
CDK
コメントにて補足しています。
import { Stack, StackProps } from 'aws-cdk-lib'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import * as Lambda from 'aws-cdk-lib/aws-lambda' import * as Iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; const CALLEE_AWS_ACCOUNT_ID = process.env.CALLEE_AWS_ACCOUNT_ID! export class CallerStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // 後述するcallee側CDKで作成するロールのARN const CALLEE_ROLE_ARN = `arn:aws:iam::${CALLEE_AWS_ACCOUNT_ID}:role/callee-iam-invoke-role` // 後述するcallee側CDKで作成するLambdaのARN const CALLEE_LAMBDA_IAM_ARN = `arn:aws:lambda:ap-northeast-1:${CALLEE_AWS_ACCOUNT_ID}:function:sample-callee-iam` // callerのLambda実装 const callerIamLambda = new NodejsFunction(this, 'CallerIamLambda', { functionName: "sample-caller-iam", entry: "./src/caller-lambda-iam.ts", runtime: Lambda.Runtime.NODEJS_14_X, environment: { CALLEE_ROLE_ARN: CALLEE_ROLE_ARN, CALLEE_LAMBDA_ARN: CALLEE_LAMBDA_IAM_ARN } }) // calleeを呼び出すためのIAMロール(後述)に対し、assume roleできる権限を付与 callerIamLambda.addToRolePolicy( new Iam.PolicyStatement({ effect: Iam.Effect.ALLOW, actions: ['sts:AssumeRole'], resources: [CALLEE_ROLE_ARN], }) ); } }
Lambda
import { STS, Lambda } from 'aws-sdk'; const REGION = process.env.REGION!; export const lambdaClient = new Lambda({ region: REGION, signatureVersion: 'v4', }); const stsClient = new STS(); const AWS_LAMBDA_FUNCTION_NAME = process.env.AWS_LAMBDA_FUNCTION_NAME!; const CALLEE_ROLE_ARN = process.env.CALLEE_ROLE_ARN!; const CALLEE_LAMBDA_ARN = process.env.CALLEE_LAMBDA_ARN!; export const handler = async (): Promise<void> => { console.log(`start caller lambda`) // calleeのRoleを利用して、一時クレデンシャルを取得 const role = await stsClient.assumeRole({ RoleArn: CALLEE_ROLE_ARN, RoleSessionName: AWS_LAMBDA_FUNCTION_NAME, }).promise(); // クレデンシャル情報を更新 lambdaClient.config.update({ accessKeyId: role.Credentials?.AccessKeyId, secretAccessKey: role.Credentials?.SecretAccessKey, sessionToken: role.Credentials?.SessionToken, }); const invocationRequest: Lambda.InvocationRequest = { FunctionName: CALLEE_LAMBDA_ARN, InvocationType: 'RequestResponse', Payload: JSON.stringify({ "message": "sample-caller-iam" }), }; // calleeのLambda呼び出し await lambdaClient.invoke(invocationRequest).promise(); };
calleeの実装
CDK
import { Stack, StackProps } from 'aws-cdk-lib'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import * as Lambda from 'aws-cdk-lib/aws-lambda' import * as Iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; // callerのアカウントID const CALLER_AWS_ACCOUNT_ID = process.env.CALLER_AWS_ACCOUNT_ID! export class CalleeStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // calleeのLambda実装 const calleeIamLambda = new NodejsFunction(this, 'CalleeIamLambda', { functionName: "sample-callee-iam", entry: "./src/callee-lambda.ts", runtime: Lambda.Runtime.NODEJS_14_X }) // calleeの実行をcallerに許可にするIAMロール実装 new Iam.Role(this, 'InvokerRole', { roleName: `callee-iam-invoke-role`, assumedBy: new Iam.AccountPrincipal(CALLER_AWS_ACCOUNT_ID), // callerのアカウントを許可 inlinePolicies: { thing: new Iam.PolicyDocument({ statements: [ new Iam.PolicyStatement({ actions: ['lambda:InvokeFunction'], // callerに付与される権限 resources: [calleeIamLambda.functionArn], // callerが実行できるLambdaのARN }), ], }), }, }); } }
Lambda
受け取るだけですので、ロギングのみしています
interface Event { message: string } export const handler = async (event: Event) => { console.log(`start callee lambda`) console.log(`event: ${JSON.stringify(event)}`) }
リソースポリシーを利用した呼び出し方法
callerの実装
CDK
import { Stack, StackProps } from 'aws-cdk-lib'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import * as Lambda from 'aws-cdk-lib/aws-lambda' import * as Iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; const CALLEE_AWS_ACCOUNT_ID = process.env.CALLEE_AWS_ACCOUNT_ID! export class CallerStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const CALLEE_LAMBDA_RESOURCE_ARN = `arn:aws:lambda:ap-northeast-1:${CALLEE_AWS_ACCOUNT_ID}:function:sample-callee-resource` // caller側のLambda実装 const callerResourceBasedLambda = new NodejsFunction(this, 'CallerResourceLambda', { functionName: "sample-caller-resource", entry: "./src/caller-lambda-resource-based.ts", runtime: Lambda.Runtime.NODEJS_14_X, environment: { CALLEE_LAMBDA_ARN: CALLEE_LAMBDA_RESOURCE_ARN } }) // caller側のLambdaにcallee側のLambdaを実行する権限を付与 // (後述するcallee側のLambdaのリソースベースポリシーでcallerアカウントからの実行を許可する) callerResourceBasedLambda.addToRolePolicy(new Iam.PolicyStatement({ effect: Iam.Effect.ALLOW, actions: ['lambda:InvokeFunction'], resources: [`${CALLEE_LAMBDA_RESOURCE_ARN}`] })) } }
Lambda
import { Lambda } from 'aws-sdk'; const REGION = process.env.REGION!; export const lambdaClient = new Lambda({ region: REGION, signatureVersion: 'v4', }); const CALLEE_LAMBDA_ARN = process.env.CALLEE_LAMBDA_ARN!; export const handler = async (): Promise<void> => { console.log(`start caller lambda`) const invocationRequest: Lambda.InvocationRequest = { FunctionName: CALLEE_LAMBDA_ARN, InvocationType: 'RequestResponse', Payload: JSON.stringify({ "message": "sample-caller-resource" }), }; await lambdaClient.invoke(invocationRequest).promise(); };
calleeの実装
CDK
import { Stack, StackProps } from 'aws-cdk-lib'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs' import * as Lambda from 'aws-cdk-lib/aws-lambda' import * as Iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs'; const CALLER_AWS_ACCOUNT_ID = process.env.CALLER_AWS_ACCOUNT_ID! export class CalleeStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // calleeのLambda実装 const calleeResourceBasedLambda = new NodejsFunction(this, 'CalleeResourceLambda', { functionName: "sample-callee-resource", entry: "./src/callee-lambda.ts", runtime: Lambda.Runtime.NODEJS_14_X }) // リソースベースポリシーでcallerのAWSアカウントIDを許可 calleeResourceBasedLambda.addPermission('invokePermission', { principal: new Iam.AccountPrincipal(CALLER_AWS_ACCOUNT_ID), action: 'lambda:InvokeFunction', }); } }
Lambda
interface Event { message: string } export const handler = async (event: Event) => { console.log(`start callee lambda`) console.log(`event: ${JSON.stringify(event)}`) }
さいごに
本稿では、IAMポリシーとリソースポリシーを使ったクロスアカウントでのLambdaからLambdaを呼び出す方法を紹介しました。 caller側で呼び出し要件がLambdaしかない場合は、リソースベースポリシーの方が実装が薄く、管理するリソースも減るのでメリットが大きいです。 ユースケースに合わせて利用してみてください。