IaC Bazaar
AWSLive-tested

KMS Key with Policy Patterns

Customer-managed KMS keys with sane key policies, aliases, rotation, and multi-region replicas.

terraformAWS#aws

Compare Secrets & Key Management across clouds →

aws-kmsterraform v1.7

Verification

Live-tested

Really deployed, verified, idempotent and destroyed in a cloud sandbox.

Conformance

  • Static validation (fmt · validate · tflint)
  • Security scan clean (Checkov)
  • Plan tests (mocked: validation rules · outputs)

Provenance

  • SHA-256 checksum
  • Signature (pending)

Functional

  • Live-tested — applied, verified, destroyed

Last verified 2026-06-11 · how we verify

Documentation

aws-kms

Customer-managed KMS key with a least-privilege, lockout-safe key policy, automatic rotation on by default, aliases, and optional multi-region replicas. Works with Terraform and OpenTofu (>= 1.6), AWS provider >= 6.0, < 7.0. The generated key policy follows the AWS reference pattern — account root keeps kms:* so the key is never orphaned, while administrators, users, and AWS service principals get only the actions they need.

Status: static-validated, live-test pending. Ships under live-test quarantine — validated with tofu fmt, tofu validate, and tflint. Real apply → verify → destroy against an AWS account is pending a cloud sandbox. KMS destroy is non-immediate by design: terraform destroy only schedules deletion (deletion_window_in_days, 7–30 days), and the key remains billable (~$1/mo) and recoverable until the window elapses. This is an expected exception to the marketplace's instant-destroyConfirmed semantics, not a teardown failure.

What you get:

  • aws_kms_key — symmetric or asymmetric/HMAC, with rotation, a deletion window, and an optional multi-region primary.
  • aws_kms_key_policy — a generated least-privilege document (override with a full policy JSON string if you need to).
  • aws_kms_alias — one or more alias/<name> aliases pointed at the key.
  • aws_kms_replica_key — cross-region replicas of a multi-region primary.

Secure defaults

  • Rotation on (enable_key_rotation = true) for symmetric encryption keys; automatically nulled for asymmetric/HMAC specs that do not support it.
  • Generated least-privilege policy: root keeps kms:* (the non-lockout safeguard), administration and cryptographic use are split into separate statements, and service principals are pinned to your account via kms:CallerAccount. Key-user grant creation is restricted to AWS resources (kms:GrantIsForAWSResource).
  • 30-day deletion window by default — the maximum recovery runway.
  • bypass_policy_lockout_safety_check defaults to false; the safety check stays on.
  • Reserved aws/* alias names and AWS-managed keys are rejected by validation.

Usage

module "kms" {
  source = "./aws-kms"

  description = "Application data encryption key"
  aliases     = ["my-app"]

  key_administrator_arns = [aws_iam_role.kms_admin.arn]
  key_user_arns          = [aws_iam_role.app.arn]
  key_service_users      = ["logs.us-east-1.amazonaws.com"]

  tags = { Environment = "prod" }
}
Multi-region replicas

A replica lives in a different region than the primary. Set multi_region = true so the primary is replicable, then create the replica from a root module that has an aws provider in the target region — either by passing an aliased provider to this module instance, or (cleaner) by instantiating the module a second time with replica_primary_key_arn set to the primary's ARN under a region-specific provider:

provider "aws" { region = "us-east-1" }
provider "aws" {
  alias  = "eu"
  region = "eu-west-1"
}

module "kms_primary" {
  source       = "./aws-kms"
  aliases      = ["my-app"]
  multi_region = true
}

# Replica via a second instance pinned to the EU provider.
module "kms_replica_eu" {
  source                  = "./aws-kms"
  providers               = { aws = aws.eu }
  aliases                 = ["my-app"]
  replica_primary_key_arn = module.kms_primary.key_arn
  replica_regions         = { "eu-west-1" = {} }
}

(The inline replica_regions map is also supported on a single instance for same-account, default-provider plans; cross-region applies require a provider in the replica's region as shown above.)

Inputs

NameTypeDefaultDescription
descriptionstring(set)Key description
aliaseslist(string)[]Alias names (alias/ prefix added automatically)
key_usagestring"ENCRYPT_DECRYPT"ENCRYPT_DECRYPT, SIGN_VERIFY, GENERATE_VERIFY_MAC, KEY_AGREEMENT
customer_master_key_specstring"SYMMETRIC_DEFAULT"Key spec (RSA/ECC/HMAC for asymmetric/MAC keys)
enable_key_rotationbooltrueYearly rotation (symmetric ENCRYPT_DECRYPT only)
rotation_period_in_daysnumbernullCustom rotation interval (90–2560); null = 365
deletion_window_in_daysnumber30Scheduled-deletion waiting period (7–30)
is_enabledbooltrueKey enabled for crypto operations
multi_regionboolfalseCreate a multi-region primary (required for replicas)
policystringnullFull JSON policy override (replaces the generated one)
key_administrator_arnslist(string)[]Principals allowed to administer the key
key_user_arnslist(string)[]Principals allowed cryptographic use
key_service_userslist(string)[]AWS service principals allowed use (account-pinned)
enable_default_root_policybooltrueGrant account root kms:* (non-lockout safeguard)
bypass_policy_lockout_safety_checkboolfalseSkip the lockout safety check
replica_regionsmap(object){}Cross-region replicas {description, deletion_window_in_days, enabled, policy}
replica_primary_key_arnstringnullReplicate from an existing primary instead of creating one
tagsmap(string){}Tags for key and replicas

Outputs

key_id, key_arn, primary_alias, alias_arns, rotation_enabled, replica_key_arns.

Provider pin

aws = {
  source  = "hashicorp/aws"
  version = ">= 6.0, < 7.0"
}

License

Commercial — LicenseRef-IaCBazaar-Commercial. © IaC Bazaar. Original work (not derived from a third-party module).