Configure AWS S3 Bucket ACLs

The aws:s3/bucketAclV2:BucketAclV2 resource, part of the Pulumi AWS provider, configures access control lists (ACLs) for S3 buckets, defining who can read, write, or manage bucket permissions. This guide focuses on three capabilities: canned ACLs for common permission patterns, public access configuration, and explicit grant-based permissions.

ACLs require an existing bucket and BucketOwnershipControls resource. The resource manages ACL configuration but does not delete ACLs on destroy (only removes from state). The examples are intentionally small. Combine them with your own bucket and ownership control configuration.

Apply a canned ACL for private access

Most buckets start with private access, restricting read and write permissions to the bucket owner. Canned ACLs provide predefined permission sets without requiring explicit grant configuration.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.s3.Bucket("example", {bucket: "my-tf-example-bucket"});
const exampleBucketOwnershipControls = new aws.s3.BucketOwnershipControls("example", {
    bucket: example.id,
    rule: {
        objectOwnership: "BucketOwnerPreferred",
    },
});
const exampleBucketAcl = new aws.s3.BucketAcl("example", {
    bucket: example.id,
    acl: "private",
}, {
    dependsOn: [exampleBucketOwnershipControls],
});
import pulumi
import pulumi_aws as aws

example = aws.s3.Bucket("example", bucket="my-tf-example-bucket")
example_bucket_ownership_controls = aws.s3.BucketOwnershipControls("example",
    bucket=example.id,
    rule={
        "object_ownership": "BucketOwnerPreferred",
    })
example_bucket_acl = aws.s3.BucketAcl("example",
    bucket=example.id,
    acl="private",
    opts = pulumi.ResourceOptions(depends_on=[example_bucket_ownership_controls]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := s3.NewBucket(ctx, "example", &s3.BucketArgs{
			Bucket: pulumi.String("my-tf-example-bucket"),
		})
		if err != nil {
			return err
		}
		exampleBucketOwnershipControls, err := s3.NewBucketOwnershipControls(ctx, "example", &s3.BucketOwnershipControlsArgs{
			Bucket: example.ID(),
			Rule: &s3.BucketOwnershipControlsRuleArgs{
				ObjectOwnership: pulumi.String("BucketOwnerPreferred"),
			},
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketAcl(ctx, "example", &s3.BucketAclArgs{
			Bucket: example.ID(),
			Acl:    pulumi.String("private"),
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleBucketOwnershipControls,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.S3.Bucket("example", new()
    {
        BucketName = "my-tf-example-bucket",
    });

    var exampleBucketOwnershipControls = new Aws.S3.BucketOwnershipControls("example", new()
    {
        Bucket = example.Id,
        Rule = new Aws.S3.Inputs.BucketOwnershipControlsRuleArgs
        {
            ObjectOwnership = "BucketOwnerPreferred",
        },
    });

    var exampleBucketAcl = new Aws.S3.BucketAcl("example", new()
    {
        Bucket = example.Id,
        Acl = "private",
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleBucketOwnershipControls,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.s3.BucketOwnershipControls;
import com.pulumi.aws.s3.BucketOwnershipControlsArgs;
import com.pulumi.aws.s3.inputs.BucketOwnershipControlsRuleArgs;
import com.pulumi.aws.s3.BucketAcl;
import com.pulumi.aws.s3.BucketAclArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Bucket("example", BucketArgs.builder()
            .bucket("my-tf-example-bucket")
            .build());

        var exampleBucketOwnershipControls = new BucketOwnershipControls("exampleBucketOwnershipControls", BucketOwnershipControlsArgs.builder()
            .bucket(example.id())
            .rule(BucketOwnershipControlsRuleArgs.builder()
                .objectOwnership("BucketOwnerPreferred")
                .build())
            .build());

        var exampleBucketAcl = new BucketAcl("exampleBucketAcl", BucketAclArgs.builder()
            .bucket(example.id())
            .acl("private")
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleBucketOwnershipControls)
                .build());

    }
}
resources:
  example:
    type: aws:s3:Bucket
    properties:
      bucket: my-tf-example-bucket
  exampleBucketOwnershipControls:
    type: aws:s3:BucketOwnershipControls
    name: example
    properties:
      bucket: ${example.id}
      rule:
        objectOwnership: BucketOwnerPreferred
  exampleBucketAcl:
    type: aws:s3:BucketAcl
    name: example
    properties:
      bucket: ${example.id}
      acl: private
    options:
      dependsOn:
        - ${exampleBucketOwnershipControls}

The acl property accepts predefined values like “private”, “public-read”, or “bucket-owner-full-control”. The dependsOn ensures BucketOwnershipControls is created first, as S3 requires ownership controls before applying ACLs. The objectOwnership setting of “BucketOwnerPreferred” allows the bucket owner to automatically own uploaded objects.

Enable public read access with security overrides

Some use cases require public read access for static website hosting or content distribution. This configuration explicitly disables S3’s default security settings to allow public access.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.s3.Bucket("example", {bucket: "my-tf-example-bucket"});
const exampleBucketOwnershipControls = new aws.s3.BucketOwnershipControls("example", {
    bucket: example.id,
    rule: {
        objectOwnership: "BucketOwnerPreferred",
    },
});
const exampleBucketPublicAccessBlock = new aws.s3.BucketPublicAccessBlock("example", {
    bucket: example.id,
    blockPublicAcls: false,
    blockPublicPolicy: false,
    ignorePublicAcls: false,
    restrictPublicBuckets: false,
});
const exampleBucketAcl = new aws.s3.BucketAcl("example", {
    bucket: example.id,
    acl: "public-read",
}, {
    dependsOn: [
        exampleBucketOwnershipControls,
        exampleBucketPublicAccessBlock,
    ],
});
import pulumi
import pulumi_aws as aws

example = aws.s3.Bucket("example", bucket="my-tf-example-bucket")
example_bucket_ownership_controls = aws.s3.BucketOwnershipControls("example",
    bucket=example.id,
    rule={
        "object_ownership": "BucketOwnerPreferred",
    })
example_bucket_public_access_block = aws.s3.BucketPublicAccessBlock("example",
    bucket=example.id,
    block_public_acls=False,
    block_public_policy=False,
    ignore_public_acls=False,
    restrict_public_buckets=False)
example_bucket_acl = aws.s3.BucketAcl("example",
    bucket=example.id,
    acl="public-read",
    opts = pulumi.ResourceOptions(depends_on=[
            example_bucket_ownership_controls,
            example_bucket_public_access_block,
        ]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := s3.NewBucket(ctx, "example", &s3.BucketArgs{
			Bucket: pulumi.String("my-tf-example-bucket"),
		})
		if err != nil {
			return err
		}
		exampleBucketOwnershipControls, err := s3.NewBucketOwnershipControls(ctx, "example", &s3.BucketOwnershipControlsArgs{
			Bucket: example.ID(),
			Rule: &s3.BucketOwnershipControlsRuleArgs{
				ObjectOwnership: pulumi.String("BucketOwnerPreferred"),
			},
		})
		if err != nil {
			return err
		}
		exampleBucketPublicAccessBlock, err := s3.NewBucketPublicAccessBlock(ctx, "example", &s3.BucketPublicAccessBlockArgs{
			Bucket:                example.ID(),
			BlockPublicAcls:       pulumi.Bool(false),
			BlockPublicPolicy:     pulumi.Bool(false),
			IgnorePublicAcls:      pulumi.Bool(false),
			RestrictPublicBuckets: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketAcl(ctx, "example", &s3.BucketAclArgs{
			Bucket: example.ID(),
			Acl:    pulumi.String("public-read"),
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleBucketOwnershipControls,
			exampleBucketPublicAccessBlock,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.S3.Bucket("example", new()
    {
        BucketName = "my-tf-example-bucket",
    });

    var exampleBucketOwnershipControls = new Aws.S3.BucketOwnershipControls("example", new()
    {
        Bucket = example.Id,
        Rule = new Aws.S3.Inputs.BucketOwnershipControlsRuleArgs
        {
            ObjectOwnership = "BucketOwnerPreferred",
        },
    });

    var exampleBucketPublicAccessBlock = new Aws.S3.BucketPublicAccessBlock("example", new()
    {
        Bucket = example.Id,
        BlockPublicAcls = false,
        BlockPublicPolicy = false,
        IgnorePublicAcls = false,
        RestrictPublicBuckets = false,
    });

    var exampleBucketAcl = new Aws.S3.BucketAcl("example", new()
    {
        Bucket = example.Id,
        Acl = "public-read",
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleBucketOwnershipControls,
            exampleBucketPublicAccessBlock,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.s3.BucketOwnershipControls;
import com.pulumi.aws.s3.BucketOwnershipControlsArgs;
import com.pulumi.aws.s3.inputs.BucketOwnershipControlsRuleArgs;
import com.pulumi.aws.s3.BucketPublicAccessBlock;
import com.pulumi.aws.s3.BucketPublicAccessBlockArgs;
import com.pulumi.aws.s3.BucketAcl;
import com.pulumi.aws.s3.BucketAclArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Bucket("example", BucketArgs.builder()
            .bucket("my-tf-example-bucket")
            .build());

        var exampleBucketOwnershipControls = new BucketOwnershipControls("exampleBucketOwnershipControls", BucketOwnershipControlsArgs.builder()
            .bucket(example.id())
            .rule(BucketOwnershipControlsRuleArgs.builder()
                .objectOwnership("BucketOwnerPreferred")
                .build())
            .build());

        var exampleBucketPublicAccessBlock = new BucketPublicAccessBlock("exampleBucketPublicAccessBlock", BucketPublicAccessBlockArgs.builder()
            .bucket(example.id())
            .blockPublicAcls(false)
            .blockPublicPolicy(false)
            .ignorePublicAcls(false)
            .restrictPublicBuckets(false)
            .build());

        var exampleBucketAcl = new BucketAcl("exampleBucketAcl", BucketAclArgs.builder()
            .bucket(example.id())
            .acl("public-read")
            .build(), CustomResourceOptions.builder()
                .dependsOn(                
                    exampleBucketOwnershipControls,
                    exampleBucketPublicAccessBlock)
                .build());

    }
}
resources:
  example:
    type: aws:s3:Bucket
    properties:
      bucket: my-tf-example-bucket
  exampleBucketOwnershipControls:
    type: aws:s3:BucketOwnershipControls
    name: example
    properties:
      bucket: ${example.id}
      rule:
        objectOwnership: BucketOwnerPreferred
  exampleBucketPublicAccessBlock:
    type: aws:s3:BucketPublicAccessBlock
    name: example
    properties:
      bucket: ${example.id}
      blockPublicAcls: false
      blockPublicPolicy: false
      ignorePublicAcls: false
      restrictPublicBuckets: false
  exampleBucketAcl:
    type: aws:s3:BucketAcl
    name: example
    properties:
      bucket: ${example.id}
      acl: public-read
    options:
      dependsOn:
        - ${exampleBucketOwnershipControls}
        - ${exampleBucketPublicAccessBlock}

The BucketPublicAccessBlock resource disables all four public access protections (blockPublicAcls, blockPublicPolicy, ignorePublicAcls, restrictPublicBuckets). The acl property then applies “public-read”, making all bucket objects publicly readable. The dependsOn array ensures both ownership controls and public access settings are configured before the ACL.

Define granular permissions with explicit grants

When canned ACLs don’t provide the right permission mix, explicit grants let you specify exactly which principals can read, write, or manage ACLs.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const current = aws.s3.getCanonicalUserId({});
const example = new aws.s3.Bucket("example", {bucket: "my-tf-example-bucket"});
const exampleBucketOwnershipControls = new aws.s3.BucketOwnershipControls("example", {
    bucket: example.id,
    rule: {
        objectOwnership: "BucketOwnerPreferred",
    },
});
const exampleBucketAcl = new aws.s3.BucketAcl("example", {
    bucket: example.id,
    accessControlPolicy: {
        grants: [
            {
                grantee: {
                    id: current.then(current => current.id),
                    type: "CanonicalUser",
                },
                permission: "READ",
            },
            {
                grantee: {
                    type: "Group",
                    uri: "http://acs.amazonaws.com/groups/s3/LogDelivery",
                },
                permission: "READ_ACP",
            },
        ],
        owner: {
            id: current.then(current => current.id),
        },
    },
}, {
    dependsOn: [exampleBucketOwnershipControls],
});
import pulumi
import pulumi_aws as aws

current = aws.s3.get_canonical_user_id()
example = aws.s3.Bucket("example", bucket="my-tf-example-bucket")
example_bucket_ownership_controls = aws.s3.BucketOwnershipControls("example",
    bucket=example.id,
    rule={
        "object_ownership": "BucketOwnerPreferred",
    })
example_bucket_acl = aws.s3.BucketAcl("example",
    bucket=example.id,
    access_control_policy={
        "grants": [
            {
                "grantee": {
                    "id": current.id,
                    "type": "CanonicalUser",
                },
                "permission": "READ",
            },
            {
                "grantee": {
                    "type": "Group",
                    "uri": "http://acs.amazonaws.com/groups/s3/LogDelivery",
                },
                "permission": "READ_ACP",
            },
        ],
        "owner": {
            "id": current.id,
        },
    },
    opts = pulumi.ResourceOptions(depends_on=[example_bucket_ownership_controls]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		current, err := s3.GetCanonicalUserId(ctx, map[string]interface{}{}, nil)
		if err != nil {
			return err
		}
		example, err := s3.NewBucket(ctx, "example", &s3.BucketArgs{
			Bucket: pulumi.String("my-tf-example-bucket"),
		})
		if err != nil {
			return err
		}
		exampleBucketOwnershipControls, err := s3.NewBucketOwnershipControls(ctx, "example", &s3.BucketOwnershipControlsArgs{
			Bucket: example.ID(),
			Rule: &s3.BucketOwnershipControlsRuleArgs{
				ObjectOwnership: pulumi.String("BucketOwnerPreferred"),
			},
		})
		if err != nil {
			return err
		}
		_, err = s3.NewBucketAcl(ctx, "example", &s3.BucketAclArgs{
			Bucket: example.ID(),
			AccessControlPolicy: &s3.BucketAclAccessControlPolicyArgs{
				Grants: s3.BucketAclAccessControlPolicyGrantArray{
					&s3.BucketAclAccessControlPolicyGrantArgs{
						Grantee: &s3.BucketAclAccessControlPolicyGrantGranteeArgs{
							Id:   pulumi.String(current.Id),
							Type: pulumi.String("CanonicalUser"),
						},
						Permission: pulumi.String("READ"),
					},
					&s3.BucketAclAccessControlPolicyGrantArgs{
						Grantee: &s3.BucketAclAccessControlPolicyGrantGranteeArgs{
							Type: pulumi.String("Group"),
							Uri:  pulumi.String("http://acs.amazonaws.com/groups/s3/LogDelivery"),
						},
						Permission: pulumi.String("READ_ACP"),
					},
				},
				Owner: &s3.BucketAclAccessControlPolicyOwnerArgs{
					Id: pulumi.String(current.Id),
				},
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleBucketOwnershipControls,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var current = Aws.S3.GetCanonicalUserId.Invoke();

    var example = new Aws.S3.Bucket("example", new()
    {
        BucketName = "my-tf-example-bucket",
    });

    var exampleBucketOwnershipControls = new Aws.S3.BucketOwnershipControls("example", new()
    {
        Bucket = example.Id,
        Rule = new Aws.S3.Inputs.BucketOwnershipControlsRuleArgs
        {
            ObjectOwnership = "BucketOwnerPreferred",
        },
    });

    var exampleBucketAcl = new Aws.S3.BucketAcl("example", new()
    {
        Bucket = example.Id,
        AccessControlPolicy = new Aws.S3.Inputs.BucketAclAccessControlPolicyArgs
        {
            Grants = new[]
            {
                new Aws.S3.Inputs.BucketAclAccessControlPolicyGrantArgs
                {
                    Grantee = new Aws.S3.Inputs.BucketAclAccessControlPolicyGrantGranteeArgs
                    {
                        Id = current.Apply(getCanonicalUserIdResult => getCanonicalUserIdResult.Id),
                        Type = "CanonicalUser",
                    },
                    Permission = "READ",
                },
                new Aws.S3.Inputs.BucketAclAccessControlPolicyGrantArgs
                {
                    Grantee = new Aws.S3.Inputs.BucketAclAccessControlPolicyGrantGranteeArgs
                    {
                        Type = "Group",
                        Uri = "http://acs.amazonaws.com/groups/s3/LogDelivery",
                    },
                    Permission = "READ_ACP",
                },
            },
            Owner = new Aws.S3.Inputs.BucketAclAccessControlPolicyOwnerArgs
            {
                Id = current.Apply(getCanonicalUserIdResult => getCanonicalUserIdResult.Id),
            },
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleBucketOwnershipControls,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.S3Functions;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.s3.BucketOwnershipControls;
import com.pulumi.aws.s3.BucketOwnershipControlsArgs;
import com.pulumi.aws.s3.inputs.BucketOwnershipControlsRuleArgs;
import com.pulumi.aws.s3.BucketAcl;
import com.pulumi.aws.s3.BucketAclArgs;
import com.pulumi.aws.s3.inputs.BucketAclAccessControlPolicyArgs;
import com.pulumi.aws.s3.inputs.BucketAclAccessControlPolicyOwnerArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var current = S3Functions.getCanonicalUserId(%!v(PANIC=Format method: runtime error: invalid memory address or nil pointer dereference);

        var example = new Bucket("example", BucketArgs.builder()
            .bucket("my-tf-example-bucket")
            .build());

        var exampleBucketOwnershipControls = new BucketOwnershipControls("exampleBucketOwnershipControls", BucketOwnershipControlsArgs.builder()
            .bucket(example.id())
            .rule(BucketOwnershipControlsRuleArgs.builder()
                .objectOwnership("BucketOwnerPreferred")
                .build())
            .build());

        var exampleBucketAcl = new BucketAcl("exampleBucketAcl", BucketAclArgs.builder()
            .bucket(example.id())
            .accessControlPolicy(BucketAclAccessControlPolicyArgs.builder()
                .grants(                
                    BucketAclAccessControlPolicyGrantArgs.builder()
                        .grantee(BucketAclAccessControlPolicyGrantGranteeArgs.builder()
                            .id(current.id())
                            .type("CanonicalUser")
                            .build())
                        .permission("READ")
                        .build(),
                    BucketAclAccessControlPolicyGrantArgs.builder()
                        .grantee(BucketAclAccessControlPolicyGrantGranteeArgs.builder()
                            .type("Group")
                            .uri("http://acs.amazonaws.com/groups/s3/LogDelivery")
                            .build())
                        .permission("READ_ACP")
                        .build())
                .owner(BucketAclAccessControlPolicyOwnerArgs.builder()
                    .id(current.id())
                    .build())
                .build())
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleBucketOwnershipControls)
                .build());

    }
}
resources:
  example:
    type: aws:s3:Bucket
    properties:
      bucket: my-tf-example-bucket
  exampleBucketOwnershipControls:
    type: aws:s3:BucketOwnershipControls
    name: example
    properties:
      bucket: ${example.id}
      rule:
        objectOwnership: BucketOwnerPreferred
  exampleBucketAcl:
    type: aws:s3:BucketAcl
    name: example
    properties:
      bucket: ${example.id}
      accessControlPolicy:
        grants:
          - grantee:
              id: ${current.id}
              type: CanonicalUser
            permission: READ
          - grantee:
              type: Group
              uri: http://acs.amazonaws.com/groups/s3/LogDelivery
            permission: READ_ACP
        owner:
          id: ${current.id}
    options:
      dependsOn:
        - ${exampleBucketOwnershipControls}
variables:
  current:
    fn::invoke:
      function: aws:s3:getCanonicalUserId
      arguments: {}

The accessControlPolicy property replaces the simple acl string with a grants array. Each grant specifies a grantee (by canonical user ID or predefined group URI) and a permission level. The getCanonicalUserId function retrieves your AWS account’s canonical user ID for the owner field. This example grants READ permission to the current user and READ_ACP (read ACL permissions) to the S3 log delivery group.

Beyond these examples

These snippets focus on specific ACL configuration features: canned ACLs for common permission patterns, public access configuration, and explicit grant-based permissions. They’re intentionally minimal rather than full bucket access control solutions.

The examples reference pre-existing infrastructure such as S3 buckets (created separately) and BucketOwnershipControls (required dependency). They focus on ACL configuration rather than bucket provisioning.

To keep things focused, common ACL patterns are omitted, including:

  • Cross-account bucket access (expectedBucketOwner)
  • ACL removal and state management (destroy behavior)
  • Directory bucket compatibility (not supported)
  • Import scenarios for existing ACLs

These omissions are intentional: the goal is to illustrate how ACL configuration is wired, not provide drop-in access control modules. See the S3 BucketAclV2 resource reference for all available configuration options.

Let's configure AWS S3 Bucket ACLs

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Lifecycle & Limitations
What happens when I destroy a BucketAclV2 resource?
Destroying the resource removes it from Pulumi state but does not delete the actual ACL from the S3 bucket. The ACL persists in AWS after destroy.
Can I use BucketAclV2 with S3 directory buckets?
No, this resource cannot be used with S3 directory buckets. Use standard S3 buckets only.
What properties can't be changed after creation?
Both bucket and expectedBucketOwner are immutable and cannot be changed after the resource is created.
Security & Public Access
What are the security risks of using public-read ACL?
Using public-read or similar public ACLs disables default S3 bucket security settings and makes all bucket objects publicly accessible. This should be done with caution and only when absolutely necessary.
What do I need to configure for a public-read ACL?
You need two dependencies: BucketOwnershipControls (with objectOwnership set to BucketOwnerPreferred) and BucketPublicAccessBlock (with all four block settings set to false).
Configuration & Dependencies
Why do I need to configure BucketOwnershipControls first?
All ACL configurations require BucketOwnershipControls to be set up before applying ACLs. Use dependsOn to ensure proper ordering, as shown in all examples.
What's the difference between acl and accessControlPolicy?
Use acl for canned (predefined) ACLs like private or public-read. Use accessControlPolicy for custom grants with specific grantees and permissions. You can use one or the other, but not both.
Cross-Account & Import
How do I work with ACLs on buckets owned by a different AWS account?
Set the expectedBucketOwner property to the account ID of the bucket owner. For import, use the format bucket-name,account-id or bucket-name,account-id,acl if a canned ACL is configured.
What grantee types can I use in accessControlPolicy?
You can use CanonicalUser (with an id) or Group (with a uri like http://acs.amazonaws.com/groups/s3/LogDelivery), as shown in the grants example.

Using a different cloud?

Explore storage guides for other cloud providers: