two spoons

Terraform AWS KMS Multi-Region Keys

Terraform just (November 2021) released the resource to create replica KMS keys! As the name says, a Multi-Region Key is a single key that’s available in two different AWS regions. There are few use cases, such as reducing cost of keys. Even a better case is the ability to share encrypted objects like AMI’s with other regions or accounts. Before I start showing the Terraform AWS KMS Multi-Region Keys Module, you have to know what AWS KMS is. Checkout my previous posts, AWS Key management service (KMS) – Part 1 and AWS KMS Customer Managed CMK with Terraform.

Terraform AWS KMS Multi-Region Keys Module code

We’ll need another “aws” provider. The second provider will be for your replicated key. This region will be different than the first provider.

provider "aws" {
  alias  = "replica"
  region = var.replica_region
}

The primary key will still use the original “aws_kms_key” Terraform resource. I just added additional tags. Don’t forget the key alias!

resource "aws_kms_key" "primary" {
  multi_region             = true
  description              = var.description
  customer_master_key_spec = var.key_spec
  is_enabled               = var.is_enabled
  enable_key_rotation      = var.rotation_enabled
  policy                   = var.primary_key_policy
  deletion_window_in_days  = var.deletion_window_in_days

  tags = merge(
    var.tags,
    {
      "Multi-Region" = "true",
      "Primary"      = "true"
    }
  )
}

# Add an alias to the primary key
resource "aws_kms_alias" "primary" {
  name          = "alias/${var.alias}"
  target_key_id = aws_kms_key.primary.key_id
}

Here comes the boom! The “aws_kms_replica_key” terraform resource is required to replicate the key that was just created with the above resource. That’s done with the “primary_key_arn” parameter. The key ARN of the replica key is the key ARN of the primary key.

Notice the “provider” is required in order to ensure this is created in another region. Now, you can reverse this design. You can have the provider on your primary key instead but this is my preference.

You can have a different or the same key policy. The alias, tags, description, deleteion_window_in_days can be the same or different, it doesn’t matter. It is “enabled” and there’s no option to rotate a replica key because the rotation is managed by the primary key.

# Create the replica key using the primary's arn.
resource "aws_kms_replica_key" "replica" {
  provider = aws.replica

  description             = var.description
  deletion_window_in_days = var.deletion_window_in_days
  primary_key_arn         = aws_kms_key.primary.arn
  policy                  = var.replica_key_policy

  tags = merge(
    var.tags,
    {
      "Multi-Region" = "true",
      "Primary"      = "false"
    }
  )
}

# Add an alias to the replica key
resource "aws_kms_alias" "replica" {
  provider = aws.replica

  name          = "alias/${var.alias}"
  target_key_id = aws_kms_replica_key.replica.key_id
}

Module usage

Here’s an example on how to use this module.

data "aws_iam_policy_document" "ebs_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: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 = ["*"]

    principals {
      type = "AWS"
      identifiers = [
        "arn:aws:iam::${local.account_id}:user/${local.admin_username}",
        "arn:aws:iam::${local.account_id}:role/${local.role_name}"
      ]
    }
  }

  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/${local.role_name}"
      ]
    }
  }

  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/${local.role_name}"
      ]
    }

    condition {
      test     = "Bool"
      variable = "kms:GrantIsForAWSResource"
      values   = ["true"]
    }
  }
}

module "ebs_key" {
  source = "git@github.com:masterwali/terraform-kms-multi-region-module.git"

  description        = "KMS key for EBS volumes."
  alias              = "multi-region-ebs"
  primary_key_policy = data.aws_iam_policy_document.ebs_key.json
  replica_key_policy = data.aws_iam_policy_document.ebs_key.json
  replica_region     = "us-west-2"

  tags = {
    Name  = "multi-region-ebs"
    Owner = "Waleed"
  }
}

Here’s my applied code. I set the EBS default encryption to use the multi-region-ebs key that I created using the module. Notice Multi-Region key ID’s start with “mrk” for Multi-Region Key.

Terraform multi-region kMS key example
A volume resource using a multi-region KMS key.

Regions supported

Multi-Region keys are supported in all AWS Regions where AWS KMS is available.

Cost

Every pair, primary and replica, is priced as a single key! But the KMS quotas are still counted separately.

Complete Code

Here’s what you came for, https://github.com/masterwali/terraform-kms-multi-region-module. Learn more about AWS KMS Multi-Region Keys.

Don’t forget to subscribe for more 🙂

Post NotificationsGet the latest post directly in your inbox!