Seamlessly Integrating AWS Secrets in Lambda with CDK: A Step-by-Step Guide

Rob Wilson
4 min readAug 15, 2023

--

Photo by Sasun Bughdaryan on Unsplash

Amazon Web Services (AWS) offers a plethora of tools that aid developers in deploying secure, scalable, and efficient applications. Among these tools, the AWS Secrets Manager and AWS Systems Manager Parameter Store play an essential role in storing and managing sensitive information such as database credentials, API keys, and other secrets.

When building serverless applications with AWS Lambda, it is not uncommon for the code to require access to these secrets. In this blog post, we’ll guide you through accessing secrets stored in the Secrets Manager or Parameter Store within an AWS Lambda function using the AWS Cloud Development Kit (CDK).

Setting Up Secrets Manager and Parameter Store

Before you can access a secret in Lambda, you need to store it first.

Secrets Manager:

Go to the AWS Management Console, navigate to the Secrets Manager service, and create a new secret. Make sure to note the name of the secret.

Parameter Store:

Head to the AWS Systems Manager and navigate to the Parameter Store. Create a new parameter, ensure you save it as a ‘SecureString’ and note down the name of the parameter.

Accessing the secret in AWS Lambda

With the secret stored, we can now retrieve the data from within the Lambda function. The way we are going to access this data is by using an official AWS provided Lambda extension, rather than fetching the secret value at the time of deployment or by fetching the data using an API. The reasons for this are:

  • Fetching the data at request time means we don’t need to redeploy our CDK app if the secret is changed
  • The extension handles caching
  • The extension handles decryption

As per the AWS documentation:

This extension retrieves parameter values and caches them for future use. Using the Lambda extension can reduce your costs by reducing the number of API calls to Parameter Store. Using the extension can also improve latency because retrieving a cached parameter is faster than retrieving it from Parameter Store.

Writing our CDK app

This guides makes an assumption that you are familiar with CDK, if not, then I can recommend this great workshop to get you up to speed.

From within our stack constructor, we will define our Lambda, the AWS Parameters and Secrets Extension layer, and an IAM role to ensure we have permissions within Lambda to access Parameter Store, Secrets Manager, and KMS to decrypt the encrypted secret.

Define the IAM role

Note: On the new policies actions, remove the array entries as needed to maintain the principle of least privilege. If you are only using Secrets Manager, you should remove ssm:GetParameter and if you are only using Parameter Store, then you should remove secretsmanager:GetSecretValue.

import * as iam from 'aws-cdk-lib/aws-iam'

const myLambdaRole = new iam.Role(this, 'MyLambdaRole', {
roleName: 'my-lambda-role',
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
})

myLambdaRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['ssm:GetParameter', 'secretsmanager:GetSecretValue', 'kms:Decrypt'],
resources: ['*'],
}),
)

Define the Lambda and add the extension

After we have defined our role to provide permissions for our Lambda (and any extension) to access Parameter Store / Secrets Manager, we define the Lambda itself. In this example it is a NodeJS Lambda, we attach the role to it, and we then define the extension and attach it as a layer to our Lambda.

Please reference the table in the following link for the correct ARN for your region and Lambda architecture: https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#intel

import * as lambda from 'aws-cdk-lib/aws-lambda';

const myLambda = new lambda.Function(this, 'MyLambdaHandler', {
runtime: lambda.Runtime.NODEJS_18_X,
code: lambda.Code.fromAsset('lambda'),
handler: 'example.handler',
role: myLambdaRole,
});


const parametersAndSecretsExtension = lambda.LayerVersion.fromLayerVersionArn(
this,
'ParametersAndSecretsLambdaExtension',
'arn:aws:lambda:eu-west-1:015030872274:layer:AWS-Parameters-and-Secrets-Lambda-Extension:10',
)

myLambda.addLayers(parametersAndSecretsExtension)

Updating our Lambda code

Finally, we can write code within our Lambda to access our secrets. We make a HTTP request to localhost on port 2773 which the extension will listen to and return the secrets value from its cache or proxy the query to the appropriate AWS service if there is a cache miss. The port and other config can be changed by overriding environment variables as described in the referenced documentation.

We complete the URL with the name of the secret or parameter and finally add a header to our HTTP request with the session token which is automatically provided for us using the environment variable AWS_SESSION_TOKEN .

The following code example shows how to get a secureString parameter from Parameter Store using NodeJS native Fetch .


const response = await fetch(
`http://localhost:2773/systemsmanager/parameters/get/?name=${encodeURIComponent(
'my-parameter-name',
)}&withDecryption=true`,
{
headers: {
'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN,
},
},
)
const json = await response.json()
const mySecret = json.Parameter.Value

I hope you found this guide helpful; please consider giving the article a clap if it did! Happy coding!

References

https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html#ps-integration-lambda-extensions-add

https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html

--

--

Rob Wilson

Full stack developer trying to scratch the insatiable itch of curiosity and knowledge seeking