0
1

More than 1 year has passed since last update.

Lambda(Node.js)で Secrets Manager を使用する

Last updated at Posted at 2023-08-12

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,
    });
});

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1