前回記事
前提条件
- IaC は CDK(CDK v2)
- Lambda のランタイムは Node.js 18(拡張子は.mjs)
- Lambda の実装は TypeScript(AWS SDK for JavaScript v3)
- API Gateway の API タイプは REST API
ファイル 構成
前回から Lambda Authorizer を追加している。
├─ src/
│ ├─ lib/
│ │ ├─ api-gateway/
│ │ │ ├─ handlers/
│ │ │ │ └─ sample-api/
│ │ │ │ └─ index.ts // Lambdaコード
│ │ │ │
│ │ │ ├─ authorizer.ts // Lambda Authorizer
│ │ │ └─ resource.ts // API Gateway + API GatewayがトリガのLambda定義
│ │ │
│ │ ├─ sample-stack.ts
│ │ └─ tsconfig.json
│ │
│ └─ main.ts
│
// 省略
│
├─ package.json
├─ package-lock.json
└─ tsconfig.json
実装
src/main.ts、src/lib/sample-stack.ts の実装は、前回参照。
src/lib/api-gateway/resource.ts
Authorizer にはCognitoUserPoolsAuthorizer、RequestAuthorizer、TokenAuthorizer等があるが、Lambda Authorizer の場合は、RequestAuthorizer を使用する。
前回と同様に Lambda Authorizer もts
をmjs
にコンパイルするためbundling
オプションを指定。
API のルート、メソッド設定はDefining APIsを参考に実装。
src/lib/api-gateway/resource.ts
import * as path from 'path';
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
interface Parameters {
environment: string;
}
export class ApiGateway {
private _parameters: Parameters;
constructor(parameters: Parameters) {
this._parameters = parameters;
}
public createResources(scope: Construct) {
const runtime = cdk.aws_lambda.Runtime.NODEJS_18_X;
const bundling = {
minify: true,
sourceMap: true,
externalModules: ['@aws-sdk/*'],
tsconfig: path.join(__dirname, '../tsconfig.json'),
format: cdk.aws_lambda_nodejs.OutputFormat.ESM,
};
// API Gateway(REST)
const restApi = new cdk.aws_apigateway.RestApi(scope, 'rest-api', {
restApiName: `${this._parameters.environment}_rest-api`,
});
// Lambda Authorizer
const authorizerFunction = new cdk.aws_lambda_nodejs.NodejsFunction(scope, 'authorizer-function', {
functionName: `${this._parameters.environment}_AuthorizerFunction`,
entry: path.join(__dirname, './authorizer.ts'),
handler: 'handler',
runtime,
memorySize: 1024,
bundling,
});
authorizerFunction.addPermission('authorizer-function-permission', {
principal: new cdk.aws_iam.ServicePrincipal('apigateway.amazonaws.com'),
action: 'lambda:InvokeFunction',
});
const lambdaAuthorizer = new cdk.aws_apigateway.RequestAuthorizer(scope, 'Authorizer', {
handler: authorizerFunction,
});
// Lambda Role
const lambdaBasicRole = new cdk.aws_iam.Role(scope, 'lambda-basic-role', {
assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
});
// Lambda
const sampleFunction = new cdk.aws_lambda_nodejs.NodejsFunction(scope, 'sample-function', {
functionName: `${this._parameters.environment}_sampleFunction`,
entry: path.join(__dirname, './handlers/sample-api/index.ts'),
handler: 'handler',
runtime,
memorySize: 1024,
role: lambdaBasicRole,
bundling,
});
// API Gatewayからの実行権限を追加
sampleFunction.addPermission('sample-function-permission', {
principal: new cdk.aws_iam.ServicePrincipal('apigateway.amazonaws.com'),
action: 'lambda:InvokeFunction',
});
// ルート設定(/api/groups/{groupId}/items/{itemId}) + Lambda Authorizer 指定
restApi.root
.addResource('api')
.addResource('groups')
.addResource('{groupId}')
.addResource('items')
.addResource('{itemId}')
.addMethod('GET', new cdk.aws_apigateway.LambdaIntegration(sampleFunction), {
authorizer: lambdaAuthorizer,
});
}
}
}
src/lib/api-gateway/authorizer.ts
Amazon API Gateway Lambda オーソライザーからの出力に従った形式で値を返却する。
Amazon API Gateway Lambda オーソライザーへの入力に Body は含まれないため、その他要素の検証を実装する。
API タイプが HTTP の場合は、レスポンス形式が異なる。
HTTP API の AWS Lambda オーソライザーの使用
src/lib/api-gateway/authorizer.ts
import * as lambda from 'aws-lambda';
export const handler: lambda.APIGatewayRequestAuthorizerHandler = async (
event: lambda.APIGatewayRequestAuthorizerEvent,
): Promise<lambda.APIGatewayAuthorizerResult> => {
try {
// 何かしらの検証処理を実装
return allowPolicy(event);
} catch (error) {
console.log(error);
return denyAllPolicy();
}
};
const allowPolicy = (event: lambda.APIGatewayRequestAuthorizerEvent): lambda.APIGatewayAuthorizerResult => {
return {
principalId: 'sample-user-id',
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: event.methodArn,
},
],
},
};
};
const denyAllPolicy = (): lambda.APIGatewayAuthorizerResult => {
return {
principalId: '*',
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: '*',
Effect: 'Deny',
Resource: '*',
},
],
},
};
};