S3 buckets should not use ACLs for user access control
S3 ACLs predate IAM and bucket policies. They grant coarse permissions (READ, WRITE, FULL_CONTROL) that cannot express conditions, require MFA, or restrict by source IP. When ACLs coexist with bucket policies, the effective permission set becomes harder to reason about, raising the chance of accidental public exposure or over-privileged access.
AWS's recommended path is disabling ACLs entirely with the BucketOwnerEnforced object ownership setting. That collapses permission audits to a single plane: IAM and resource policies.
Retrofit consideration
Existing buckets that rely on ACL grants for cross-account or application access will break when you switch to BucketOwnerEnforced. Audit every bucket's current ACL grants with aws s3api get-bucket-acl before migrating.
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 = "nistcsf.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "acscessentialeight.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "acscism2023.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "rbiitfnbfc.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"
}
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"
object_ownership = "BucketOwnerEnforced"
}
Use AWS provider resources directly. See docs for the resources involved: aws_s3_bucket_ownership_controls.
resource "aws_s3_bucket" "this" {
bucket = "pofix-abc123"
force_destroy = true
}
resource "aws_s3_bucket_ownership_controls" "this" {
bucket = "example-bucket-abc123"
rule {
object_ownership = "BucketOwnerEnforced"
}
}
What this control checks
To pass, each S3 bucket needs an aws_s3_bucket_ownership_controls resource with object_ownership set to "BucketOwnerEnforced". This setting rejects any request that includes an ACL and removes all existing grants except those held by the bucket owner.
In Terraform, declare aws_s3_bucket_ownership_controls referencing the bucket's ID and set object_ownership to "BucketOwnerEnforced" in the rule block. Omit the aws_s3_bucket_acl resource entirely. If one exists with any acl value other than "private", or with explicit access_control_policy grants to non-owner accounts or groups, the control fails.
"BucketOwnerPreferred" and "ObjectWriter" both still allow ACLs and will fail this control.
Common pitfalls
BucketOwnerPreferred still allows ACLs
Teams migrating to fix cross-account object ownership sometimes stop at
"BucketOwnerPreferred"and consider the job done. It's not.BucketOwnerPreferredchanges ownership semantics but leaves ACLs enabled. Only"BucketOwnerEnforced"disables them.Legacy inline acl argument on aws_s3_bucket
Older Terraform configs use the deprecated
aclargument directly onaws_s3_bucket. In provider v4+, S3 bucket settings split into dedicated resources, includingaws_s3_bucket_acl. Removing the inlineaclargument without addingaws_s3_bucket_ownership_controlsleaves ACLs enabled by default, so ACL-based access remains possible.CloudFront OAI requires ACL grants
Before switching to
BucketOwnerEnforced, check whether any CloudFront distributions use Origin Access Identity (OAI). If they do, migrate to Origin Access Control (OAC) first. OAC authenticates with SigV4 and a bucket policy, so it works cleanly with the new ownership setting.Cross-account log delivery via ACL
S3 server access logging and some AWS services (ELB access logs in certain regions, for example) historically required the
log-delivery-writeACL grant. WithBucketOwnerEnforced, those grants stop working. Replace them with a bucket policy grantings3:PutObjectto thelogging.s3.amazonaws.comservice principal instead.
Audit evidence
Config rule s3-bucket-acl-prohibited evaluation results showing all in-scope buckets as COMPLIANT are the primary evidence artifact. Supplement with console screenshots of each bucket's Permissions tab showing "ACLs disabled" under Object Ownership, and programmatic output from aws s3api get-bucket-ownership-controls confirming BucketOwnerEnforced on every bucket in scope.
CloudTrail logs for PutBucketOwnershipControls and PutBucketAcl calls establish when the migration occurred and confirm no ACL modifications have been made since.
Framework-specific interpretation
PCI DSS v4.0: Requirement 7 says access to cardholder data must follow business-need-to-know principles. ACLs can't enforce conditions like source IP, MFA, or time windows, so they can't carry the full weight of a least-privilege model. Bucket policies can, and replacing ACLs with them means access to S3 buckets storing cardholder data is governed by auditable, condition-aware rules that align with what Requirement 7 expects.
NIST Cybersecurity Framework v2.0: PR.AA under NIST CSF v2 calls for managing access permissions and authorizations through defined policies. ACL-based permissions can't express conditions or integrate with centralized IAM auditing. Consolidating S3 access control into IAM and bucket policies is how you meet that requirement.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
s3_bucket_acls_should_prohibit_user_accessAWS Config Managed Rule:
S3_BUCKET_ACL_PROHIBITEDCheckov Check:
CKV2_AWS_65Powerpipe Control:
aws_compliance.control.s3_bucket_acls_should_prohibit_user_accessProwler Check:
s3_bucket_acl_prohibitedAWS Security Hub Control:
S3.12
Last reviewed: 2026-03-09