Configuring storage in the Amazon Web Services (AWS) ecosystem requires a precise understanding of how access permissions are managed at the infrastructure level. For organizations utilizing Terraform to provision cloud resources, the aws_s3_bucket_acl resource and the related acl argument within bucket definitions serve as critical controls for security and interoperability. However, the landscape of S3 access management has shifted significantly. Since April 2023, AWS has altered the default behavior for bucket ownership, leading to widespread deployment failures for teams relying on legacy Access Control List (ACL) patterns. This article provides a comprehensive technical deep dive into the aws_s3_bucket_acl resource, the Terraform AWS module for S3 buckets, and the specific mechanisms required to resolve the AccessControlListNotSupported error while maintaining secure, compliant infrastructure-as-code practices.
Understanding S3 Access Control Mechanisms and Terraform Resources
Access Control Lists (ACLs) represent one of the original methods for managing access to S3 buckets and objects. They function by attaching a list of access permissions to each bucket and object, specifying which AWS accounts or pre-defined groups are granted what type of access. At the bucket level, ACLs can control permissions like the ability to list objects or manage the bucket's ACL itself. Historically, this was the primary method for granting third-party access or enabling specific log delivery services. However, AWS now offers a broader set of access control mechanisms designed to protect S3 resources, including IAM policies, S3 Access Points, Multi-Region Access Points, and signed URLs.
In the context of Terraform, managing these permissions is handled through specific resources and arguments. The aws_s3_bucket_acl resource allows users to apply a "canned ACL" or a granular access_control_policy to a bucket. It is critical to understand that terraform destroy does not delete the S3 bucket ACL but does remove the resource from Terraform state. This behavior can lead to unexpected permissions if the underlying AWS resource is not explicitly deleted or if ownership controls interfere with the state tracking.
The primary arguments for the aws_s3_bucket_acl resource include acl, access_control_policy, and bucket.
| Argument | Type | Description |
|---|---|---|
acl |
String | (Optional, Conflicts with access_control_policy) The canned ACL to apply to the bucket. Valid values: private, public-read, public-read-write, authenticated-read. |
access_control_policy |
Block | (Optional, Conflicts with acl) A configuration block that sets the ACL permissions for an object per grantee. |
bucket |
String | (Required, Forces new resource) The name of the bucket. |
When using the access_control_policy block, the configuration supports grant blocks (required) and an owner block. Each grant specifies a grantee (such as a CanonicalUser or Group) and a permission level (such as READ or READ_ACP). For example, granting public read access via a group URI like http://acs.amazonaws.com/groups/global/AllUsers is a common, albeit risky, pattern that requires careful consideration of security implications.
The Impact of S3 Object Ownership Changes on Terraform Configurations
A major operational challenge for infrastructure teams arises from the introduction of the "S3 Object Ownership" feature. AWS introduced this feature to simplify access management. For buckets created on or after April 2023, the default ownership setting is BucketOwnerEnforced, which disables ACLs entirely. This change was implemented to move the industry away from ACLs, which are considered a legacy and potentially insecure mechanism, toward IAM-based access controls.
When a Terraform configuration attempts to set an ACL on a bucket where ownership controls enforce BucketOwnerEnforced, the deployment fails with a specific error. This error has become extremely common in modern pipelines. The error message typically appears during the terraform apply phase:
text
Error: error creating S3 Bucket (my-bucket) ACL: AccessControlListNotSupported:
The bucket does not allow ACLs
status code: 400, request id: ABC123XYZ
Alternatively, users may encounter the error during the PUT operation for the ACL:
text
Error: error putting S3 Bucket ACL: AccessControlListNotSupported:
The bucket does not allow ACLs
This failure occurs because the bucket's ownership controls explicitly prevent the application of ACLs. If a user specifies a canned ACL when creating a bucket that is subject to these new default controls, Amazon S3 may ignore it or, in the case of explicit Terraform resource management, throw the AccessControlListNotSupported exception. This shift means that simply applying a private or log-delivery-write ACL is no longer sufficient or, in many cases, possible without first adjusting the ownership controls.
Configuring Ownership Controls in the Terraform AWS Module
The terraform-aws-modules/s3-bucket module provides a robust way to manage S3 buckets with nearly all features provided by the Terraform AWS provider. This module supports static website hosting, access logging, versioning, CORS, lifecycle rules, server-side encryption, object locking, Cross-Region Replication (CRR), and various log delivery bucket policies (ELB, ALB, NLB, WAF). Crucially, the module includes arguments to manage the new ownership controls, which are essential for resolving the ACL error described above.
The two primary arguments for controlling ownership within the module are control_object_ownership and object_ownership.
| Argument | Type | Default | Description |
|---|---|---|---|
control_object_ownership |
Bool | false |
Whether to manage S3 Bucket Ownership Controls on this bucket. |
object_ownership |
String | null |
The ownership setting for the bucket. Valid values include BucketOwnerEnforced, BucketOwnerPreferred, and ObjectWriter. |
To successfully apply an ACL via the module, the control_object_ownership argument must be set to true, and the object_ownership must be set to a value that allows ACLs. The BucketOwnerEnforced value disables ACLs, so it is incompatible with ACL-based configurations. The ObjectWriter or BucketOwnerPreferred values permit ACL usage, though BucketOwnerPreferred is generally recommended for new buckets that require ACLs for log delivery or compatibility.
It is important to note that simply disabling ACLs on your S3 bucket is not always a straightforward task via the Terraform module without explicitly managing these ownership arguments. If a configuration relies on the default behavior, it will likely fail if it attempts to assign an ACL. Understanding what is happening under the hood is critical in cloud environments to avoid deployment failures.
Resolving the Error with Module Configuration
The standard approach to fixing the AccessControlListNotSupported error when using the terraform-aws-modules/s3-bucket module is to explicitly enable ownership control management and select a compatible ownership mode. For a standard private bucket that does not require public access but may need to support log delivery, the configuration would look like the following:
```hcl
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
bucket = "my-s3-bucket"
acl = "private"
controlobjectownership = true
object_ownership = "ObjectWriter"
versioning = {
enabled = true
}
}
```
In this example, control_object_ownership is set to true, which allows the module to manage the ownership controls resource. The object_ownership is set to ObjectWriter, which allows the owner of the object to be the entity that uploaded the object, but more importantly, in the context of bucket-level ACLs, it avoids the strict enforcement of BucketOwnerEnforced that blocks ACLs. Note that for many use cases, BucketOwnerPreferred is also a viable option, but ObjectWriter is often used in the provided module examples for general bucket creation where ACLs are explicitly set.
For buckets dedicated to log delivery, such as those for ELB or ALB, the configuration must include specific policies to allow the logging service to write to the bucket. The module supports this via the attach_elb_log_delivery_policy and attach_lb_log_delivery_policy arguments.
Advanced Module Features and Conditional Resource Creation
The Terraform AWS S3 bucket module offers additional features that enhance infrastructure flexibility. One such feature is the ability to create S3 resources conditionally. Since Terraform does not allow the use of count inside a module block, the module provides the create_bucket argument to control whether the S3 bucket resource is created.
| Argument | Type | Default | Description |
|---|---|---|---|
create_bucket |
Bool | true |
Controls if S3 bucket should be created. |
force_destroy |
Bool | false |
A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable. |
bucket_prefix |
String | null |
(Optional, Forces new resource) Creates a unique bucket name beginning with the specified prefix. Conflicts with bucket. |
If a configuration requires a bucket only in certain environments (e.g., production but not development), setting create_bucket = false prevents the module from creating the bucket resource. This is particularly useful for managing dependencies in complex multi-environment setups.
Furthermore, the module supports placeholders in bucket policies to keep policies correct with the S3 bucket and AWS account properties. You can use the placeholders _S3_BUCKET_ID_, _S3_BUCKET_ARN_, and _AWS_ACCOUNT_ID_ in the policy document. These values are replaced with the actual values during the policy attachment. This is especially useful when using bucket prefixes, where the final bucket name is generated dynamically.
Log Delivery Bucket Configuration
Configuring buckets for log delivery requires specific permissions. For example, a bucket intended for ELB logs requires the attach_elb_log_delivery_policy argument to be set to true. For ALB or NLB logs, the attach_lb_log_delivery_policy argument is required. These policies allow the respective load balancers to deliver logs to the bucket.
```hcl
module "s3bucketfor_logs" {
source = "terraform-aws-modules/s3-bucket/aws"
bucket = "my-s3-bucket-for-logs"
acl = "log-delivery-write"
# Allow deletion of non-empty bucket
force_destroy = true
controlobjectownership = true
object_ownership = "ObjectWriter"
attachelblogdeliverypolicy = true # Required for ALB logs
attachlblogdeliverypolicy = true # Required for ALB/NLB logs
}
```
In this configuration, the acl is set to log-delivery-write, which is a specific canned ACL designed for this purpose. The control_object_ownership and object_ownership arguments are included to ensure that the bucket allows the application of this ACL, thereby preventing the AccessControlListNotSupported error. The force_destroy argument is set to true to allow the bucket to be destroyed even if it contains log files, which is common in CI/CD pipelines where ephemeral logging buckets are used.
Security Considerations and Best Practices
While ACLs are still supported and necessary for certain use cases like log delivery, AWS recommends moving away from ACLs in favor of IAM policies wherever possible. IAM policies offer more granular control and are better integrated with AWS Identity and Access Management. However, for backward compatibility and specific service integrations, ACLs remain relevant.
When configuring ACLs via Terraform, it is essential to avoid the public-read and public-read-write canned ACLs unless absolutely necessary. Publicly accessible buckets are a common source of data breaches. The private ACL is the default and most secure option. If public access is required, S3 Access Points or CloudFront distributions with OAI/OAC should be considered instead of public ACLs.
Additionally, the ignore_public_acls argument in the module controls whether Amazon S3 should ignore public ACLs for this bucket. This is part of the Account-level Public Access Block settings and provides an additional layer of security to prevent accidental public exposure.
Understanding the interaction between ownership controls and ACLs is vital. The BucketOwnerEnforced setting is the most secure in terms of preventing unintended ACL-based access, but it breaks legacy workflows that rely on ACLs. Therefore, the strategy should be to minimize the use of ACLs and reserve them for specific, documented use cases such as log delivery, where they are still supported and required.
Conclusion
The management of aws_s3_bucket_acl in Terraform has evolved significantly due to AWS's shift toward IAM-based access controls and the introduction of S3 Object Ownership. The AccessControlListNotSupported error is a direct consequence of this shift, affecting buckets created after April 2023 that default to BucketOwnerEnforced. To deploy S3 buckets with ACLs successfully, infrastructure engineers must explicitly manage ownership controls using the control_object_ownership and object_ownership arguments in the Terraform AWS module or the native AWS provider resources.
The terraform-aws-modules/s3-bucket module provides a comprehensive solution for managing these complexities, offering support for a wide range of S3 features including versioning, lifecycle rules, and specific log delivery policies. By setting control_object_ownership to true and selecting a compatible object_ownership value such as ObjectWriter or BucketOwnerPreferred, teams can continue to use ACLs where necessary while maintaining control over the bucket's security posture. Furthermore, the module's support for conditional creation via create_bucket and dynamic policy placeholders enhances the flexibility and reusability of Terraform configurations. As AWS continues to deprecate certain legacy features, staying informed about these changes and adapting Infrastructure-as-Code templates is essential for maintaining robust, secure, and error-free deployments.