Managing secrets in Terraform

Share on:

Managing secrets in Terraform can be daunting especially when plans are stored in plain text. But it doesn't have to be this way.

In this article I'm going to cover several methods of storing and accessing secrets that can be applied easily and could also be used in other tools such as Ansible and even Bash.

Prerequisites

In order to try any technique laid out in this article you will need the following:

The Terraform CLI, version 0.14 or later.

AWS Credentials configured for use with Terraform.

The git CLI.

To start login to the AWS Console and navigate to AWS Secrets Manager and click Store a new secret

Next select Other type of secrets followed by Plaintext.

Store a new secret

Fill out the required secrets information and click Next

Now give your secret a name and maybe a description for future reference.

Secret name and description

Depending on your needs you can now set up auto rotation (an excellent idea for RDS) however for this example just leave it as disabled.

Configure automatic rotation

Check the Review page and if everything is ok click Store

So now that we have a stored secret we can start using it in our Terraform code with the aws_secretsmanager_secret_version parameter.

1data "aws_secretsmanager_secret_version" "idm_admin_password" {
2  secret_id = "idm_admin_password"
3}

Decode the json file (If you saved your secret as json that it :wink: ).

1locals {
2  idm = jsondecode(data.aws_secretsmanager_secret_version.idm_admin_password.secret_string)
3  sensitive = true
4}

NOTE: The sensitive option is only available in Terraform 0.14 and above

 1resource "aws_db_instance" "primary" {
 2  allocated_storage    = 10
 3  engine               = "mysql"
 4  engine_version       = "5.7"
 5  instance_class       = "db.t3.medium"
 6  name                 = "Craft"
 7  username             = local.idm.username
 8  password             = local.idm.password
 9  parameter_group_name = "default.mysql5.7"
10  skip_final_snapshot  = true
11}

After doing a terraform plan you will see in the output that the password is marked as a sensive value

Terraform plan output

Pros

  • No plain text secrets are stored in terraform files or version control.
  • Because everything is codified there's no need for external scripts or wrappers.
  • AWS logs all transactions to the secrets manager so it's easy to see who did what and when.
  • As I hinted at the start because the data is stored in AWS it can be accessed using the API so effectively any application can use the data not just Terraform.

Cons

  • Cost! At the time of writing is costs $0.40 per secret per month. For secrets that are stored for less than a month, the price is prorated (based on the number of hours.) And on top of that there's $0.05 per 10,000 API calls. See this link for example costings

Alternatives

You could use AWS SSM Parameter Store. It's very simple to use in Terraform and other tools including Ansible or even Bash scripts.

Start by navigating to AWS Systems Manager and look down the left hand menu for Parameter Store

Click Create parameter and give the parameter a name (for example username) and give it a value that suits you.

Parameter store user

Do the same again for the password but this time select the SecureString option so that the contents are encrypted. For this you will need to choose an encryption method that fits your requirements. I'm choosing the default KMS key source in My current account.

Encrypted password settings

This is all that's required.

1data "aws_ssm_parameter" "user" {
2  name            = "idm_admin_user"
3  with_decryption = false
4}
5
6data "aws_ssm_parameter" "passwd" {
7  name            = "idm_admin__password"
8  with_decryption = true
9}

And then use it like this.

 1resource "aws_db_instance" "primary" {
 2  allocated_storage    = 10
 3  engine               = "mysql"
 4  engine_version       = "5.7"
 5  instance_class       = "db.t3.medium"
 6  name                 = "Craft"
 7  username             = data.aws_ssm_parameter.user
 8  password             = data.aws_ssm_parameter.passwd
 9  parameter_group_name = "default.mysql5.7"
10  skip_final_snapshot  = true
11}

The first thing to notice is that there are now two entries. One for the user and one for the password. Ok so it's a bit more coding but it is elegant.

true