Chapter 11
8 min read

AWS CDK Secrets Manager Tutorial & Best Practices

Learn how to securely manage sensitive information in your cloud applications using AWS Secrets Manager and AWS Cloud Development Kit (CDK).

Feb 14, 2024
AWS CDK Secrets Manager Tutorial & Best Practices

AWS CDK Secrets Manager Tutorial & Best Practices

This article demonstrates how you can leverage Secrets Manager with AWS Cloud Development Kit (CDK), an open-source software development framework, to model and provision your AWS infrastructure as code. We will detail how to use AWS CDK to provision and reference secrets in Secrets Manager. We will also review other AWS CDK secrets manager options, like Parameter Store, and how integration with third-party tools can help improve secret AWS CDK secrets manager workflows.

Summary of AWS CDK Secrets Manager concepts

The table below summarizes the key concepts that will be covered in this article.

Common secrets and parameters used in AWS CDK projects

AWS CDK provides construct libraries for handling secrets and parameters using Secrets Manager and Parameter Store. Let’s take a quick look at the common use cases for secrets and parameters in a CDK project.

Common secrets

Some of the commonly used secrets in CDK projects are:

  • API keys: Authentication keys are crucial for interacting with API Gateways or external services.
  • Database credentials: Access details for securely connecting to databases.
  • IAM user credentials: Credentials for accessing your AWS account.
  • SSH Keys: These keys enable secure communication and access via SSH protocols..
  • Authentication tokens: Tokens validating and granting access within applications.

Common parameters

Some of the common parameters used along with CDK are:

  • Database connection URI: A passwordless string defining the connection details for databases.
  • Environment variables: Configurable variables within the application or code.
  • Resource ARNs: Amazon Resource Names (ARNs) represent the identity of the resources within your AWS account.

Managing Secrets in AWS CDK with Secrets Manager and Parameter Store

Now that we’ve gone through the common secrets and parameters used in CDK, let’s learn how we can use CDK to provision and retrieve these secrets in your project.

We will use the TypeScript CDK construct libraries to provision Secret Manager and SSM Parameter Store resources.

Secret Manager integration

To provision Secret Manager using AWS CDK, you must include the aws-cdk-lib/aws-secretsmanager module in your CDK project.

The following example demonstrates how to create a Secret Manager secret for database credentials.

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4
5export class SecretManagerCdkStack extends cdk.Stack {
6  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
7    super(scope, id, props);
8
9    // Secret Manager Credentials
10    const secret = new secretsmanager.Secret(this, 'SampleSecret', {
11      secretName: '/stage/credentials',
12      generateSecretString: {
13        secretStringTemplate: JSON.stringify({ username: 'admin' }),
14        generateStringKey: 'password',
15      },
16    });
17   }
18}

To create a Secret Manager store with a custom KMS encryption key, use the following code:

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4import * as kms from 'aws-cdk-lib/aws-kms'; 
5
6export class SecretManagerCdkStack extends cdk.Stack {
7  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
8    super(scope, id, props);
9    // KMS Key
10    const key = kms.Key.fromKeyArn(this, 'secretManagerKey', 'arn:aws:kms:ap-south-1:126345658785:key/8cd94c7e-ef37-4423-9bf7-2b0642fab0ef');
11
12    // Secret Manager Credentials
13    const secret = new secretsmanager.Secret(this, 'SampleSecret', {
14      secretName: '/stage/credentials',
15      generateSecretString: {
16        secretStringTemplate: JSON.stringify({ username: 'admin' }),
17        generateStringKey: 'password',
18      },
19      encryptionKey: key,
20    });
21   }
22}

To create a Secret Manager store for use cases such as API keys, use the following code:

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4
5export class SecretManagerCdkStack extends cdk.Stack {
6  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
7    super(scope, id, props);
8
9    // Secret Manager API Key
10    const apiKey = new secretsmanager.Secret(this, 'SampleApiKey', {
11      secretName: '/stage/api-key',
12      generateSecretString: {
13        secretStringTemplate: JSON.stringify({ apiKey: 'asdasdasd' }),
14        generateStringKey: 'apiKey',
15      },
16    });
17  }
18}

For the purpose of this article, let’s look at how we can inject the new Secret Manager value into a Lambda function:

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4
5export class SecretManagerCdkStack extends cdk.Stack {
6  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
7    super(scope, id, props);
8
9    // Secret Manager API Key
10    const apiKey = new secretsmanager.Secret(this, 'SampleApiKey', {
11      secretName: '/stage/api-key',
12      generateSecretString: {
13        secretStringTemplate: JSON.stringify({ apiKey: 'asdasdasd' }),
14        generateStringKey: 'apiKey',
15      },
16    });
17
18    // Associate Secret Manager with Lambda
19    const sampleLambda = new lambda.Function(this, 'SampleLambda', {
20      runtime: lambda.Runtime.NODEJS_18_X,
21      handler: 'index.handler',
22      code: lambda.Code.fromInline(`exports.handler = async (event) => { console.log("Hello World"); return { statusCode: 200, body: JSON.stringify('Hello from Lambda!') }; };`),
23      environment: {
24        API_KEY_ID: apiKey.secretValue.unsafeUnwrap().toString(), // This is not recommended
25      },
26    });
27  }
28}

Note that for security purposes, it is recommended to retrieve the secret at Lambda runtime logic to avoid exposing the secret in the CloudFormation template. For this, pass the secret ARN as an environment variable to the lambda function instead of passing the secret value directly.

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4
5export class SecretManagerCdkStack extends cdk.Stack {
6  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
7    super(scope, id, props);
8
9    // Secret Manager API Key
10    const apiKey = new secretsmanager.Secret(this, 'SampleApiKey', {
11      secretName: '/stage/api-key',
12      generateSecretString: {
13        secretStringTemplate: JSON.stringify({ apiKey: 'asdasdasd' }),
14        generateStringKey: 'apiKey',
15      },
16    });
17
18    // Associate Secret Manager with Lambda
19    const sampleLambda = new lambda.Function(this, 'SampleLambda', {
20      runtime: lambda.Runtime.NODEJS_18_X,
21      handler: 'index.handler',
22      code: lambda.Code.fromInline(`exports.handler = async (event) => { console.log("Hello World"); return { statusCode: 200, body: JSON.stringify('Hello from Lambda!') }; };`),
23      environment: {
24        API_KEY_ID: apiKey.secretArn // Note that we’re passing the secret ARN here
25      },
26    });
27  }
28}

To retrieve an existing Secret Manager secret in your CDK project using the Secret Manager ARN, use the following code:

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4import * as lambda from 'aws-cdk-lib/aws-lambda';
5
6export class SecretManagerCdkStack extends cdk.Stack {
7  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
8    super(scope, id, props);
9    
10    // Secret Manager secretKey
11    const secretKey = secretsmanager.Secret.fromSecretCompleteArn(this, 'secretKey', 'arn:aws:secretsmanager:ap-south-1:1234567895:secret:prod/secretKey-2pUG9Y');
12
13    // Associate Secret Manager with Lambda
14    const sampleLambda = new lambda.Function(this, 'SampleLambda', {
15      runtime: lambda.Runtime.NODEJS_18_X,
16      handler: 'index.handler',
17      code: lambda.Code.fromInline(`exports.handler = async (event) => { console.log("Hello World"); return { statusCode: 200, body: JSON.stringify('Hello from Lambda!') }; };`),
18      environment: {
19        API_KEY_ID: secretKey.secretValue.unsafeUnwrap().toString(), // This is not recommended
20      },
21    });
22  }
23}

SSM Parameter Store integration

To provision Parameter Store with the AWS CDK, include the aws-cdk-lib/aws-ssm module in your CDK project.

The following example demonstrates how you can create an SSM Parameter Store:

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as ssm from 'aws-cdk-lib/aws-ssm';
4
5export class SecretManagerCdkStack extends cdk.Stack {
6  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
7    super(scope, id, props);
8
9    // Parameter Store
10    const sampleStore = new ssm.StringParameter(this, 'SecretKey', {
11      parameterName: '/sample/secretvalue',
12      stringValue: 'asdasadas',
13      description: 'Sample secret value',
14      tier: ssm.ParameterTier.STANDARD,
15    });
16  }
17}

To inject a Parameter Store value into a Lambda function, use the following code:

1import * as cdk from 'aws-cdk-lib';
2import { Construct } from 'constructs';
3import * as ssm from 'aws-cdk-lib/aws-ssm';
4
5export class SecretManagerCdkStack extends cdk.Stack {
6  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
7    super(scope, id, props);
8
9    // Parameter Store
10    const sampleStore = new ssm.StringParameter(this, 'SecretKey', {
11      parameterName: '/sample/secretvalue',
12      stringValue: 'asdasadas',
13      description: 'Sample secret value',
14      tier: ssm.ParameterTier.STANDARD,
15    });
16
17    // Associate Secret Manager with Lambda
18    const sampleLambda = new lambda.Function(this, 'SampleLambda', {
19      runtime: lambda.Runtime.NODEJS_18_X,
20      handler: 'index.handler',
21      code: lambda.Code.fromInline(`exports.handler = async (event) => { console.log("Hello World"); return { statusCode: 200, body: JSON.stringify('Hello from Lambda!') }; };`),
22      environment: {
23        SECRET_VALUE: sampleStore.stringValue, // This is not recommended
24      },
25    });
26  }
27}

Similar to the Secrets Manager example mentioned above, using this method is not recommended for security reasons as it can expose the secret value. Instead, pass the secret ARN as an environment variable to the lambda function so that you can fetch the secret during runtime.

Secret Manager vs. Parameter Store

To learn more about which secret management solution suits your use case and to understand the difference between them, follow this detailed AWS guide by Doppler.

Six limitations of AWS native tools

AWS CDK faces the following limitations when it comes to secret management using Secrets Manager and Parameter Store in CDK projects:

  • Limited secret types: While Secrets Manager and Parameter Store support various secret types, CDK does not provide explicit constructs for all of them.
  • Dependency issues between CDK projects: When working with multiple CDK projects, managing dependencies between them can be challenging and becomes crucial when shared secrets or parameters are accessed across different CDK stacks.
  • Complex secret rotation setup: While Secret Manager supports automatic rotation of secrets, setting this up using CDK can be cumbersome. Developers might need to implement custom rotation logic using additional codes and Lambda functions, leading to complexities.
  • No automatic updation of secret references in CDK: When secrets or parameters are updated in Secrets Manager or Parameter Store, CDK does not automatically detect these changes and update the references in the deployed infrastructure. Developers must manually redeploy the CDK project to reflect the secret changes.
  • Parameter Store is unencrypted by default: AWS CDK does not support provisioning SecureString Parameter Store by default. This means that the secrets stored via CDK are by default unencrypted plaintext and encrypting them will require significant efforts outside of CDK.
  • No built-in logging and auditing capability: AWS CDK lacks built-in logging and auditing features tailored explicitly for secrets management. Developers must configure and manage AWS CloudTrail to enable these features at extra cost and complexity.

An alternative solution natively integrated with AWS

Doppler is a third-party secrets management tool that can integrate with AWS CDK and address the limitations mentioned above. Doppler provides a user-friendly platform for managing secrets, configurations, and credentials. It can enhance the CDK development experience in the following ways:

  • Centralized solution: Doppler provides a centralized platform for managing secrets and configurations across multiple projects and environments. Developers can use Doppler to store and retrieve secrets, ensuring consistency and ease of access across CDK projects.
  • Built-in rotation support: Doppler supports built-in rotation, allowing developers to automate the process of updating secrets. This eliminates the need for manual rotation logic outside CDK. Doppler can trigger rotations automatically and propagate updated values to CDK projects seamlessly.
  • Real-time updates to secrets: With real-time updates to secrets across platforms and environments, when a secret is updated in Doppler, the new value can be automatically reflected anywhere with the help of Doppler CLI or SDK.
  • Encrypted by default: Doppler supports SecureString parameters by default. The secrets stored in Doppler are encrypted at rest and in transit, providing a secure solution for managing sensitive information. CDK projects can easily reference SecureString from Doppler without additional complexity.
  • Built-in logging and auditing capability: Doppler includes robust logging and auditing features. Developers can easily track changes to secrets, view access logs, and monitor secret-related activities directly from the Doppler dashboard.

Conclusion

While AWS CDK can lay the foundation for infrastructure as code in your projects, it’s essential to acknowledge the limitations within CDK when it comes to handling secrets effectively. Challenges such as dependency management between CDK projects, external secret rotation, limited support for secret types, and the absence of built-in auditing and logging capabilities can introduce complexities and hinder a seamless development experience. Integrating Doppler with AWS CDK can significantly enhance the secrets management experience in your projects. Developers can mitigate these limitations by leveraging Doppler’s more efficient, secure, and agile solution for managing secrets and configurations in their AWS environments.