S3 buckets should have default encryption enabled using KMS
SSE-S3 encryption uses keys that AWS manages entirely, giving you no control over key rotation schedules, key policies, or access grants. KMS encryption lets you define who can use the key through IAM and KMS key policies, log every decrypt call in CloudTrail, and disable or rotate keys on your own schedule. This separation between the data plane and the key plane is what auditors look for when they evaluate whether you actually control your encryption.
Without KMS, you cannot revoke decryption ability independently of S3 bucket policies. A single overly broad bucket policy could expose plaintext objects with no compensating control.
Retrofit consideration
Changing encryption from SSE-S3 to SSE-KMS on an existing bucket does not re-encrypt objects already stored. Existing objects retain their original encryption. You must copy objects in place (e.g., with aws s3 cp --sse aws:kms) to apply the new key. KMS also introduces per-request costs and API rate limits that may affect high-throughput buckets.
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 "s3_bucket" {
source = "pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaa.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "gdpr.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist80053.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsf.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist800171.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "awswellarchitected.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nydfs23.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "ffiec.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "eugmpannex11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedramplow.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
If you use terraform-aws-modules/s3-bucket/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 "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
server_side_encryption_configuration = {
rule = {
apply_server_side_encryption_by_default = {
sse_algorithm = "aws:kms"
}
}
}
}
Use AWS provider resources directly. See docs for the resources involved: aws_s3_bucket_server_side_encryption_configuration.
resource "aws_s3_bucket" "this" {
bucket = "pofix-abc123"
force_destroy = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = "example-bucket-abc123"
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
sse_algorithm = "aws:kms"
}
}
}
What this control checks
This control validates that every aws_s3_bucket has an associated aws_s3_bucket_server_side_encryption_configuration resource where rule.apply_server_side_encryption_by_default.sse_algorithm is "aws:kms" or "aws:kms:dsse". The kms_master_key_id argument must reference a valid KMS key ARN or alias. A bucket passes when SSE-KMS is the default algorithm. It fails if sse_algorithm is "AES256" (SSE-S3) or if the encryption configuration resource is missing entirely. When using "aws:kms", ensure the referenced KMS key policy grants kms:GenerateDataKey and kms:Decrypt to the principals that need to read and write objects.
Common pitfalls
SSE-S3 looks encrypted but fails this control
Setting
sse_algorithm = "AES256"inaws_s3_bucket_server_side_encryption_configurationenables encryption, but with S3-managed keys. This control requires"aws:kms"or"aws:kms:dsse". Teams often assume any encryption satisfies the rule.Deprecated inline encryption block
Older Terraform configurations use the
server_side_encryption_configurationblock inline withinaws_s3_bucket. AWS provider v4 refactored S3 subresource configuration into standalone resources. Useaws_s3_bucket_server_side_encryption_configurationto avoid drift and evaluation failures.KMS request throttling on high-throughput buckets
SSE-KMS generates a KMS API call per object operation. At high request rates that adds up fast, and KMS quotas are regional and account-specific. Check current limits in Service Quotas before rolling this out to high-throughput buckets. Enabling S3 Bucket Keys (
bucket_key_enabled = true) cuts KMS request volume significantly for most workloads.Cross-account access requires explicit KMS key policy
If another account accesses the bucket, the KMS key policy must grant
kms:Decryptandkms:GenerateDataKeyto that account's principals. IAM policies alone are not sufficient for cross-account KMS usage. Missing key policy grants produceAccessDeniederrors that are difficult to trace back to the key policy.Existing objects are not re-encrypted
Get this wrong and you'll pass the control while leaving a large portion of your data under SSE-S3. Changing the default encryption on a bucket only affects new objects. Run
aws s3 cp s3://bucket/ s3://bucket/ --recursive --sse aws:kms --sse-kms-key-id <key-arn>to re-encrypt objects already in place.
Audit evidence
An auditor expects AWS Config rule evaluation results for S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED (or an equivalent custom rule enforcing KMS-only encryption) showing all buckets as COMPLIANT. Supporting evidence includes the output of aws s3api get-bucket-encryption for each bucket, confirming SSEAlgorithm is aws:kms and KMSMasterKeyID references a specific key ARN. CloudTrail logs should show kms:Decrypt and kms:GenerateDataKey events tied to S3 operations, proving KMS is actively used for object-level encryption.
If a KMS key policy restricts usage, attach a copy of the key policy showing which principals have access. AWS Security Hub compliance findings or third-party scanner output showing bucket encryption status over time strengthen the evidence package.
Framework-specific interpretation
PCI DSS v4.0: Requirement 3.5 is specific: stored account data needs strong cryptography with documented key management processes. KMS checks all those boxes, rotation policies, access controls, and usage logs included.
HIPAA Omnibus Rule 2013: Under 164.312(a)(2)(iv), encryption of ePHI at rest is an addressable specification, not a hard requirement, but most covered entities implement it. KMS is a common way to satisfy this safeguard, and CloudTrail logging of key usage supports the access monitoring evidence 164.312(b) calls for.
GDPR: Article 32's requirement for appropriate technical security measures is the relevant hook. Customer-controlled KMS keys give you a verifiable way to protect personal data at rest in S3, and the key policy itself doubles as documentation of data protection by design.
NIST SP 800-53 Rev 5: SC-28(1) requires cryptographic protection of information at rest; SC-12 covers key establishment and management. This control addresses both. KMS key policies and CloudTrail cover the auditability side of the key lifecycle.
NIST Cybersecurity Framework v2.0: PR.DS calls for protecting data at rest. KMS-managed encryption gives the organization direct control over cryptographic operations on anything stored in S3, which is what that subcategory is looking for.
FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, SC-28 and SC-12 both apply. KMS uses FIPS 140-2 validated modules, which is a hard prerequisite for FedRAMP authorization, not just a recommendation.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
s3_bucket_default_encryption_enabled_kmsAWS Config Managed Rule:
S3_DEFAULT_ENCRYPTION_KMSCheckov Check:
CKV_AWS_145Powerpipe Control:
aws_compliance.control.s3_bucket_default_encryption_enabled_kmsProwler Checks:
backup_vaults_encrypted,s3_bucket_kms_encryptionAWS Security Hub Control:
S3.17Trivy Checks:
AWS-0088,AWS-0132
Last reviewed: 2026-03-09