Secure Secrets Management in Terraform — Part1: Leveraging AWS KMS

One of the key principles of modern Infrastructure as Code is the secure management of sensitive information. In thins first part of our series about secure secrets management in Terraform/OpenTofu, we will focus on the use of AWS Key Management Service, better known as KMS, to securely encrypt and manage secrets with Terraform/OpenTofu.

Prerequisites

  • AWS Account with appropriate permissions
  • Terraform/OpenTofu installed
  • AWS CLI configured

AWS KMS

First, let’s create a KMS key with proper permissions and configurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    resource "aws_kms_key" "secrets_key" {
      description             = "KMS key for encrypting application secrets"
      deletion_window_in_days = 7
      enable_key_rotation     = true
    }

    resource "aws_kms_alias" "secrets_key_alias" {
      name          = "alias/awsmorocco-key"
      target_key_id = aws_kms_key.secrets_key.key_id
    }

    resource "aws_kms_key_policy" "example" {
      key_id = aws_kms_key.secrets_key.id
      policy = jsonencode({
        Id = "example"
        Statement = [
          {
            Action = "kms:*"
            Effect = "Allow"
            Principal = {
              AWS = "*"
            }

            Resource = "*"
            Sid      = "Enable IAM User Permissions"
          },
        ]
        Version = "2012-10-17"
      })
    }

After applying this configuration, you can find the key in the AWS Console under KMS > Customer managed keys :

Image

Encrypting Secrets with AWS CLI

Now that we have our KMS key set up, we can use it to encrypt sensitive values using the AWS CLI:

1
2
3
4
5
6
    aws kms encrypt \
        --key-id alias/awsmorocco-key \
        --plaintext fileb://<(echo -n "my-super-secret-password") \
        --output text \
        --query CiphertextBlob \
        --region us-east-1

This command will output a base64-encoded encrypted string that you can safely store in your Terraform configurations

Using Encrypted Values in Terraform:

Create variables to store the encrypted values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    # variables.tf
    variable "encrypted_db_password" {
      description = "KMS encrypted database password"
      type        = string
      default     = "AQICAHhw31bEpV7TSsANCrrf6ZKimnQvVlNeIPn2xFDmUQPjOAGLFV0lcS4XiuwfqRIC1YI+AAAAdjB0BgkqhkiG9w0BBwagZzBlAgEAMGAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM/MZEgbuuMwVNBHJ1AgEQgDNUMmkSJO/f1t5w5JIWLc2MmGyU4/Az5IypmMuTTShUqRArmYsyzvA/G54jekuCyip7VmA="

      # the best way is to use a valuesfiles: terraform.tfvars
      # encrypted_db_password = "AQICAHhw31bEpV7TSsANCrrf6ZKimnQvVlNeIPn2xFDmUQPjOAGLFV0lcS4XiuwfqRIC1YI+AAAAdjB0BgkqhkiG9w0BBwagZzBlAgEAMGAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM/MZEgbuuMwVNBHJ1AgEQgDNUMmkSJO/f1t5w5JIWLc2MmGyU4/Az5IypmMuTTShUqRArmYsyzvA/G54jekuCyip7VmA="
    }

    variable "encrypted_api_key" {
      type    = string
      default = ""
    }

Create data sources to decrypt the values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    ### data sources

    data "aws_kms_secrets" "application_secrets" {
      secret {
        name    = "db_password"
        payload = var.encrypted_db_password
      }

      # You can decrypt multiple secrets in one block
      # secret {
      #   name    = "api_key"
      #   payload = var.encrypted_api_key
      # }
    }

    # Access the decrypted value
    locals {
      db_password = data.aws_kms_secrets.application_secrets.plaintext["db_password"]
      #api_key     = data.aws_kms_secrets.application_secrets.plaintext["api_key"]
    }

Use the decrypted values in your resources:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    ### Provision resources and provide the decrypted values

    resource "aws_db_instance" "db" {
      identifier           = "awsmorocco-db"
      engine               = "mysql"
      instance_class       = "db.t3.micro"
      username             = "awsmorocco-admin-user-1"
      password             = local.db_password # Using the decrypted
      parameter_group_name = "default.mysql8.0"
      skip_final_snapshot  = true
      allocated_storage    = 10
    }

Best Practices for KMS Usage

1 — Key Rotation

  • Enable automatic key rotation as we did in our configuration
  • Consider using different keys for different environments

2 — Access Control

  • Implement least-privilege access in your key policies
  • Use separate keys for different applications or services

3 — Monitoring

  • Enable AWS CloudTrail to audit KMS key usage
  • Set up alerts for unauthorized access attempts

4 — Security

  • Never store unencrypted sensitive values in version control
  • Use separate key aliases for different environments
  • Implement proper backup and recovery procedures

Next Steps

In Part 2 of this series, we’ll explore how to integrate AWS Secrets Manager with our KMS setup for more advanced secrets management capabilities.

Conclusion

Using AWS KMS with Terraform provides a secure way to manage encrypted secrets in your infrastructure code. By following these practices, you can ensure that your sensitive data remains protected while still being accessible to your authorized applications and services.

ℹ️ Terraform code is available here

Secure Secrets Management in Terraform — Part1: Leveraging AWSKMS was originally published in AWSMorocco on Medium, where people are continuing the conversation by highlighting and responding to this story.