Skip to content

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 encrypted from false to true triggers resource destruction because encrypted is ForceNew in the Terraform AWS provider. Set final_snapshot_identifier before 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 = true without kms_key_id tells Redshift to use the default aws/redshift AWS-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 KMSKeyNotAccessibleFault if the key policy doesn't give Redshift what it needs. The key must grant kms:Encrypt, kms:Decrypt, kms:ReEncrypt*, kms:GenerateDataKey*, kms:DescribeKey, and kms:CreateGrant to 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.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: redshift_cluster_kms_enabled

  • AWS Config Managed Rule: REDSHIFT_CLUSTER_KMS_ENABLED

  • Checkov Check: CKV_AWS_142

  • Powerpipe Controls: aws_compliance.control.redshift_cluster_encrypted_with_cmk, aws_compliance.control.redshift_cluster_kms_enabled

  • Prowler Check: redshift_cluster_encrypted_at_rest

  • AWS Security Hub Control: Redshift.10

  • KICS Query: cfdcabb0-fc06-427c-865b-c59f13e898ce

  • Trivy Check: AWS-0084

Last reviewed: 2026-03-09