How to restrict default access to KMS via key policy with Terraform

Rmag Breaking News

The objective of this article 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 article 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 and TERRAFORM 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 = “Informações da 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”,
“arn:aws:iam::${local.aws_account_id}:user/matheus”
]
},
]
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.

Check out the full code on GitHub!.

Leave a Reply

Your email address will not be published. Required fields are marked *