cloudandthings.io
Published in

cloudandthings.io

terraform | git commit -m “all the secrets”

One of the first things I learned as a junior developer was to never push secrets to git. Sharing the secrets needed to bootstrap an environment has always been a bit wonky and I often wonder if we are not creating bigger security holes than what we were trying to avoid with not having secrets in a (private) repo. Another side effect of not having a proper way to handle secrets is that these things do not get rotated as often as they should have.

It is important to note that tools like HashiCorp Vault address a lot of the problems associated with sharing and distributing secrets, but even with Vault, you still need to bootstrap its trust at some point.

This problem got much more complicated when I started with infrastructure as code especially when doing it across many AWS accounts and projects. Keeping track of all these secrets became a nightmare, I really just wanted a way to check these secrets into git.

Having your secrets in the public domain is actually quite common. If you’ve ever used a service like Travis CI, then you’ll know that we store our secrets as secure environment variables in code. The trick here is that there is a central service (Travis) that can decrypt these secrets when they are needed, so how do we do this for something like Terraform, where you can do a deployment from your local machine or centrally using some CD tool (Terraform Cloud, GoCD, Github Actions, …).

👮‍♀️ sops to the rescue

No surprise that it is pretty common for developers to want to put secrets in code. The engineering team at Mozilla built sops for exactly this purpose, sops allow you to encrypt the keys in a key-value pair for commonly used file formats like JSON and YAML. What makes it really awesome is that it supports a wide array of cryptographic service providers like PGP, AWS KMS, GCP KMS and Azure Key Vault.

Imagine I have a file with the following content I want to use in my Terraform (TF) configuration:

cat sops.dec.yaml
api_token: 1234-5678-9012
some_other_secret: P@55W0rd!e

I can encrypt this file with sops using AWS KMS with the following command:

sops --encrypt --kms ‘arn:aws:kms:us-east-2:353444730604:key/d9ed5787–3300–4460-b1f5–0fc4948c19a5’ sops.dec.yaml > sops.enc.yaml

I now have a file called sops.enc.yaml that I can push to git and only people or systems with sufficient permissions on the KMS key can decrypt the content. Below is the content of the encrypted file. Note how only the keys of key-value pairs are encrypted. This allows you to do a proper git diff on this secret file to track changes to it, the downside is you are potentially leaking a bit of metadata about what the secrets are used, if this is a concern you can encrypt the whole file, but loose some of the benefits of having it in git.

cat sops.enc.yaml
api_token: ENC[AES256_GCM,data:Q0GNZ8ntLWcip5Df5vc=,iv:lLSwQxWOQgnLcJdJ/Hcvr83G1wa9Vz/n1gYRPIGOnJ0=,tag:2hcnn+f2zbDRkaceNd69oQ==,type:str]
some_other_secret: ENC[AES256_GCM,data:/gOv9m/ZK3uxRQ==,iv:nuNsrdV74KCFLjHsTJFWQZ1TSJNYPEFKUJMBCFFDlZU=,tag:dyuH+9HpjAKHuNBBDPfuxg==,type:str]
sops:
kms:
- arn: arn:aws:kms:us-east-2:353444730604:key/d9ed5787-3300-4460-b1f5-0fc4948c19a5
created_at: '2021-07-15T17:09:34Z'
enc: AQICAHjY7y7vtFFAU4LYNnRK7PpsVqG2ZahHzm9CYQOtf+XrbQFp3nyRmQfwDG/WTgQ6pHFkAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMl1MZdsNMcV8EJih7AgEQgDuwxRVumHul2tbOh9DXukNqT4J8XMXL3STqlDGgvtE1+W/UTgoq476Jtc8YUtWgQ50R86902i+H7W9J0Q==
aws_profile: ""
gcp_kms: []
azure_kv: []
lastmodified: '2021-07-15T17:09:36Z'
mac: ENC[AES256_GCM,data:woL8iy0GXN7RqkOLaOGTsGf7aHMusO9X/UrSjm9bzn3xzC3BpjwDG5qqiUdcqTwVXrCcUi990X+L55EwD2RtWZ63TLbsbukiTylOh/NU2qViCe9wA+yJyxJcmZC7rrTZvuZekWdHhqq7d/Lo6E7MwOyCv7Dzb8FBDHaj83cKykI=,iv:LYLk/frFPIzcwIcuutAjSCumVLIa9NIa2cta2ig3eso=,tag:xiaWWIguMdY4eY9Bl8Rtdw==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.5.0

Also, notice the aws_profile="" in the file, this means I used my default AWS credentials in my environment to access the KMS key. You can change this to any profile you have configured in your AWS CLI config or credentials file. Additionally, you can specify a role in the KMS block as described in the official sops docs.

This solves storing the secrets in code, but how do use this in TF. The simplest way would be for team members to decrypt the file and then load it up in TF using something like this.

First, decrypt the file:

sops --decrypt sops.enc.yaml > sops.dec.yaml

Load the secrets into TF. In TF 0.15 and later you can mark the value as sensitive that will prevent it from being displayed during plan and apply stages:

locals {
secrets = yamldecode(file('sops.dec.yaml'))
}
output "api_token" {
value = local.secret["api_token"]
}

Although this will work, it’s a tedious workflow and you stand the chance of the unencrypted file landing up in code, additionally, it will not work if you are using something like Terraform Cloud to manage your deployments.

terraform-provider-sops

sops contain a go package called decrypt which can be used by other go programs to decrypt sops files. Calle Pettersson used this package to build terraform-provider-sops, this TF provider allows you to load encrypted files directly into TF without the need to decrypt them first.

A simple example of how this can be used:

terraform {
required_providers {
sops = {
source = "carlpett/sops"
version = "~> 0.5"
}
}
}

data "sops_file" "demo-secret" {
source_file = "sops.dec.yaml"
}

output "api_token" {
value = data.sops_file.demo-secret.data["api_token"]
}

output "api_token_yaml" {
value = yamldecode(data.sops_file.demo-secret.raw).api_token
}

It is important to note that when trying to decrypt the secrets sops will need access to whatever cryptographic service provider you used, for example, if you used AWS KMS, sops used the Go AWS SDK, which will look for credentials to use in the local environment where this is run.

If you have other ways to sharing secrets in code or with team members, I’d love to hear about it.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store