Skip to content

Upload fails on S3 buckets with ACLs disabled (BucketOwnerEnforced) #194

Description

@nodoxi

Description

Since April 2023, new S3 buckets are created with Object Ownership set to "Bucket owner enforced" by default, which disables ACLs entirely. AWS recommends using bucket policies instead of ACLs for access control.

The plugin currently always sends an ACL header with every upload (public-read or private depending on the makeUploadsPublic setting). On buckets with ACLs disabled, any request containing an ACL header is rejected by AWS with AccessControlListNotSupported.

This means there is no configuration in the plugin that allows it to work with modern S3 buckets without enabling ACLs on the bucket side (going against AWS recommendations).

Steps to reproduce

  1. Create an S3 bucket (new buckets default to BucketOwnerEnforced / ACLs disabled)
  2. Configure the Craft CMS S3 filesystem pointing to this bucket
  3. Set Make Uploads Public to either ON or OFF
  4. Try to upload any asset

Error

AWS HTTP error: AccessControlListNotSupported (client): The bucket does not allow ACLs

Full trace shows the error originates in FlysystemFs::writeFileFromStreamAwsS3V3Adapter:

{
  "name": "Filesystem Error",
  "message": "Unable to write stream to \"test-upload.png\"",
  "exception": "craft\\errors\\FsException",
  "file": "vendor/craftcms/cms/src/flysystem/src/base/FlysystemFs.php",
  "line": 139
}

Root cause

In Fs::createAdapter() (src/Fs.php, line ~292), the adapter is always created with PortableVisibilityConverter:

return new AwsS3V3Adapter(
    $client,
    App::parseEnv($this->bucket),
    $this->_subfolder(),
    new PortableVisibilityConverter($this->visibility()),  // ← always sends ACL
    // ...
);

PortableVisibilityConverter maps visibility to ACL strings:

  • Visibility::PUBLICpublic-read
  • Visibility::PRIVATEprivate

Both values cause putObject to include an ACL parameter, which is rejected when ACLs are disabled on the bucket.

The existing makeUploadsPublic toggle (added in PR #50) only switches between ACL values — there is no option to not send ACL at all.

Proposed solution

Add a new setting disableAcl.

Environment

  • Craft CMS: 5.x
  • Plugin version: 2.3.0
  • PHP: 8.1+
  • Flysystem: 3.x

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions