Redshift clusters should have KMS encryption enabled
Default Redshift encryption uses an AWS-managed key that you cannot rotate, audit, or revoke independently. A customer-managed KMS key gives you direct control over the key lifecycle: you can set automatic rotation, define granular key policies restricting which IAM principals can decrypt data, and disable or delete the key to render cluster data unreadable in an emergency.
Without a specified KMS key, you lose the ability to enforce separation of duties between the team that operates Redshift and the team that controls encryption keys.
Retrofit consideration
The encrypted argument on aws_redshift_cluster is ForceNew in the Terraform AWS provider. Enabling encryption or changing the KMS key on an existing unencrypted cluster forces resource replacement, requiring a snapshot, new cluster creation, and endpoint migration with associated downtime.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "redshift" {
source = "pcidss.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "hipaa.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "nist80053.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "nydfs23.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "ffiec.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "fedramplow.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
module "redshift" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
If you use terraform-aws-modules/redshift/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "redshift" {
source = "terraform-aws-modules/redshift/aws"
version = ">=7.0.0,<8.0.0"
automated_snapshot_retention_period = 7
cluster_identifier = "abc123"
create_cloudwatch_log_group = true
database_name = "mydb"
logging = {
log_destination_type = "cloudwatch"
log_exports = ["connectionlog", "userlog", "useractivitylog"]
}
master_password_wo = "change-me-in-production"
master_username = "admin"
node_type = "ra3.xlplus"
number_of_nodes = 2
subnet_ids = ["subnet-12345678", "subnet-12345678", "subnet-12345678"]
vpc_id = "vpc-12345678"
vpc_security_group_ids = ["sg-12345678"]
}
Use AWS provider resources directly. See docs for the resources involved: aws_redshift_cluster.
resource "aws_redshift_cluster" "this" {
automated_snapshot_retention_period = 7
cluster_identifier = "pofix-abc123"
cluster_subnet_group_name = "example-redshift-subnet-group"
master_password = "ChangeMe123!"
master_username = "admin"
node_type = "ra3.large"
skip_final_snapshot = true
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
What this control checks
On the aws_redshift_cluster resource, encrypted must be true and kms_key_id must hold the ARN of the designated customer-managed KMS key. A cluster with encrypted = false, or encrypted = true but referencing a different key ARN (including the default aws/redshift key when kms_key_id is omitted), fails this control. Define the KMS key as an aws_kms_key resource or reference it via an aws_kms_alias data source. Its key policy must grant the Redshift service principal kms:Encrypt, kms:Decrypt, kms:GenerateDataKey*, kms:DescribeKey, and kms:CreateGrant.
Common pitfalls
ForceNew on encrypted argument causes cluster replacement
Snapshot first, then rebuild. Changing
encryptedfromfalsetotruetriggers resource destruction becauseencryptedis ForceNew in the Terraform AWS provider. Setfinal_snapshot_identifierbefore the change, restore from that snapshot with encryption enabled, and plan for the cluster endpoint to change.Omitting kms_key_id still fails despite encryption being on
Setting
encrypted = truewithoutkms_key_idtells Redshift to use the defaultaws/redshiftAWS-managed key. That's not a customer-managed key, so the cluster fails this control even though encryption is technically on.KMS key policy missing Redshift service permissions
Cluster creation fails with
KMSKeyNotAccessibleFaultif the key policy doesn't give Redshift what it needs. The key must grantkms:Encrypt,kms:Decrypt,kms:ReEncrypt*,kms:GenerateDataKey*,kms:DescribeKey, andkms:CreateGrantto the Redshift service principal or the IAM role used by the cluster.Cross-region or cross-account key ARN mismatch
Multi-region keys and cross-account setups both produce ARN mismatches that fail this control. A replica key in a different region has a different ARN than the primary, so the ARN in the compliance rule parameter must exactly match what the cluster is using, even if encryption is active.
Audit evidence
Config rule evaluation results showing each cluster as COMPLIANT, with kmsKeyArn matching the organization-approved key, are the primary artifact. Pair that with aws redshift describe-clusters output showing "Encrypted": true and "KmsKeyId" set to the correct ARN for every cluster in scope. CloudTrail logs for redshift:CreateCluster and redshift:ModifyCluster confirm encryption settings were applied at provisioning, not patched in after the fact.
A KMS key policy showing restricted principal access rounds out the package. If the organization requires key rotation, include the aws_kms_key config with enable_key_rotation = true and the KMS rotation status from the console or API.
Framework-specific interpretation
PCI DSS v4.0: For Redshift clusters storing account data, Requirement 3.5 calls for strong cryptography and full key lifecycle management. A customer-managed KMS key with defined rotation and access policies covers the objectives in 3.5.1 and 3.6, but meeting the full requirement also takes documented procedures and governance controls.
HIPAA Omnibus Rule 2013: The HIPAA Security Rule treats encryption of ePHI at rest as an addressable implementation specification under 164.312(a)(2)(iv). Customer-managed keys handle that requirement, and the KMS key policy provides the access controls needed to support audit trails and breach risk assessment.
NIST SP 800-53 Rev 5: SC-12, SC-13, and SC-28 all apply here. Customer-managed keys cover the lifecycle management required by SC-12, the cryptographic protection specified in SC-13, and the at-rest data protection in SC-28, with rotation and revocation capabilities built in.
FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, SC-12 and SC-28 both apply. Customer-managed KMS keys satisfy SC-12's key establishment and management requirements and SC-28's at-rest protection requirement, with the key policy and rotation schedule covering the key independence controls FedRAMP expects.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
redshift_cluster_kms_enabledAWS Config Managed Rule:
REDSHIFT_CLUSTER_KMS_ENABLEDCheckov Check:
CKV_AWS_142Powerpipe Controls:
aws_compliance.control.redshift_cluster_encrypted_with_cmk,aws_compliance.control.redshift_cluster_kms_enabledProwler Check:
redshift_cluster_encrypted_at_restAWS Security Hub Control:
Redshift.10KICS Query:
cfdcabb0-fc06-427c-865b-c59f13e898ceTrivy Check:
AWS-0084
Last reviewed: 2026-03-09