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

> Create KMS keys with rotation and policies, and store and read secrets in AWS Secrets Manager and SSM Parameter Store with Terraform or OpenTofu, safely.

- Source: https://www.itwonderlab.com/terraform-aws-kms-secrets-manager/
- Published: 2026-05-16
- Updated: 2026-05-16
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Keys and secrets as code

[AWS KMS](https://www.itwonderlab.com/aws-kms/) manages encryption keys, and [AWS Secrets Manager](https://www.itwonderlab.com/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](https://www.itwonderlab.com/terraform-secrets-management/) to understand what ends up in the [state](https://www.itwonderlab.com/terraform-state/).

### A customer managed KMS key

```hcl title="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](https://www.itwonderlab.com/aws-terraform-tutorial-aws-iam-roles-policies/).

### A secret in Secrets Manager

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

```hcl title="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:

```shell
$ 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:

```hcl title="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](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-backends/) or [OpenTofu state encryption](https://www.itwonderlab.com/terraform-state-file-encryption/). For RDS prefer `manage_master_user_password = true`, where AWS creates and rotates the secret and Terraform never sees the password ([RDS](https://www.itwonderlab.com/aws-terraform-tutorial-aws-rds/)).

### Reading a secret in Terraform

```hcl title="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:

```hcl title="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](https://www.itwonderlab.com/aws-systems-manager/) has no per-secret fee for standard parameters:

```hcl title="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.
