A comprehensive Terraform implementation for managing AWS KMS keys with strict security controls following industry regulatory requirements (PCI DSS, NIST) and AWS best practices.
- AWS KMS Security Framework
This framework provides a robust approach to managing AWS KMS encryption keys across multiple environments while enforcing security controls through AWS Organizations policies and strict module configurations. The solution implements:
- Service Control Policies (SCPs) for organization-wide guardrails
- Resource Control Policies (RCPs) for resource-level protections
- Environment-specific key management configurations
- Automated compliance enforcement
- Secure key lifecycle management
- âś… Strict Access Controls: Environment boundary enforcement preventing cross-account misuse
- âś… MFA Enforcement: Requires multi-factor authentication for sensitive key operations
- âś… Tag Enforcement: Mandatory tagging for classification and access control
- âś… Key Rotation: Automatic key rotation enforcement
- âś… Deletion Protection: 30-day minimum deletion window with approval process
- âś… Secure CloudHSM Integration: Support for custom key stores
- âś… Audit & Monitoring: Comprehensive logging and monitoring configuration
.
├── env/ # Environment-specific configurations
│ ├── dev/ # Development environment
│ └── prod/ # Production environment
├── modules/ # Reusable Terraform modules
│ ├── kms_key/ # KMS key management module
│ └── org_policies/ # AWS Organizations policy module
├── organization/ # Organization-level configurations
│ └── ... # Organization setup files
├── policies/ # Policy definitions
│ ├── kms/ # Key policies
│ └── org/ # Organization policies
│ ├── resource_control_policy/ # RCPs
│ └── service_control_policy/ # SCPs
└── .gitignore, .tflint.hcl, etc. # Project configuration filesThis project implements controls aligned with the AWS KMS Policy document, including:
| Control | Implementation | Policy Reference |
|---|---|---|
| Environment Boundaries | SCPs preventing cross-environment key access | 3.2.2 |
| MFA for Critical Operations | SCP requiring MFA for key management | 3.2.4 |
| Key Deletion Protection | 30-day minimum deletion window enforcement | 3.4.1 |
| Tag-based Access Control | Resource tagging requirements | 3.1.1 |
| Automatic Key Rotation | Enabled by default on all CMKs | 3.3.1 |
| Administrative Isolation | Role-based access restrictions | 3.2.3 |
- Terraform ≥ 1.10
- AWS CLI configured with appropriate permissions
- AWS Organization setup with appropriate OUs
- IAM permissions to manage organizations and policies
cd organization
cp terraform.tfvars.sample terraform.tfvars
# Edit terraform.tfvars with your AWS credentials
terraform init
terraform plan
terraform apply# For development environment
cd env/dev
cp terraform.tfvars.sample terraform.tfvars
# Edit terraform.tfvars with your AWS credentials and KMS key configuration
terraform init
terraform plan
terraform applyKMS key policies are defined in the policies/kms/ directory. To create a new policy:
- Create a JSON file in the
policies/kms/directory (e.g.,custom-app-policy.json) - Reference the policy file in your environment's
terraform.tfvars
The module implementation is kept clean by defining all configuration values in terraform.tfvars:
# Module in main.tf
module "kms_keys" {
source = "../../modules/kms_key"
environment_name = var.environment_name
key_function = var.key_function
key_team = var.key_team
key_purpose = var.key_purpose
description = var.description
custom_policy = var.custom_policy
enable_key_rotation = var.enable_key_rotation
deletion_window_in_days = var.deletion_window_in_days
tags = var.tags
}Then in your terraform.tfvars:
# terraform.tfvars
environment_name = "prod"
key_function = "db"
key_team = "payments"
key_purpose = "encryption"
description = "KMS key for payment database encryption"
custom_policy = "prod-payment-policy.json"
enable_key_rotation = true
deletion_window_in_days = 30
tags = {
BU = "Finance"
BusinessOwner = "Finance Team"
TechnicalOwner = "Database Team"
ProjectManager = "Jane Smith"
Project = "Payment System"
Owner = "Payments Team"
Environment = "prod"
}module "scps" {
source = "../modules/org_policies"
policy_type = "SERVICE_CONTROL_POLICY"
policies_directory = "../policies/org/service_control_policy"
ou_map = {
"${local.root_id}" = ["mfa_critical_api", "waiting_period", "automatic_key_rotation"]
"${local.dev_id}" = ["deny_dev_key_access_except_dev_ou", "tag_enforcement"]
"${local.prod_id}" = ["deny_prod_key_access_except_prod_ou", "tag_enforcement", "kms_spec_admin"]
}
}The framework implements several critical SCPs to enforce the security policy:
Requires MFA for all sensitive KMS operations:
{
"Sid": "RequireMFAForCriticalKMSActions",
"Effect": "Deny",
"Action": [
"kms:ScheduleKeyDeletion",
"kms:DeleteImportedKeyMaterial",
"kms:DisableKey",
"kms:PutKeyPolicy",
"kms:CreateKey"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}Prevents cross-environment key access:
{
"Sid": "DenyDevKeyAccessExceptDevOU",
"Effect": "Deny",
"Action": [
"kms:*"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/environment": "dev"
},
"ForAnyValue:StringNotLike": {
"aws:PrincipalOrgPaths": "/root/dev/*"
}
}
}Enforces a minimum 30-day waiting period for key deletion:
{
"Sid": "EnforceKMSKeyWaitingPeriod",
"Effect": "Deny",
"Action": [
"kms:ScheduleKeyDeletion"
],
"Resource": "*",
"Condition": {
"NumericLessThan": {
"kms:ScheduleKeyDeletionPendingWindowInDays": "30"
}
}
}Example key policy for a production RDS database encryption key that follows best practices:
{
"Version": "2012-10-17",
"Id": "prod-rds-encryption-key-policy",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow RDS Service to Use the Key",
"Effect": "Allow",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:CreateGrant",
"kms:ListGrants",
"kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "rds.us-east-1.amazonaws.com",
"aws:SourceAccount": "123456789012"
}
}
},
{
"Sid": "Allow DB Admin Role to Use the Key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/prod-db-admin-role"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Allow Key Administrators",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/prod-kms-admin-role"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:TagResource",
"kms:UntagResource"
],
"Resource": "*"
},
{
"Sid": "Restrict Sensitive Operations to MFA",
"Effect": "Deny",
"Principal": "*",
"Action": [
"kms:ScheduleKeyDeletion",
"kms:PutKeyPolicy",
"kms:DisableKey"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
},
{
"Sid": "DenyKeyUsageFromNonProdOUs",
"Effect": "Deny",
"Principal": "*",
"Action": "kms:*",
"Resource": "*",
"Condition": {
"StringNotLike": {
"aws:PrincipalOrgPath": "/root/prod/*"
},
"Bool": {
"aws:PrincipalIsAWSService": "false"
}
}
}
]
}For regulatory requirements that demand FIPS 140-2 Level 3 compliance or hardware-based key material storage, the framework supports AWS CloudHSM integration:
# First, create a CloudHSM cluster (if not already existing)
resource "aws_cloudhsm_v2_cluster" "hsm_cluster" {
hsm_type = "hsm1.medium"
subnet_ids = [aws_subnet.hsm_subnet_az1.id, aws_subnet.hsm_subnet_az2.id]
tags = {
Name = "prod-cloudhsm-cluster"
}
}
# Create HSM instances within the cluster (minimum 2 for HA)
resource "aws_cloudhsm_v2_hsm" "hsm_az1" {
cluster_id = aws_cloudhsm_v2_cluster.hsm_cluster.cluster_id
subnet_id = aws_subnet.hsm_subnet_az1.id
}
resource "aws_cloudhsm_v2_hsm" "hsm_az2" {
cluster_id = aws_cloudhsm_v2_cluster.hsm_cluster.cluster_id
subnet_id = aws_subnet.hsm_subnet_az2.id
}
# Configure a custom key store backed by CloudHSM
resource "aws_kms_custom_key_store" "hsm_store" {
cloud_hsm_cluster_id = aws_cloudhsm_v2_cluster.hsm_cluster.cluster_id
custom_key_store_name = "prod-hsm-store"
key_store_password = var.hsm_password # Store securely in AWS Secrets Manager
trust_anchor_certificate = file("${path.module}/certs/customerCA.crt")
}
# Use the custom key store ID in the KMS module
module "kms_keys" {
source = "../../modules/kms_key"
environment_name = var.environment_name
# Other variables from terraform.tfvars
# The module references this in locals.tf
custom_key_store_id = aws_kms_custom_key_store.hsm_store.id
}Important Notes on CloudHSM:
- CloudHSM requires careful network planning with private subnets across multiple AZs
- Initial setup requires manual configuration of HSM users and trust anchor
- When using CloudHSM with KMS, automatic key rotation isn't supported and must be implemented manually
- CloudHSM incurs significant additional costs (per HSM instance per hour)
For more details, see AWS CloudHSM documentation and KMS Custom Key Store documentation.
The framework supports AWS Config managed rules for continuous KMS compliance monitoring. These rules automatically check for non-compliant configurations and can be deployed through AWS Config.
| Rule Name | Purpose | Documentation |
|---|---|---|
kms-cmk-not-scheduled-for-deletion |
Checks if KMS CMKs are scheduled for deletion | AWS Docs |
kms-key-rotation-enabled |
Checks if automatic key rotation is enabled for each KMS CMK | AWS Docs |
cloud-trail-encryption-enabled |
Verifies CloudTrail logs are encrypted with KMS | AWS Docs |
s3-default-encryption-kms |
Checks if S3 buckets are encrypted with KMS | AWS Docs |
Example deployment using Terraform:
resource "aws_config_config_rule" "kms_rotation_rule" {
name = "kms-key-rotation-enabled"
description = "Checks whether automatic key rotation is enabled for each KMS key"
source {
owner = "AWS"
source_identifier = "KMS_CMK_ROTATION_ENABLED"
}
depends_on = [aws_config_configuration_recorder.config_recorder]
}For detailed guidance on implementing AWS Config with Terraform, see the Terraform AWS Config documentation.
Implementation includes CloudTrail logging for key events that should be monitored:
| Event | Severity | Description |
|---|---|---|
kms:CreateKey |
Medium | New key created |
kms:DisableKey |
High | Key disabled, which may impact services |
kms:ScheduleKeyDeletion |
Critical | Key scheduled for deletion |
kms:PutKeyPolicy |
High | Key policy modified |
kms:EnableKey |
Medium | Disabled key re-enabled |
kms:Decrypt, kms:Encrypt |
Low (High Volume) | Normal key usage operations |
Example CloudWatch Event Rule for detecting key deletion events:
resource "aws_cloudwatch_event_rule" "key_deletion_alert" {
name = "kms-key-deletion-alert"
description = "Alert on KMS key deletion scheduling"
event_pattern = jsonencode({
source = ["aws.kms"],
detail-type = ["AWS API Call via CloudTrail"],
detail = {
eventSource = ["kms.amazonaws.com"],
eventName = ["ScheduleKeyDeletion"]
}
})
}
resource "aws_cloudwatch_event_target" "sns" {
rule = aws_cloudwatch_event_rule.key_deletion_alert.name
target_id = "SendToSNS"
arn = aws_sns_topic.security_alerts.arn
}For implementing comprehensive AWS CloudTrail analysis, consider setting up Amazon Detective or Amazon Security Lake which provide advanced security analytics for KMS and other services.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request