I found AWS’s documentation for how to setup Secrets Manager secret rotation in CloudFormation to be severely lacking as no AWS documentation explains how to use the secret rotation templates provided by AWS within CloudFormation. Automating Secret Creation in AWS CloudFormation gives an example of how to setup the CloudFormation resources for the secret and its rotation, but it’s an incomplete example; it tells you where to place your Lambda’s S3 information in the template, but what if you want to use one of AWS’s provided rotation functions? AWS Templates You Can Use to Create Lambda Rotation Functions give you a list of RDS secret rotation “templates” and information about them but it does not give any information or examples for how to actually use them. After a bit of work, I figured out how to combine the information from those two documents to create a complete CloudFormation based RDS secret rotation example.
The AWS templates are Serverless Applications which can loaded in CloudFormation using AWS::Serverless::Application
. To use them, start with the CloudFormation example given in Automating Secret Creation in AWS CloudFormation. Add the top level directive Transform: AWS::Serverless-2016-10-31
to allow processing of Serverless resources. Remove the MyRotationLambda
resource. Add a new resource of type AWS::Serverless::Application
referencing one of the rotation function listed in AWS Templates You Can Use to Create Lambda Rotation Functions. Then change LambdaInvokePermission
to reference the Serverless Application. Here’s a complete example:
---
Description: "This is an example template to demonstrate CloudFormation resources for Secrets Manager"
Transform: AWS::Serverless-2016-10-31
Resources:
#This is a Secret resource with a randomly generated password in its SecretString JSON.
MyRDSInstanceRotationSecret:
Type: AWS::SecretsManager::Secret
Properties:
Description: 'This is my rds instance secret'
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: 'password'
PasswordLength: 16
ExcludeCharacters: '"@/\'
Tags:
-
Key: AppName
Value: MyApp
#This is a RDS instance resource. Its master username and password use dynamic references to resolve values from
#SecretsManager. The dynamic reference guarantees that CloudFormation will not log or persist the resolved value
#We use a ref to the Secret resource logical id in order to construct the dynamic reference, since the Secret name is being
#generated by CloudFormation
MyDBInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t2.micro
Engine: mysql
MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref MyRDSInstanceRotationSecret, ':SecretString:username}}' ]]
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref MyRDSInstanceRotationSecret, ':SecretString:password}}' ]]
BackupRetentionPeriod: 0
DBInstanceIdentifier: 'rotation-instance'
#This is a SecretTargetAttachment resource which updates the referenced Secret resource with properties about
#the referenced RDS instance
SecretRDSInstanceAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref MyRDSInstanceRotationSecret
TargetId: !Ref MyDBInstance
TargetType: AWS::RDS::DBInstance
#This is a RotationSchedule resource. It configures rotation of password for the referenced secret using a rotation lambda
#The first rotation happens at resource creation time, with subsequent rotations scheduled according to the rotation rules
#We explicitly depend on the SecretTargetAttachment resource being created to ensure that the secret contains all the
#information necessary for rotation to succeed
MySecretRotationSchedule:
Type: AWS::SecretsManager::RotationSchedule
DependsOn: SecretRDSInstanceAttachment
Properties:
SecretId: !Ref MyRDSInstanceRotationSecret
RotationLambdaARN: !GetAtt MyRotationServerlessApplication.Outputs.RotationLambdaARN
RotationRules:
AutomaticallyAfterDays: 30
#This is ResourcePolicy resource which can be used to attach a resource policy to the referenced secret.
#The resource policy in this example denies the DeleteSecret action to all principals within the current account
MySecretResourcePolicy:
Type: AWS::SecretsManager::ResourcePolicy
Properties:
SecretId: !Ref MyRDSInstanceRotationSecret
ResourcePolicy: !Sub '{
"Version" : "2012-10-17",
"Statement" : [
{
"Effect": "Deny",
"Principal": {"AWS":"arn:aws:iam::${AWS::AccountId}:root"},
"Action": "secretsmanager:DeleteSecret",
"Resource": "*"
}
]
}'
#This is a Serverless Application resource. We will use its lambda to rotate secrets
#For details about rotation lambdas, see https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html
MyRotationServerlessApplication:
Type: AWS::Serverless::Application
Properties:
Location:
ApplicationId: arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser
SemanticVersion: 1.0.117
Parameters:
endpoint: !Sub 'https://secretsmanager.${AWS::Region}.${AWS::URLSuffix}'
functionName: 'cfn-rotation-lambda'
#This is a lambda Permission resource which grants Secrets Manager permission to invoke the rotation lambda function
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt MyRotationServerlessApplication.Outputs.RotationLambdaARN
Action: 'lambda:InvokeFunction'
Principal: secretsmanager.amazonaws.com
This example uses SecretsManagerRDSMySQLRotationSingleUser which is the single user template for MySQL, however there are AWS provided templates for MariaDB, Oracle, and PostgreSQL as well. To find them, search the AWS Serverless Application Repository (the rotation templates all start with “SecretsManagerRDS”). Once you’ve found the desired template, click the “Deploy” button then select “Copy as SAM Resource” – the clipboard will now contain the CloudFormation YAML for the AWS::Serverless::Application
resource.
AWS Secrets Manager Rotation in CloudFormation by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Great article,
I hace found the article usefull. I was also searching for a bit more explanation for how to use the AWS provided lambda functions and didn’t find that in the AWS documentation.
I did some testing with this and noticed that the lambda function also gets updated when I update the RDS instance through cloudformation. I haven’t found an explanation for that yet. Hope to find an answer for that soon.
Regards