Friday, November 6, 2020

AWS RDS Secret Rotation configuration with Terraform

Problem:

AWS Secret Manager has an optional feature to automatically rotate the secret with specified intervals and they are using expandable design where entire rotation mechanism is based on prebuilt lambda function provided through AWS SAM (Serverless Application Model) Repository. This repository has several ready made password rotation lambda function for standard RDS database such as MySQL, Postgres, Oracle ect. 

When the secret rotation is enabled from the console GUI, AWS setup everything in the background to enable the key rotation without any manual user input. However if you are configuring Key rotation inside a Terraform configuration, all the operations done internally by GUI has to be performed by the terraform script. This article shows the steps required and some background information as well.

Solution:

Since AWS SAM Repository applications are provided as  CloudFormation templates, first we need to deploy the required stack from CloudFormation template using aws_cloudformation_stack resource. for this resource there are few required parameters to provided by argument template_body as a JSON document for the stack. This type of stack are called as a nested stack because instead of defining the required resources directly from JSON the template its utilize external template resources identified by an AWS arn to create the stack. For the JSON document:
  • The argument capabilities should be set with CAPABILITY_AUTO_EXPAND
  • Transform should be AWS:Serverless-2016-10-31 for CloudForm to identify this as a nested application stack
  • Type should be set to AWS::Serverless::application
  • Under the properties, ApplicationId specify the prebuilt rotation function and supportive resources such as execution policies to be created inside your AWS account before linking it with the Secret Manager. This parameter accept the standard AWS arn of the application that can be retrived on SAM Repository Console. for example arn:aws:serverlessrepo:us-east-1:297356227824:applications/<name_of_the_application>. name_of_the_application should be selected based on the type of RDS database and the type of authentication mode used.  "SecretsManagerRDSPostgreSQLRotationSingleUser" is used in this example to deploy the secret rotation lambda function and related resources for a RDS Postgres instance with Single user credentials mode. details of other available applications can be found here.
  • SemanticVersion is used to specify the version of the function as as AWS team keep developing this git repository and made available over the SAM Repository. As of the time of writing there was no way to determine the latest version of the application so I had to use AWS CLI command
    aws serverlessrepo list-application-versions --application-id <ApplicationId>
     to get the list of version available.  For the version it is recommended to use the latest version.
  • depending on the ApplicationId and the SemanticVersion parameters should be specified and terraform apply will fail with validation errors if not supplied correctly.

resource "aws_cloudformation_stack" "myappstack" {
  name = "myapp-stack"
  capabilities=["CAPABILITY_IAM","CAPABILITY_AUTO_EXPAND"]


  template_body = <<STACK
Transform: AWS::Serverless-2016-10-31
Resources:
  mySAR:
    Type: AWS::Serverless::Application
    Properties:
      Location:
        ApplicationId: "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSPostgreSQLRotationSingleUser"
        SemanticVersion: 1.1.80
      Parameters:
        endpoint: "https://secretsmanager.us-east-1.amazonaws.com"
        functionName: SecretsManager-new-version
        vpcSecurityGroupIds: "sg-012716d1a55d60278"
        vpcSubnetIds: "subnet-03f0cbeb7bd7f8f06,subnet-0d44efcaa26a2eb98"
STACK
}

Once the stack created resulting lambda function can be associated with with the secret by associating the lambda arn to the rotation_lambda_arnr argument. Although the aws_cloudformation_stack has an attribute outputs as a map of Cloudformation output values, terraform does not support those parameters and always returns an empty array.  as a workaround data resource aws_lambda_function can be used with the functionName given in the parameter block of the JSON document. optionally  depends_on can be used to make sure that data resources should be created after the completion of the stack.

data "aws_lambda_function" "db-rotation" {
    depends_on = [aws_cloudformation_stack.myappstack]
    function_name = "SecretsManager-new-version"
}

Then using the data resource defined above, lambda function arn can be assigned to the secret manager using aws_secretmanager_secret_rotation as follows

resource "aws_secretsmanager_secret_rotation" "database" {
    rotation_lambda_arn = data.aws_lambda_function.db-rotation.arn
    secret_id           = aws_secretsmanager_secret.db.id
    rotation_rules {
        automatically_after_days = 1
    }
}

reference: