The objective of this post is to implement KMS key access security for AWS Identity and Access Management (IAM) identities by changing the default policy when provisioning the resource with Terraform.
This is a practical example, so I first recommend recommend read this post to better understand the objective of restricted key policy.
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.
The default KMS key policy contains the following statement:
{
"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:
{
"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
andTERRAFORM
has access to perform management operations on the key. - Third statement All account principals are able to read the key.
Terraform restrict KMS blueprint
1. versions.tf
Define Terraform versions and providers.
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0.0"
}
}
}
2. main.tf
Use the AWS provider in a region and create the KMS resource with your configuration parameters including your policy.
provider "aws" {
region = "us-east-1"
}
resource "aws_kms_key" "this" {
description = "Restricted kms key policy example"
policy = data.aws_iam_policy_document.restricted_key_policy.json
}
3. variables.tf
Defines the variable that will be responsible for the value of the new desired policy to be attached to the KMS policy.
variable key_policy {
description = "key policy"
type = list(object({
sid = optional(string)
effect = string
actions = list(string)
resources = list(string)
principals = optional(list(object({
type = string
identifiers = list(string)
})))
conditions = optional(list(object({
test = optional(string),
variable = string,
values = list(string)
})))
}))
default = []
}
4. policy.json
Create a rule with the new default policy based on the current account id and merge it with the custom policy passed by variable.
data "aws_caller_identity" "current" {}
locals {
aws_account_id = data.aws_caller_identity.current.account_id
# Default key policy to restrict AWS access
default_key_policy = [
{
sid = "Enable root access and prevent permission delegation"
effect = "Allow"
principals = [
{
type = "AWS"
identifiers = [local.aws_account_id]
},
]
actions = ["kms:*"]
resources = ["*"]
conditions = [
{
test = "StringEquals"
variable = "aws:PrincipalType"
values = ["Account"]
},
]
},
{
sid = "Allow access for key administrators"
effect = "Allow"
principals = [
{
type = "AWS"
identifiers = [
"arn:aws:iam::${local.aws_account_id}:role/TERRAFORM",
"arn:aws:iam::${local.aws_account_id}:role/ADMIN"
]
},
]
actions = [
"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"
],
resources = ["*"]
},
{
sid = "Enable read access to all identities"
effect = "Allow"
principals = [
{
type = "AWS"
identifiers = [local.aws_account_id]
},
]
actions = [
"kms:List*",
"kms:Describe*",
"kms:Get*",
]
resources = ["*"]
}
]
}
# Merge the default key policy with the new key policy
data "aws_iam_policy_document" "restricted_key_policy" {
dynamic "statement" {
for_each = concat(local.default_key_policy, var.key_policy)
content {
sid = statement.value.sid
effect = statement.value.effect
actions = statement.value.actions
resources = statement.value.resources
dynamic "principals" {
for_each = try(statement.value.principals, null) == null ? [] : statement.value.principals
content {
type = principals.value.type
identifiers = principals.value.identifiers
}
}
dynamic "condition" {
for_each = try(statement.value.conditions, null) == null ? [] : statement.value.conditions
content {
test = condition.value.test
variable = condition.value.variable
values = condition.value.values
}
}
}
}
}
5. outputs.tf
Defines the output of the kms arn value after its creation.
output arn {
value = aws_kms_key.this.arn
description = "ARN KMS key"
}
6. terraform.tfvars
Enter a custom policy for the purpose of creating the KMS, in this case I will create one just to allow the use of actions for a specific role.
key_policy = [
{
sid = "Allow use of the key"
effect = "Allow"
principals = [
{
type = "AWS"
identifiers = ["arn:aws:iam::123456789012:role/ANALYST"]
},
]
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
resources = ["*"]
}
]
After apply, as a result you will always have a restricted KMS with the possibility of customizing it by changing the value of the key_policy
variable, making it not only a secure blueprint but also scalable.
Top comments (1)
nice post. useful to not recreate al that code manully