A few years ago, I needed periodic rotation for MongoDB Atlas API keys. We had a pretty well established CDK project with linting, cdk-nag, diffs on PRs, automatic deployments on merge to main, the works. Everything was reasonably locked down with least privilege, backups, automatically rotating secrets, hardened containers, and the kitchen sink. So obviously when adding MongoDB Atlas to the mix, I wanted to apply the same principles and build secure automated deployment with CDK.
Luckily for me, MongoDB Atlas has CloudFormation resources and even a CDK library. A lot of the resources are L1, but it’s better than creating custom resources for everything. Creating an API key is something along the lines of:
import * as cdk from 'aws-cdk-lib';import * as l1_resources from 'awscdk-resources-mongodbatlas/lib/l1-resources/api-key';const app = new cdk.App();const stack = new cdk.Stack(app, 'mongo-example');const apiSecret = new secretsmanager.Secret(stack, 'API Key Secret');new l1_resources.CfnApiKey(stack, 'API Key', { awsSecretName: apiSecret.secretFullArn,});
This takes care of creating the API key and writing it into a secret. It’s not exactly the usual CDK pattern where the secret is created but its value needs to be filled later outside of the deployment. It’s actually pretty nice, since one command can do it all including taking care of rollbacks and everything.
But there was still one problem. I wanted periodic API key rotation. For security. Of course…
The “proper” solution would probably involve:
- A scheduled Lambda
- Calling the Atlas API directly
- Storing the new secret
- Updating dependent systems
- Deleting old keys
- Handling rollback and partial failures
I really did not want to own that code. Or debug it. Or think about it. And I really wanted to keep using the available MongoDB CloudFormation resources. Switching to AWS Secrets Manager’s built-in rotation would mean generating API keys outside of CloudFormation.
Then I realized… CloudFormation already knows how to do create-before-delete safely.
So instead of building rotation logic, I just forced periodic replacement using CloudFormation.
The trick
Put the current month into the resource ID.
const apiSecret = new secretsmanager.Secret(stack, `API Key Secret m${new Date().getUTCMonth()}`);new l1_resources.CfnApiKey(stack, `API Key m${new Date().getUTCMonth()}`, { awsSecretName: apiSecret.secretFullArn,});
Next month:
- The logical ID changes
- CloudFormation creates a new API key
- References get updated
- Old key gets deleted once everything points to the new key
That’s it. Simple and effective.
No Lambda rotation logic.
No direct Atlas API integration.
No state machine.
No scheduling infrastructure.
Just normal CDK deployments.
Caveats
No one solution can fit every use case. This is true here as well.
This won’t work for strict rotation schedules unless you also automate deployments on a schedule. This only rotates during deployments. In this case ongoing development and continuous dependency version updates guarantee the API keys will be rotated on a reasonably consistent schedule.
Another issue was cross-stack usage of the secret. Deleting the old secret would fail with the good old deadly embrace. That was actually the case for this project. My solution was keeping last month’s secret around.
const apiSecret = new secretsmanager.Secret(stack, `API Key Secret m${new Date().getUTCMonth()}`);// we have to use modulo so the old id remains the same even when switching from december to januaryconst oldApiSecret = new secretsmanager.Secret(stack, `API Key Secret m${(new Date().getUTCMonth() + 11) % 12}`);new l1_resources.CfnApiKey(stack, `API Key m${new Date().getUTCMonth()}`, { awsSecretName: apiSecret.secretFullArn,});new l1_resources.CfnApiKey(stack, `API Key m${(new Date().getUTCMonth() + 11) % 12}`, { awsSecretName: oldApiSecret.secretFullArn,});
Keeping the old secret around will also help smooth out transition to the new secret by any systems that might cache it locally.
Summary
If you have a CloudFormation resource that needs rotation and you’re willing to live with an approximate rotation schedule, just slap the month number into its logical ID. CloudFormation will handle most rotation safety issues for you.