S3 bucket
For an engineer moving an existing S3 bucket from terraform-aws-modules to compliance.tf. The controls cover encryption, access logging, versioning, public access, replication and object lock. When you finish, terraform plan passes with the compliance.tf source.
Minor fixes · 15-30 minutes per instance · No state moves · Upstream v5.x
Before you start
- A registry token, set up as in Registry tokens. The registry host for each framework is on Registry endpoints.
- An upstream version inside the supported range (v5.x). Older versions: upgrade upstream first, see Version compatibility.
- State: No
terraform state mv. Resource addresses stay the same, because compliance.tf modules keep the upstream resource structure. If a control adds a resource, the plan shows it as an addition. - Not sure if this module should go first? The Migration kit tells you where to start. The Assessment checklist finds risks before you plan.
Change the source
The diff changes the source and adds the settings this module's controls need, so the plan passes. Replace the referenced resources (aws_...) with your own.
module "s3_bucket" {
- source = "terraform-aws-modules/s3-bucket/aws"
+ source = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = "~> 5.0"
bucket = "my-app-logs"
+
+ logging = {
+ target_bucket = "my-access-log-bucket"
+ target_prefix = "s3-access-logs/my-app-logs/"
+ }
+
+ server_side_encryption_configuration = {
+ rule = {
+ apply_server_side_encryption_by_default = {
+ sse_algorithm = "aws:kms"
+ kms_master_key_id = aws_kms_key.s3.arn
+ }
+ }
+ }
+
+ versioning = {
+ enabled = true
+ }
+
+ lifecycle_rule = [{
+ id = "abort-incomplete-multipart-upload"
+ status = "Enabled"
+ abort_incomplete_multipart_upload_days = 7
+ }]
+
+ replication_configuration = {
+ role = aws_iam_role.s3_replication.arn
+
+ rules = [{
+ id = "replicate-all"
+ status = "Enabled"
+ priority = 1
+
+ delete_marker_replication = true
+
+ source_selection_criteria = {
+ sse_kms_encrypted_objects = {
+ enabled = true
+ }
+ }
+
+ destination = {
+ bucket = aws_s3_bucket.replica.arn
+ storage_class = "STANDARD"
+ replica_kms_key_id = aws_kms_key.replica.arn
+ }
+ }]
+ }
tags = {
Environment = "production"
}
} module "s3_bucket" {
- source = "terraform-aws-modules/s3-bucket/aws"
- version = "~> 5.0"
+ # Exception, reviewed: MFA delete cannot coexist with the lifecycle rule below
+ source = "https://pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_mfa_delete_enabled"
bucket = "my-app-logs"
+
+ logging = {
+ target_bucket = "my-access-log-bucket"
+ target_prefix = "s3-access-logs/my-app-logs/"
+ }
+
+ server_side_encryption_configuration = {
+ rule = {
+ apply_server_side_encryption_by_default = {
+ sse_algorithm = "aws:kms"
+ kms_master_key_id = aws_kms_key.s3.arn
+ }
+ }
+ }
+
+ versioning = {
+ enabled = true
+ }
+
+ lifecycle_rule = [{
+ id = "abort-incomplete-multipart-upload"
+ status = "Enabled"
+ abort_incomplete_multipart_upload_days = 7
+ }]
+
+ replication_configuration = {
+ role = aws_iam_role.s3_replication.arn
+
+ rules = [{
+ id = "replicate-all"
+ status = "Enabled"
+ priority = 1
+
+ delete_marker_replication = true
+
+ source_selection_criteria = {
+ sse_kms_encrypted_objects = {
+ enabled = true
+ }
+ }
+
+ destination = {
+ bucket = aws_s3_bucket.replica.arn
+ storage_class = "STANDARD"
+ replica_kms_key_id = aws_kms_key.replica.arn
+ }
+ }]
+ }
tags = {
Environment = "production"
}
}Then download the new source and plan. terraform init installs a module whose source changed; -upgrade is only for fetching the same source and version again:
terraform init
terraform planAfter init, .terraform/modules/<module name>/compliancetf-manifest.json names the framework the module was built for.
Fix the plan errors
Each error names the control and links its page, which lists the frameworks that map it. These are the ones this module raises most often.
Access logging is not configured
The control requires a logging block with a target bucket. The module has no default for it, because only you know which bucket receives the logs. Control: s3_bucket_logging_enabled.
Add a logging block that names an existing bucket you own.
logging = {
target_bucket = "my-access-log-bucket"
target_prefix = "s3-access-logs/my-app-logs/"
}Encryption uses AES-256 or has no KMS key
The control requires sse_algorithm set to aws:kms (or aws:kms:dsse) together with a customer managed key in kms_master_key_id. AES-256, or aws:kms without a key ID, fails. Control: s3_bucket_default_encryption_enabled_kms.
Point default encryption at a KMS key you manage.
server_side_encryption_configuration = {
rule = {
apply_server_side_encryption_by_default = {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
}
}Cross-region replication is not configured
The control requires a replication rule with status Enabled, which needs an IAM role and a destination bucket in another region. Versioning must be on for both buckets. Control: s3_bucket_cross_region_replication_enabled.
Create the role and the destination bucket first, then add a replication rule. The bucket is KMS-encrypted, so the rule also opts in to replicating KMS-encrypted objects and names the replica key; the role needs kms:Decrypt on the source key and kms:Encrypt on the replica key.
replication_configuration = {
role = aws_iam_role.s3_replication.arn
rules = [{
id = "replicate-all"
status = "Enabled"
priority = 1
delete_marker_replication = true
source_selection_criteria = {
sse_kms_encrypted_objects = {
enabled = true
}
}
destination = {
bucket = aws_s3_bucket.replica.arn
storage_class = "STANDARD"
replica_kms_key_id = aws_kms_key.replica.arn
}
}]
}No enabled lifecycle rule on a versioned bucket
When versioning is Enabled, the control requires at least one lifecycle rule with status Enabled. The module's default is no lifecycle rules. Control: s3_bucket_versioning_and_lifecycle_policy_enabled.
Add an enabled lifecycle rule. Aborting incomplete multipart uploads is a rule almost every bucket wants anyway.
lifecycle_rule = [{
id = "abort-incomplete-multipart-upload"
status = "Enabled"
abort_incomplete_multipart_upload_days = 7
}]Object lock is off on an existing bucket
AWS lets you turn Object Lock on for an existing bucket once versioning is enabled, and AWS never turns it off again. New objects get a retention period only if you also configure a default retention rule; without one, objects can still be deleted. Objects already in the bucket get no retention until you apply it with S3 Batch Operations. Control: s3_bucket_object_lock_enabled.
Enable versioning and Object Lock, then use S3 Batch Operations for the existing objects. If the bucket does not need object lock, turn the control off on the source URL and record why.
versioning = {
enabled = true
}
object_lock_enabled = trueMFA delete conflicts with the lifecycle rule (PCI DSS v4.0 only)
PCI DSS v4.0 maps s3_bucket_mfa_delete_enabled and s3_bucket_versioning_and_lifecycle_policy_enabled; SOC 2 maps only the second. AWS does not allow lifecycle configurations on a bucket with MFA delete, so one bucket cannot meet both controls. Only the root user can enable MFA delete, with the MFA device serial and a current code, through the AWS CLI or the API. Control: s3_bucket_mfa_delete_enabled.
Pick one control per bucket and turn the other off on the source, with the reason in the pull request. The After block above keeps the lifecycle rule. To keep MFA delete instead, turn off the lifecycle control, remove lifecycle_rule, set mfa_delete with the root user's MFA serial and code in mfa, and have the root user make that one change.
source = "https://pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_versioning_and_lifecycle_policy_enabled"
versioning = {
enabled = true
mfa_delete = true
mfa = var.root_mfa # "<MFA device serial> <current code>", root user only
}A control that does not apply to this instance can be turned off on the source URL; see Turn controls on and off.
Controls this enforces
SOC 2
- S3 buckets should have cross-region replication enabled
- S3 buckets should have default encryption enabled using KMS
- S3 buckets should have event notifications enabled
- S3 buckets should have logging enabled
- S3 buckets should not be accessible to all authenticated users
- S3 buckets should have object lock enabled
- S3 buckets with versioning enabled should have lifecycle policies configured
- S3 buckets should have versioning enabled
- S3 public access should be blocked at account level
PCI DSS v4.0
- S3 buckets should not use ACLs for user access control
- S3 buckets should have cross-region replication enabled
- S3 buckets should have default encryption enabled using KMS
- S3 buckets should have event notifications enabled
- S3 buckets should have logging enabled
- S3 buckets should have MFA delete enabled
- S3 buckets should have object lock enabled
- S3 buckets with versioning enabled should have lifecycle policies configured
- S3 buckets should have versioning enabled
- S3 public access should be blocked at account level
Each framework also maps controls that this module does not enforce. Those stay with your team; the module page lists enforced and mapped-only controls apart.
Roll back
- Point the source back at
terraform-aws-modules/s3-bucket/aws, keeping the same version constraint. - Run
terraform init. - Run
terraform plan. Expect no resource changes: the values you added stay in place, but compliance.tf no longer checks them.