この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
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
コメントにて補足しています。
lib/caller-stack.ts
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
src/caller-lambda-iam.ts
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
lib/callee-stack.ts
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
受け取るだけですので、ロギングのみしています
/src/callee-lambda.ts
interface Event {
message: string
}
export const handler = async (event: Event) => {
console.log(`start callee lambda`)
console.log(`event: ${JSON.stringify(event)}`)
}
リソースポリシーを利用した呼び出し方法
callerの実装
CDK
lib/caller-stack.ts
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
src/caller-lambda.ts
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
lib/callee-stack.ts
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
src/callee-lambda.ts
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しかない場合は、リソースベースポリシーの方が実装が薄く、管理するリソースも減るのでメリットが大きいです。 ユースケースに合わせて利用してみてください。