Main issue

Terraform is a great tool for infra structure as code. I use it quite a lot for creating resources in AWS e.g. EC2, RDS instances. However, there are some resources in AWS that require passwords before Terraform can create it e.g. RDS instance or ElastiCache instance.

Of course you can define the user and the password as Terraform variables but these credentials will be plain text inside your project.

For instance if we want to create an RDS instance this what it would look like in Terraform:

resource "aws_db_instance" "default" {
  allocated_storage    = 20
  storage_type         = "gp2"
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t2.micro"
  name                 = "mydb"
  username             = "foo"
  password             = "foobarbaz"
  parameter_group_name = "default.mysql5.7"
}

As you can see the password foobarbaz will be stored as plain text in your terraform project.

Solution

Step 1. Create secrets directory

Create a secrets directory which will contains all sort of sensitive data used in Terraform. In this example we will focus on encrypting one secret i.e. RDS instance password.

mkdir secrets

echo "{ \"password\": \"foobarbaz\" }" >> secrets/rds.json


Step 2. Get secrets from the json file

data "external" "rds" {
  program  = [ "cat", ".secrets/rds.json"]
}


resource "aws_db_instance" "default" {
  allocated_storage    = 20
  storage_type         = "gp2"
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t2.micro"
  name                 = "mydb"
  username             = "foo"
  password             = "${data.external.rds.result.password}"
  parameter_group_name = "default.mysql5.7"
}


Step 3. Encryption

To encrypt the secret file i.e. rds.json we can use gpg and encrypting the file either with a key or passphrase.


gpg -o secrets/rds.json.gpg \
      --batch \
      --symmetric \
      --openpgp \
      --cipher-algo AES256 \
      --s2k-cipher-algo AES256 \
      --s2k-digest-algo SHA512 \
      --s2k-mode 3 \
      --s2k-count 65011712 \
      --armor \
      --emit-version \
      secrets/rds.json


Then you will be prompted for entering a pass-phrase twice.

This is what the output file rds.json.gpg will look like after encryption


cat secrets/rds.json.gpg
-----BEGIN PGP MESSAGE-----
Version: GnuPG/MacGPG2 v2

jA0ECQMKrGbmVhBpU7L/0lkBkpKcI8nXpiWDGKV7qdBDOt2Gb4qGvCu9aUkYdhcH
cwRsCBFyqDQRIfQSTmc1NFfr/3HJyADQKOBoftnCC1TGYYOL7AhexGhl0NaemqH8
zl1GOz3b1QHETg==
=Gyon
-----END PGP MESSAGE-----


Step 4. Wrap up

As you noticed the secrets directory will contains two files one is encrypted rds.json.gpg and one is not encrypted rds.son

Now we need to add the un-encrypted file to gitignore so it will not be committed.

.gitignore:

**/secrets/*
!**/secrets/*.gpg


Extra

To make this more efficient, you can create an init function that decrypts all secrets in your project. This way all secrets will be available for Terraform and you won’t get errors when running Terraform commands e.g. terraform plan, terraform apply

Note

This post only focuses on providing a solution to encrypt secrets without committing plain text passwords into your git project. However, the .tfstate will still contain the secrets un-encrypted.