Creating and controlling AWS Key Management Service keys with Terraform requires careful handling of key policies, grants, aliases, and module abstractions. The patterns below are drawn from existing Terraform practices for provisioning customer managed keys, updating existing key policies without recreation, and restricting default root access.
Introduction
Terraform provides resources for provisioning KMS keys and their associated policy, alias, grant, and external key objects. Because a KMS key policy is the ultimate access control for the key, changes to it must be performed with awareness of lockout risk, service-specific grant requirements, and Terraform's import behavior for existing keys.
Terraform Resources for KMS
AWS provider exposes a set of KMS resources and data sources.
9 Terraform resources and 7 data sources available.
Resources
| Resource | Management Scope |
|---|---|
| awskmsalias | Manages an Kms Alias resource. |
| awskmsciphertext | Manages an Kms Ciphertext resource. |
| awskmscustomkeystore | Manages an Kms Custom Key Store resource. |
| awskmsexternal_key | Manages an Kms External Key resource. |
| awskmsgrant | Manages an Kms Grant resource. |
| awskmskey | Manages an Kms Key resource. |
| awskmskey_policy | Manages an Kms Key Policy resource. |
| awskmsreplicaexternalkey | Manages an Kms Replica External Key resource. |
| awskmsreplica_key | Manages an Kms Replica Key resource. |
These resources cover key creation, alias naming, ciphertext operations, custom key stores, external key material, grants, policy attachment, and multi-region replica keys.
Creating KMS Keys with Terraform Modules
Modules encapsulate key creation with policy defaults and tagging.
Reference usage for EC2 AutoScaling service linked role to launch encrypted EBS volumes:
hcl
module "kms" {
source = "terraform-aws-modules/kms/aws"
description = "EC2 AutoScaling key usage"
key_usage = "ENCRYPT_DECRYPT"
key_administrators = ["arn:aws:iam::012345678901:role/admin"]
key_service_roles_for_autoscaling = ["arn:aws:iam::012345678901:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling"]
aliases = ["mycompany/ebs"]
tags = {
Terraform = "true"
Environment = "dev"
}
}
Reference usage for external CMK with externally provided encryption material:
hcl
module "kms" {
source = "terraform-aws-modules/kms/aws"
description = "External key example"
key_material_base64 = "Wblj06fduthWggmsT0cLVoIMOkeLbc2kVfMud77i/JY="
valid_to = "2085-04-12T23:20:50.52Z"
key_owners = ["arn:aws:iam::012345678901:role/owner"]
key_administrators = ["arn:aws:iam::012345678901:role/admin"]
key_users = ["arn:aws:iam::012345678901:role/user"]
key_service_users = ["arn:aws:iam::012345678901:role/ec2-role"]
aliases = ["mycompany/external"]
aliases_use_name_prefix = true
grants = {
lambda = {
grantee_principal = "arn:aws:iam::012345678901:role/lambda-function"
operations = ["Encrypt", "Decrypt", "GenerateDataKey"]
constraints = {
encryption_context_equals = {
Department = "Finance"
}
}
}
}
tags = {
Terraform = "true"
Environment =
}
}
The cloudposse module provides a simpler alias and key provisioning pattern.
hcl
module "kms_key" {
source = "cloudposse/kms-key/aws"
namespace = "eg"
stage = "test"
name = "chamber"
description = "KMS key for chamber"
deletion_window_in_days = 10
enable_key_rotation = true
alias = "alias/parameter_store_key"
}
Important requirements for the cloudposse module:
| Name | Version |
|---|---|
| terraform | >= 0.13 |
| aws | >= 3.64.0 |
| Name | Version |
|---|---|
| aws | >= 3.64.0 |
| Name | Source | Version |
|---|---|---|
| this | cloudposse/label/null | 0.25.0 |
| Name | Type |
|---|---|
| awskmsalias.default | resource |
| awskmskey.default | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| additionaltagmap | Additional key-value pairs to add to each map in tagsaslistofmaps. Not added to tags or id.This is for some rare cases where resources want additional configuration of tagsand therefore take a list of maps with tag key, value, and additional configuration. | map(string) | {} | no |
| alias | The display name of the alias. The name must start with the word alias followed by a forward slash. If not specified, the alias name will be auto-generated. | string | "" | no |
| attributes | ID element |
Typical outputs exported for reuse:
```hcl
output "kmskeyid" {
description = "The KMS key ID"
value = awskmskey.main.key_id
}
output "kmskeyarn" {
description = "The KMS key ARN"
value = awskmskey.main.arn
}
output "kmsaliasarn" {
description = "The KMS alias ARN"
value = awskmsalias.main.arn
}
```
Key Policy Defaults and Restricting Default Access
The objective of this post is to implement KMS key access security for AWS Identity and Access Management identities by changing the default policy when provisioning the resource with Terraform.
The default KMS key policy contains the following statement:
json
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "kms:*",
"Resource": "*"
}
By default KMS policy allow caller's account to use IAM policy to control key access. The Effect and Principal elements do not refer to the AWS root user account.
Instead, it allows any principal in AWS account 123456789012 to have root access to the KMS key as long as you have attached the required permissions to the IAM entity.
The created terraform blueprint will come with the following custom policy by default:
json
{
"Sid": "Enable root access and prevent permission delegation",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "kms:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalType": "Account"
}
}
},
{
"Sid": "Allow access for key administrators",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:role/TERRAFORM",
"arn:aws:iam::012345678901:role/ADMIN"
]
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "Enable read access to all identities",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"kms:List*",
"kms:Get*",
"kms:Describe*"
],
"Resource": "*"
}
The key policy allows the following permissions:
- First statement: The AWS root user account has full access to the key.
- Second statement: The principals role ADMIN and TERRAFORM has access to perform management operations on the key
Note: This post demonstrates the AWS account ID 123456789012 with existing role named TERRAFORM, ADMIN and ANALYST. These values must be replaced for your environment.
Updating an Existing KMS Key Policy with Terraform
While Terraform doesn't directly support updating an existing KMS key policy through its resources, there are a few approaches you can consider to achieve this.
First approach uses data sources to read then rewrite policy.
hcl
data "aws_kms_key" "existing_key" {
key_id = "your-key-id-or-arn"
}
hcl
data "aws_kms_key_policy" "existing_policy" {
key_id = data.aws_kms_key.existing_key.id
}
hcl
locals {
updated_policy = jsonencode({
Version = "2012-10-17"
Statement = concat(
jsondecode(data.aws_kms_key_policy.existing_policy.policy).Statement,
[
{
Sid = "NewStatement"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::123456789012:user/NewUser"
}
Action = ["kms:Encrypt", "kms:Decrypt"]
Resource = "*"
}
]
)
})
}
hcl
resource "aws_kms_key_policy" "updated_policy" {
key_id = data.aws_kms_key.existing_key.id
policy = local.updated_policy
}
Alternative approach uses resource import.
If you need more control or if the above method doesn't work for your use case, you can use the awskmskey resource with the policy argument, but you'll need to import the existing key first:
hcl
resource "aws_kms_key" "existing_key" {
description = "Existing KMS key"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# Your existing policy statements
Common Mistakes and Operational Guidance
Locking yourself out. If you remove the root account access from the key policy and no remaining principal can update the policy, you can't modify the key anymore. The only way to recover is to contact AWS support.
Forgetting grants. Some AWS services like EBS and RDS use grants instead of direct key policies. Make sure the roles that interact with these services have kms:CreateGrant permission.
Deleting keys too quickly. The minimum deletion window is 7 days. Always use the maximum of 30 days for production keys. Better yet, disable the key first and wait before scheduling deletion.
You'll need to handle key rotation manually for these.
KMS keys are the backbone of encryption on AWS. Customer-managed keys give you control over who can access your encrypted data, audit trails through CloudTrail, and automatic key rotation. Create separate keys for different services, always include root account access in key policies, and never set the deletion window below 30 days for production. Encryption is only as strong as your key management, and Terraform makes it auditable.
Conclusion
Terraform KMS key management spans creation, policy definition, aliasing, grants, and updates to existing keys. Modules from terraform-aws-modules and cloudposse provide opinionated defaults for key administrators, owners, users, and service roles, while raw resources awskmskey and awskmskeypolicy give fine-grained control. Updating an existing key policy is best performed by reading the current policy with data sources, merging new statements via locals, and applying with awskmskeypolicy, or by importing the key and managing its policy as code. Restricting default root access requires replacing the permissive Enable IAM User Permissions statement with a conditioned root statement and explicit administrator statements for TERRAFORM and ADMIN roles. Operational safety demands retaining root account access for recovery, understanding service-specific grant requirements, and enforcing a deletion window of at least 30 days for production keys. With these patterns, Terraform provides auditable, repeatable KMS key lifecycle control.