AWS Parameters and Secrets Lambda Extensionを使用して、Lambdaからシークレットマネージャーの値を取得します。
シークレットがキャッシュされるので、SDKで取得するより高速かつコスト削減になります。
AWS CDK
lib/sample-stack.ts
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodeJs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as sm from 'aws-cdk-lib/aws-secretsmanager';
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Secrets Manager
const secret = this.createSampleSecret();
// 既存のシークレットを取得する場合はfromSecretNameV2などを使用する
// https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_secretsmanager.Secret.html#static-fromwbrsecretwbrnamewbrv2scope-id-secretname
// const secret = sm.Secret.fromSecretNameV2(scope, 'Secrets', '/sample/login');
// Lambda
const lambda = this.createFunc();
secret.grantRead(lambda);
}
/**
* @description シークレットを作成
* {@link class Secret | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_secretsmanager.Secret.html}
*/
private createSampleSecret(): sm.ISecret {
return new sm.Secret(this, 'Secret', {
secretName: '/sample/login',
description: 'シークレットのサンプル',
secretObjectValue: {
clientID: cdk.SecretValue.unsafePlainText('dummy'),
clientSecret: cdk.SecretValue.unsafePlainText('dummy'),
publicKeyID: cdk.SecretValue.unsafePlainText('dummy'),
privateKey: cdk.SecretValue.unsafePlainText('dummy'),
passphrase: cdk.SecretValue.unsafePlainText('dummy'),
enterpriseID: cdk.SecretValue.unsafePlainText('dummy'),
},
});
}
/**
* @description Lambdaの作成
* {@link ParamsAndSecretsLayerVersion | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.ParamsAndSecretsLayerVersion.html}
* {@link NodejsFunction | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html}
*/
private createFunc(): lambdaNodeJs.NodejsFunction {
// AWS Parameters and Secrets Lambda Extension の作成
const paramsAndSecrets = lambda.ParamsAndSecretsLayerVersion.fromVersion(
lambda.ParamsAndSecretsVersions.V1_0_103,
{
cacheSize: 500,
logLevel: lambda.ParamsAndSecretsLogLevel.DEBUG,
}
);
return new lambdaNodeJs.NodejsFunction(this, 'SampleFunc', {
entry: 'src/lambda/sample/index.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_18_X,
timeout: cdk.Duration.minutes(5),
paramsAndSecrets,
});
}
}
補足
LambdaにVPCを設定する場合は、シークレットにアクセスするためにVPCエンドポイントが必要です。
// VPCエンドポイント
// https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.InterfaceVpcEndpoint.html
new ec2.InterfaceVpcEndpoint(this, 'VpcEndpointToSecretsManager', {
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
vpc: vpc,
subnets: vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_ISOLATED }),
});
Lambda
Lambda関数でシークレットの値を取得するにはHTTP GET リクエストを行います。
エンドポイントのポート番号とセッショントークンはAWS Parameters and Secrets Lambda Extensionの環境変数で取得します。
シークレットのキャッシュはデフォルトで300秒保持されます。環境変数のSECRETS_MANAGER_TTL
で変更可能です。
% npm install axios
src/lambda/sample/index.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import axios from 'axios';
const AWS_SESSION_TOKEN = process.env['AWS_SESSION_TOKEN'] || '';
const PARAMETERS_SECRETS_EXTENSION_HTTP_PORT = process.env['PARAMETERS_SECRETS_EXTENSION_HTTP_PORT'] || '';
interface LoginSecret {
clientID: string;
clientSecret: string;
publicKeyID: string;
privateKey: string;
passphrase: string;
}
/**
* {@link AWS Lambda 関数で AWS Secrets Manager シークレットを使用する | https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/retrieving-secrets_lambda.html}
* {@link axios | https://www.npmjs.com/package/axios}
*/
export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
// シークレットから値を取得する
const smUri = `http://localhost:${PARAMETERS_SECRETS_EXTENSION_HTTP_PORT}/secretsmanager/get`;
const res = await axios.get(smUri, {
params: { secretId: '/sample/login' },
headers: { 'X-Aws-Parameters-Secrets-Token': AWS_SESSION_TOKEN },
});
const secret = JSON.parse(res.data.SecretString) as LoginSecret;
console.debug(`clientID: ${secret.clientID}`);
console.debug(`clientSecret: ${secret.clientSecret}`);
console.debug(`publicKeyID: ${secret.publicKeyID}`);
console.debug(`privateKey: ${secret.privateKey}`);
return {
statusCode: 200,
body: '',
};
}
テスト
test/sample.test.ts
test/sample.test.ts
import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { SampleStack } from '../lib/sample-stack';
test('SM', () => {
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack');
const template = Template.fromStack(stack);
console.debug(JSON.stringify(template));
// Secrets Manager
template.resourceCountIs('AWS::SecretsManager::Secret', 1);
template.hasResourceProperties('AWS::SecretsManager::Secret', {
Description: 'シークレットのサンプル',
Name: '/sample/login',
SecretString:
'{"clientID":"client-id-dummy","clientSecret":"client-secret-dummy","publicKeyID":"public-key-id-dummy","privateKey":"private-key-dummy","passphrase":"passphrase-dummy"}',
});
});
test('Lambda', () => {
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack');
const template = Template.fromStack(stack);
console.debug(JSON.stringify(template));
// Lambda
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
Runtime: 'nodejs18.x',
Timeout: 300,
});
});