AWS Key Management Service (KMS) is a AWS managed service that allows us to create, manage, and delete customer master keys (CMK) or simply use AWS customer managed keys for encrypting our data in the AWS cloud. From my experience with passing both the AWS Certified Security – Speciality and the AWS Certified Solutions Architect – Professional, AWS Key Management Service is a must to learn inside and out. If you can understand all of the KMS features you’ll have a better chance of passing those two exams. Let’s learn how to create and manage AWS KMS customer managed CMK with Terraform! I will also be using Terragrunt so we can follow the DRY (Don’t repeat yourself) model.
This is part two of AWS Key management service (KMS) – Part 1.

Key properties
- Key Id
- Creation date
- Description
- Key state
Customer managed keys (CMK)
Given the name you can guess the differences right away, right? For starter, you as the customer will have to explicitly create the key with AWS CLI, AWS API, or Terraform or any other available methods. You can set the CMK policies to allow services or users to use the key. The key policy can pass the permission responsibilities to be managed by IAM policies instead of the KMS CMK key policies. A CMK can be set to enable or disable at any time to allow usage or stop the usage of the key.
Key Alias are a great way to tag and identify Customer managed CMKs. This will help the user quickly find the desired key in the AWS console or AWS CLI query results. Since rotation is possible and it can be enabled to automatically rotate on yearly basis. Key aliases helps with that by re-assigning the alias to the new key.
You can definitely delete the key, but you must be damn sure that no one or any data or services is using that key! Once the key is gone you cannot unencrypt the data that was encrypted with the deleted key! So in order to semi control this chaos AWS has enforced a scheduling delete functionality rather than immediate delete. The customer can delete any Customer managed key by scheduling a delete; the minimum number of days to schedule a delete is 7 days. Best practice is to set this for a month or more.
NOTE: Before proceeding
- If you haven’t installed Terraform or Terragrunt then you must follow this guide
- For Terraform deep dive explanation follow this guide
Pricing
Each customer master key (CMK) that you create in AWS Key Management Service (KMS) costs $1/month until you delete it. For the N. VA region:
- $0.03 per 10,000 requests
- $0.03 per 10,000 requests involving RSA 2048 keys
- $0.10 per 10,000 ECC GenerateDataKeyPair requests
- $0.15 per 10,000 asymmetric requests except RSA 2048
- $12.00 per 10,000 RSA GenerateDataKeyPair requests
Learn more at https://aws.amazon.com/kms/pricing/
Create and edit KMS Keys
Terraform module: main.tf
# Creates/manages KMS CMK
resource "aws_kms_key" "this" {
description = var.description
customer_master_key_spec = var.key_spec
is_enabled = var.enabled
enable_key_rotation = var.rotation_enabled
tags = var.tags
policy = var.policy
deletion_window_in_days = 30
}
# Add an alias to the key
resource "aws_kms_alias" "this" {
name = "alias/${var.alias}"
target_key_id = aws_kms_key.this.key_id
}
Terraform module: vars.tf
variable description {}
# Options available
# SYMMETRIC_DEFAULT, RSA_2048, RSA_3072,
# RSA_4096, ECC_NIST_P256, ECC_NIST_P384,
# ECC_NIST_P521, or ECC_SECG_P256K1
variable key_spec {
default = "SYMMETRIC_DEFAULT"
}
variable enabled {
default = true
}
variable rotation_enabled {
default = true
}
variable tags {}
variable alias {}
variable policy {}
Terragrunt KMS directory structure

Terraform plan
main.tf
locals {
admin_username = "waleed"
account_id = data.aws_caller_identity.current.account_id
}
provider "aws" {
region = var.aws_region
profile = var.aws_cli_profile
}
terraform {
backend "s3" {}
}
data "aws_caller_identity" "current" {}
data "aws_iam_policy_document" "ssm_key" {
statement {
sid = "Enable IAM User Permissions"
effect = "Allow"
actions = ["kms:*"]
resources = ["*"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${local.account_id}:root"]
}
}
statement {
sid = "Allow access for Key Administrators"
effect = "Allow"
actions = ["kms:*"]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${local.account_id}:user/${local.admin_username}",
"arn:aws:iam::${local.account_id}:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"arn:aws:iam::${local.account_id}:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor"
]
}
}
statement {
sid = "Allow use of the key"
effect = "Allow"
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${local.account_id}:user/${local.admin_username}",
"arn:aws:iam::${local.account_id}:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"arn:aws:iam::${local.account_id}:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor"
]
}
}
statement {
sid = "Allow attachment of persistent resources"
effect = "Allow"
actions = [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${local.account_id}:user/${local.admin_username}",
"arn:aws:iam::${local.account_id}:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"arn:aws:iam::${local.account_id}:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor"
]
}
condition {
test = "Bool"
variable = "kms:GrantIsForAWSResource"
values = ["true"]
}
}
}
ssm-key.tf : It’s best practice to have each type of key in its own terraform file. In this example this key is for SSM. You can create keys for any services possible in your region!
Notice the source is only pulling the ‘dev’ branch. Once tested and verified the source branch would change in each environment.
module "ssm" {
source = "git@github.com:S-Waleed/terraform-aws-kms-module.git"
description = "KMS key for System Manager"
alias = "ssm"
policy = data.aws_iam_policy_document.ssm_key.json
tags = {
Name = "ssm"
Owner = "wsarwari"
}
}
# module.ssm.aws_kms_alias.this will be created
+ resource "aws_kms_alias" "this" {
+ arn = (known after apply)
+ id = (known after apply)
+ name = "alias/ssm"
+ target_key_arn = (known after apply)
+ target_key_id = (known after apply)
}
# module.ssm.aws_kms_key.this will be created
+ resource "aws_kms_key" "this" {
+ arn = (known after apply)
+ customer_master_key_spec = "SYMMETRIC_DEFAULT"
+ deletion_window_in_days = 30
+ description = "KMS key for System Manager"
+ enable_key_rotation = true
+ id = (known after apply)
+ is_enabled = true
+ key_id = (known after apply)
+ key_usage = "ENCRYPT_DECRYPT"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "kms:*"
+ Effect = "Allow"
+ Principal = {
+ AWS = "arn:aws:iam::111122223334:root"
}
+ Resource = "*"
+ Sid = "Enable IAM User Permissions"
},
+ {
+ Action = "kms:*"
+ Effect = "Allow"
+ Principal = {
+ AWS = [
+ "arn:aws:iam::111122223334:user/waleed",
+ "arn:aws:iam::111122223334:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor",
+ "arn:aws:iam::111122223334:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
]
}
+ Resource = "*"
+ Sid = "Allow access for Key Administrators"
},
+ {
+ Action = [
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:Encrypt",
+ "kms:DescribeKey",
+ "kms:Decrypt",
]
+ Effect = "Allow"
+ Principal = {
+ AWS = [
+ "arn:aws:iam::111122223334:user/waleed",
+ "arn:aws:iam::111122223334:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor",
+ "arn:aws:iam::111122223334:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
]
}
+ Resource = "*"
+ Sid = "Allow use of the key"
},
+ {
+ Action = [
+ "kms:RevokeGrant",
+ "kms:ListGrants",
+ "kms:CreateGrant",
]
+ Condition = {
+ Bool = {
+ kms:GrantIsForAWSResource = "true"
}
}
+ Effect = "Allow"
+ Principal = {
+ AWS = [
+ "arn:aws:iam::111122223334:user/waleed",
+ "arn:aws:iam::111122223334:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor",
+ "arn:aws:iam::111122223334:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
]
}
+ Resource = "*"
+ Sid = "Allow attachment of persistent resources"
},
]
+ Version = "2012-10-17"
}
)
+ tags = {
+ "Name" = "ssm"
+ "Owner" = "wsarwari"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
After applying the code you should see the key in Key Management Service.

Enable and disable CMKs
Simple, just update the “enabled” variable to false! Plan and apply.
module "ssm" {
source = "git@github.com:S-Waleed/terraform-aws-kms-module.git"
description = "KMS key for System Manager"
alias = "ssm"
...
enabled = false
....
}
Automatic rotation
Another easy change with Terraform! By default I have set it to rotate the key in the custom module I created above. Now if you want to disable automatic rotation then all you have to do is set the variable rotation_enabled to false.
module "ssm" {
source = "git@github.com:S-Waleed/terraform-aws-kms-module.git"
description = "KMS key for System Manager"
alias = "ssm"
...
rotation_enabled = false
....
}
Key Alias
Keys are identified by randomly generated GUIDs. It’s best to create in alias for each key so it can be easily identified by everyone. Aliases are also easy to create and update in Terraform. In the custom module above I have added the ability to create the key alias right after the key is provisioned. The key alias is also passed as a variable as shown below.
module "ssm" {
source = "git@github.com:S-Waleed/terraform-aws-kms-module.git"
description = "KMS key for System Manager"
alias = "ssm"
...
}
Type of CMK | Can view CMK metadata | Can manage CMK | Used only for my AWS account | Automatic rotation |
---|---|---|---|---|
Customer managed CMK | Yes | Yes | Yes | Optional. Every 365 days (1 year). |
AWS managed CMK | Yes | No | Yes | Required. Every 1095 days (3 years). |
AWS owned CMK | No | No | No | Varies |
Key Policy
Each key must have a policy that allows or denies the key to be used by users or services. In the design that I have created allows you to give each key a policy. Let’s breakdown each statement of the key.
Enable IAM User Permissions
statement {
sid = "Enable IAM User Permissions"
effect = "Allow"
actions = ["kms:*"]
resources = ["*"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${local.account_id}:root"]
}
}
This statement allows all users and services in this account to execute all KMS actions on this key. It’s best practice to follow up on this open statement by creating an IAM policy to restrict the key usage. The identifiers in the principles section can be other AWS accounts.
Allow access for Key Administrators
statement {
sid = "Allow access for Key Administrators"
effect = "Allow"
actions = ["kms:*"]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${local.account_id}:user/${local.admin_username}",
"arn:aws:iam::${local.account_id}:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"arn:aws:iam::${local.account_id}:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor"
]
}
}
This statement allows selected individual users and IAM roles to fully manage this key. This statement must exist for every single key. Without this statement you will absolutely lose access and management of this key.
Allow use of the key
statement {
sid = "Allow use of the key"
effect = "Allow"
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${local.account_id}:user/${local.admin_username}",
"arn:aws:iam::${local.account_id}:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"arn:aws:iam::${local.account_id}:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor"
]
}
}
This statement is specifically for the usage of the key. If you do not provide the statement Enable IAM User Permissions then you must include this statement; Otherwise this key will not be usable by anyone besides the key administrators.
Allow attachment of persistent resources
statement {
sid = "Allow attachment of persistent resources"
effect = "Allow"
actions = [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${local.account_id}:user/${local.admin_username}",
"arn:aws:iam::${local.account_id}:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"arn:aws:iam::${local.account_id}:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor"
]
}
condition {
test = "Bool"
variable = "kms:GrantIsForAWSResource"
values = ["true"]
}
}
This statement allows listing, creating, and revoking grants for the key by the principals identified in the statement.
Deleting the key
Do not delete production keys! You must be 100% sure no service or data has ever used the key you are able to delete. Once a key is deleted it’s not possible to restore! You cannot decrypt data that was encrypted with the key that you delete. In Terraform you can delete the key by deleting the code or commenting out the code, then Terraform plan and apply. Or if you want to keep the code and just want to remove the resource from AWS then execute Terragrunt destroy. This destroy will also release the alias so it can be reused.

Status is now pending deletion. It will delete this key 30 days from the day of the destroy.
Stop KMS key deletion
If you decide to not delete it then on the AWS console you can select the key then click on Key actions. Finally select Cancel key deletion. This option is only available before the deletion date.

Read more about deleting KMS customer managed keys.
KMS Multi-Region Keys
Take a look at Terraform AWS KMS Multi-Region Keys.
As always if you see any errors, mistakes, have suggestions or questions please comment below. Don’t forget to like, share, and subscribe below for more!
AWS managed CMKs
I talked about AWS managed customer master keys in my previous post here.
Complete Code
Get the complete module from https://github.com/masterwali/terraform-aws-kms-module.
[…] Wrapping up KMS for now; but in the future I’ll cover AWS KMS monitoring in detail, how and what can KMS integrate with other services, cross account KMS permissions, customer managed CMKs, and much more deep dives coming soon so be sure to subscribe! Here’s part two: https://cloudly.engineer/2020/aws-kms-customer-managed-cmk-with-terraform/aws/ […]
[…] use an Amazon managed encryption key. For production environments please take the time to create a KMS key and use that for your […]