Managing (safely) Secrets as Code with sops and terraform

Terraform Secret as code sops git

3 min read | by Jordi Prats

If you are using Infrastructure as Code you've realized there is something it shouldn't be on a git repository: That's the secrets, we should never store clear-text secrets on a git repository, not even if it's a private repository: Anyone with access to that repository could get them.

How can we securely create secrets as code into the AWS Secrets Manager using terraform?

By using SOPS together with carlpett/sops terraform provider we can push secrets as code into AWS Secrets Manager using an encrypted file that can be safely pushed into a git repository.

First we'll need to install sops, using SOPS releases on github we can download it as a binary, as a RPM or a DEB package. For .deb we can install it as follows:

$ sudo dpkg -i sops_3.7.1_amd64.deb 

Once we have sops installed on our workstation we'll have to decide how are we going to encrypt. Since, on this example, we are going to push it into AWS Secrets Manager we can use a KMS key for encrypting the data.

To set the KMS key (or keys) to use we'll have to set the SOPS_KMS_ARN environment variable with it's arn:

export SOPS_KMS_ARN="arn:aws:kms:..."

With this variable set, we can now proceed to create the encrypted yaml file. We'll be using the following as a source:

password: 'secret'

To encrypt this file we will be using sops as follows, redirecting it's output to a new file (that will be stored on a git repository)

$ sops -e cleartext-secret.yaml > encrypted-secrets.yaml

The resulting file will look like this:

password: ENC[AES256_GCM,data:6sQAHANZUnAAnVin,iv:eKIxkTsASeeAAAAVMY61sAR52AKABT0MBXc96CojB1M=,tag:1Q0e2aaMSaYqsSUDOsfOBA==,type:str]
        - arn: arn:aws:kms:eu-west-1:123456789876:key/00aabbcc0-000e-0aa0-dead-abcdef01234
          created_at: "2021-12-25T10:13:19Z"
          enc: AQHaazICAQD8ffHiT77jQUzHG/ETF3uoEwnagRBmpX2tEz4hZgHhEXzYDShd78PsCOcRAAAAfjB8BgkEyRlqhkiG9wAgEAMGg0BBwagbzBtGCSqGSIb3DQEHATAeBgEAS4wEQQMklghkgBZQMbj+AgEQgvcS7MH1cJWuSDsMXm4SzIU++iO5WYSC2W7u7wUciOOaCNfa9YYLkp6fJtcsAvqnsDISh8/XMYRvoFi96a+YzrJzpHZrAg==
          aws_profile: ""
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age: []
    lastmodified: "2021-12-25T10:13:20Z"
    mac: ENC[AES256_GCM,data:ZfVJbtHwhdZ9ZfffffzoLbbJBu92c3xJw6iCQnWwWwWIr/bwBLPX89abc3X0muDeJ7VDWPz03hYnEJLUKyK7R1b8Y4jjpHV0XcAx81P43GpzvIqAcWgM1C4ffxF+ZQXlNdI5Fr0nBG3yhq9Jc4XAjhNkkkK5AhvtIrQRBwO1fCs8=,iv:d/9yhzRBb4+0cNhhkW7Ll003vUVw0uLqe+mHVHS3b4w=,tag:9fOkifqG3Xz+joEjfAgsNw==,type:str]
    pgp: []
    unencrypted_suffix: _unencrypted
    version: 3.7.1

Since data is encrypted and we need to be able to reach the KMS key to decrypt it, this data can be considered safe. Now we will be using terraform to read this file, decrypt the secret and push it into AWS Secrets Manager. To do so we will use the following terraform provider:

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

Using the sops_file datasource terraform is going to read the file an decrypt it's contents using the KMS key that's specified on it:

data "sops_file" "secrets" {
  source_file = "encrypted-secrets.yaml"

To push it's data into AWS Secrets Manager we just need to loop thru the data it reads, using aws_secretsmanager_secret and aws_secretsmanager_secret_version to create the secrets:

resource "aws_secretsmanager_secret" "secrets" {
  for_each                = nonsensitive(
  name                    = "Secrets/as/Code/${each.key}"
  description             = "Secrets as Code using sops: ${each.key}"
  recovery_window_in_days = 7
  kms_key_id              = var.kms_key_id

resource "aws_secretsmanager_secret_version" "secrets" {
  for_each      = nonsensitive(
  secret_id     = aws_secretsmanager_secret.secrets[each.key].id
  secret_string = sensitive([each.key])

Posted on 15/02/2022