AWS KMS and Secrets Manager with Terraform: Encrypt and Store Secrets

· 2 min read · Terraform & OpenTofu Tutorials

Keys and secrets as code #

AWS KMS manages encryption keys, and AWS Secrets Manager stores secrets (passwords, API keys) and can rotate them. Together they are the base of a secure application. Read first secrets management in Terraform to understand what ends up in the state.

A customer managed KMS key #

kms.tf
data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "key" {
  # The account administrators manage the key through IAM
  statement {
    sid       = "EnableIAMPolicies"
    actions   = ["kms:*"]
    resources = ["*"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
    }
  }
}

resource "aws_kms_key" "app" {
  description             = "Application data key"
  enable_key_rotation     = true
  deletion_window_in_days = 30
  policy                  = data.aws_iam_policy_document.key.json
}

resource "aws_kms_alias" "app" {
  name          = "alias/ditwl-pro-app"
  target_key_id = aws_kms_key.app.key_id
}

Notes:

  • The statement for the account root does not give access to everyone: it enables IAM policies to grant it. Without it, only the key policy decides, and you can lock yourself out.
  • enable_key_rotation = true rotates the key material every year.
  • deletion_window_in_days is the wait before real deletion. Data encrypted with a deleted key is lost.
  • Use the alias in code so applications do not depend on the key ID.

To let a role use the key, grant kms:Decrypt, kms:Encrypt and kms:GenerateDataKey on its ARN in the role's IAM policy.

A secret in Secrets Manager #

Create the secret as a container and keep the real value out of Terraform when you can:

secrets.tf
resource "aws_secretsmanager_secret" "api" {
  name        = "pro/app/api-key"
  description = "Third-party API key"
  kms_key_id  = aws_kms_key.app.arn

  recovery_window_in_days = 7
}

Set the value with the CLI or from the application that owns it:

$ aws secretsmanager put-secret-value \
    --secret-id pro/app/api-key --secret-string "$API_KEY"

When you must set it with Terraform (for example a generated password), remember that it will be in the state:

secrets.tf
resource "random_password" "db" {
  length  = 32
  special = false
}

resource "aws_secretsmanager_secret_version" "db" {
  secret_id     = aws_secretsmanager_secret.db.id
  secret_string = jsonencode({ username = "app", password = random_password.db.result })
}

Encrypt the state with a KMS-enabled backend or OpenTofu state encryption. For RDS prefer manage_master_user_password = true, where AWS creates and rotates the secret and Terraform never sees the password (RDS).

Reading a secret in Terraform #

read.tf
data "aws_secretsmanager_secret_version" "api" {
  secret_id = aws_secretsmanager_secret.api.id
}

locals {
  api_key = data.aws_secretsmanager_secret_version.api.secret_string
}

Any value that you read this way is stored in the state. Prefer that the application reads the secret at runtime with its own IAM role, and pass only the secret name or ARN as an environment variable.

Rotation #

Secrets Manager can rotate secrets with a Lambda function. For RDS, AWS provides ready-made rotation functions:

rotation.tf
resource "aws_secretsmanager_secret_rotation" "db" {
  secret_id           = aws_secretsmanager_secret.db.id
  rotation_lambda_arn = aws_lambda_function.rotate.arn

  rotation_rules {
    automatically_after_days = 30
  }
}

SSM Parameter Store as a cheaper alternative #

For configuration and simple secrets, Systems Manager Parameter Store has no per-secret fee for standard parameters:

ssm.tf
resource "aws_ssm_parameter" "app_config" {
  name  = "/pro/app/log_level"
  type  = "String"
  value = "info"
}

resource "aws_ssm_parameter" "api_key" {
  name   = "/pro/app/api_key"
  type   = "SecureString"
  key_id = aws_kms_key.app.arn
  value  = var.api_key # sensitive variable, ends up in the state
}
Secrets Manager Parameter Store
Automatic rotation Yes No
Price Per secret per month plus API calls Free for standard parameters
Cross-account sharing Yes, with resource policies Limited
Best for Database credentials, rotating secrets Configuration, static values

Cost and cleanup #

Each customer managed KMS key and each secret has a monthly fee. Secrets scheduled for deletion keep the name reserved during the recovery window, which can break a quick destroy followed by apply with the same name: set recovery_window_in_days = 0 only in test environments.

#AWS #AWS KMS #Security #Terraform #OpenTofu