The aws:s3/bucketAclV2:BucketAclV2 resource, part of the Pulumi AWS provider, configures access control lists (ACLs) for S3 buckets, defining who can read or write objects. This guide focuses on three capabilities: canned ACLs for common permission patterns, public access configuration, and explicit grant-based permissions.
ACLs require existing buckets and BucketOwnershipControls configuration. Public access requires disabling S3’s default security protections. The examples are intentionally small. Combine them with your own bucket and security configuration.
Apply a canned ACL for private access
Most buckets start with private access, restricting permissions to the bucket owner. Canned ACLs provide predefined permission sets without requiring explicit grants.
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”. BucketOwnershipControls must be configured first; the dependsOn ensures proper ordering. The “private” ACL grants full control to the bucket owner and denies access to everyone else.
Enable public read access with security overrides
Static website hosting or public datasets require anonymous read access, which means explicitly disabling S3’s default security protections.
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}
BucketPublicAccessBlock controls four security settings that normally prevent public access. Setting all four to false allows the “public-read” ACL to take effect. The dependsOn ensures both ownership controls and public access settings are configured before applying the ACL. This configuration exposes all bucket objects publicly.
Define granular permissions with explicit grants
When canned ACLs don’t provide the right permission mix, explicit grants specify exactly which principals get which permissions.
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 acl property when you need fine-grained control. Each grant specifies a grantee (user or group) and permission (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL). The owner field identifies the bucket owner using their canonical user ID. The getCanonicalUserId function retrieves the current account’s ID.
Beyond these examples
These snippets focus on specific ACL-level features: canned ACLs for common permission patterns, public access configuration, and explicit grant-based permissions. They’re intentionally minimal rather than full access control solutions.
The examples require pre-existing infrastructure such as S3 buckets (created separately) and BucketOwnershipControls configuration. They focus on configuring ACLs rather than provisioning buckets or managing lifecycle.
To keep things focused, common ACL patterns are omitted, including:
- Cross-account access (expectedBucketOwner)
- ACL removal and state management
- Directory bucket compatibility
These omissions are intentional: the goal is to illustrate how each ACL feature 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 FREEFrequently Asked Questions
Resource Lifecycle & Limitations
bucket property is immutable and cannot be changed after creation.ACL Configuration
acl for canned (predefined) ACLs like private or public-read. Use accessControlPolicy for custom grants with specific grantees and permissions. You typically use one or the other, not both.private, public-read, public-read-write, aws-exec-read, authenticated-read, bucket-owner-read, bucket-owner-full-control, and log-delivery-write.acl to public-read and configure BucketPublicAccessBlock with all blocks disabled (blockPublicAcls, blockPublicPolicy, ignorePublicAcls, restrictPublicBuckets all set to false). This disables default S3 security settings and makes all bucket objects publicly exposed, so use with caution.Dependencies & Prerequisites
dependsOn: BucketOwnershipControls to ensure bucket ownership settings (like objectOwnership: "BucketOwnerPreferred") are configured before applying ACLs.Import
Import syntax depends on your scenario:
- Same account, no canned ACL:
bucket-name - Same account, with canned ACL:
bucket-name,private - Different account, no canned ACL:
bucket-name,123456789012 - Different account, with canned ACL:
bucket-name,123456789012,private
Note: expectedBucketOwner is deprecated and will be removed in a future version.