AWS Key Management Service is a managed service that makes it easy to create and control the cryptographic keys used to protect data. AWS KMS is integrated with many AWS Services and integrates with AWS CloudTrail to log use of your KMS keys for auditing, regulatory, and compliance needs. AWS KMS uses an Envelope encryption strategy to protect the keys that encrypt data. Envelope encryption is the practice of encrypting plaintext data with a data key, and then encrypting the data key with a second key, known as the root key. AWS KMS protects the encryption keys by storing and managing them securely. Root keys never leave the AWS KMS unencrypted. Key resource policy along with IAM policies controls the access to the AWS KMS APIs.
AWS KMS keys can be AWS owned, AWS managed or customer managed. Some AWS Services encrypt the data, by default, with an AWS owned key or an AWS managed key. Some AWS Services support customer managed keys.
Existing Key Policy Update Patterns
Terraform does not directly support updating an existing KMS key policy through its resources with a simple in-place edit. There are a few approaches you can consider to achieve this.
The first approach uses data sources to fetch the existing policy, then a local variable to modify it.
First, use the awskmskey data source to get information about the existing key:
hcl
data "aws_kms_key" "existing_key" {
key_id = "your-key-id-or-arn"
}
Then, use the awskmskey_policy data source to fetch the current policy:
hcl
data "aws_kms_key_policy" "existing_policy" {
key_id = data.aws_kms_key.existing_key.id
}
Next, create a local variable to modify the policy:
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 = "*"
}
]
)
})
}
Finally, use the awskmskey_policy resource to apply the updated policy:
hcl
resource "aws_kms_key_policy" "updated_policy" {
key_id = data.aws_kms_key.existing_key.id
policy = local.updated_policy
}
If you need more control or if the above method does not work for your use case, you can use the awskmskey resource with the policy argument, but you will 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
]
})
}
Restricting Default Access via Key Policy
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::123456789012: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
This example demonstrates the AWS account ID 123456789012 with existing role named TERRAFORM, ADMIN and ANALYST. These values must be replaced for your environment.
Terraform Resources and Data Sources for KMS
Terraform provides dedicated resources and data sources for KMS operations.
9 Terraform resources and 7 data sources available.
| Resource | Purpose |
|---|---|
| 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 |
Data sources commonly used with KMS include awskmskey and awskmskey_policy as shown in the existing key update pattern.
Module Based Provisioning
This solution is a set of Terraform modules that provision symmetric customer managed AWS KMS keys for use by the target AWS Services. You can optionally manage the key resource policy for the cross-account access via the AWS Services and the account principals. An additional module is included that supports creating multi-region replica keys in another region. The full set of features is listed here. The solution also provides three example scenarios of how the solution solves common enterprise use cases.
A community Terraform module which creates AWS KMS resources is available. 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"
# Policy
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
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"
# Policy
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
aliases = ["mycompany/external"]
aliases_use_name_prefix = true
# Grants
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 =
}
}
Outputs export key information for other Terraform configurations:
hcl
output "kms_key_id" {
description = "The KMS key ID"
value = aws_kms_key.main.key_id
}
output "kms_key_arn" {
description = "The KMS key ARN"
value = aws_kms_key.main.arn
}
output "kms_alias_arn" {
description = "The KMS alias ARN"
value = aws_kms_alias.main.arn
}
Operational Safety and Common Mistakes
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.
Common mistakes include:
- Locking yourself out. If you remove the root account access from the key policy and no remaining principal can update the policy, you cannot 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.
Conclusion
Managing AWS KMS keys with Terraform requires careful separation between key creation, key policy definition, and ongoing access control. Importing existing keys and using data sources to read current policies allows safe incremental updates without destroying the key material. Restricting default account-wide access by replacing the broad Enable IAM User Permissions statement with scoped statements that require aws:PrincipalType Account condition, explicit administrator roles, and read-only access for identities reduces blast radius while preserving operational recovery.
Module based provisioning provides reusable patterns for symmetric customer managed keys, cross-account access, multi-region replicas, and grant management. Outputs for key ID, ARN, and alias ARN enable composition across configurations. Operational discipline around root access retention, grant permissions for services like EBS and RDS, and deletion windows of 30 days for production keys prevents irreversible loss of access. With these practices, Terraform becomes an auditable source of truth for KMS lifecycle and policy governance.