S3 Access Control: Bucket Policies and ACLs
Write bucket policies, compare them with ACLs, and configure public access block settings for secure hosting.
S3 Access Control: Bucket Policies and ACLs is a free AWS Solutions Architect lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AWS Solutions Architect learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
S3 Access Control Overview
S3 offers multiple overlapping access control mechanisms: IAM policies (identity-based, control what principals can do), bucket policies (resource-based JSON policies on the bucket), Access Control Lists (ACLs) (legacy per-object/bucket grants), and S3 Block Public Access (account or bucket-level override that blocks any public access regardless of other policies). For most use cases today, bucket policies plus Block Public Access is the recommended approach—ACLs are considered legacy.
Bucket Policies: Resource-Based JSON
A bucket policy is a JSON document attached directly to the S3 bucket. It specifies which principals (IAM users, roles, AWS accounts, services, or the public) can perform which actions on which resources (the bucket and/or specific key prefixes). Bucket policies support cross-account access without needing IAM roles: you can grant a different AWS account's IAM role read access to specific objects directly in the bucket policy. Each bucket can have one policy, and the maximum size is 20 KB.
# Allow a specific IAM role from another account to read objects
{
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Principal': {
'AWS': 'arn:aws:iam::999999999999:role/PartnerReadRole'
},
'Action': 's3:GetObject',
'Resource': 'arn:aws:s3:::my-bucket/partner-data/*'
}]
}Making Objects Publicly Readable
To serve public content (e.g., static website assets, public datasets), you can make objects publicly readable via a bucket policy. First, disable Block Public Access at the bucket level, then add a bucket policy statement with Principal: '*' and Action: s3:GetObject. The combination of disabling the Block Public Access setting and the bucket policy Allow is required—enabling one without the other will not work. Always scope the Resource to a specific prefix rather than the entire bucket unless you intentionally want all objects public.
# Public read policy for static website assets
{
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Principal': '*',
'Action': 's3:GetObject',
'Resource': 'arn:aws:s3:::my-website-bucket/public/*'
}]
}S3 Block Public Access Settings
S3 Block Public Access is a safety net with four settings that override bucket policies and ACLs: BlockPublicAcls (rejects requests to set public ACLs), IgnorePublicAcls (ignores existing public ACLs), BlockPublicPolicy (rejects bucket policies that grant public access), and RestrictPublicBuckets (restricts access based on public policy). All four settings are enabled by default. You can also enable Block Public Access at the account level, blocking it for all buckets regardless of individual bucket settings—ideal for preventing accidental public exposure.
# Enable all Block Public Access settings on a bucket
aws s3api put-public-access-block \
--bucket my-private-bucket \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=trueAccess Control Lists (ACLs): Legacy
S3 ACLs are the original access control mechanism, predating IAM. An ACL grants predefined permissions (READ, WRITE, FULL_CONTROL) to AWS accounts or predefined groups (all users, authenticated AWS users, log delivery). ACLs can be applied at the bucket level or the individual object level. AWS now recommends disabling ACLs (the S3 'Bucket Owner Enforced' setting makes the bucket owner own all objects, disabling ACLs) and using bucket policies and IAM instead. ACLs are still tested on the SAA-C03 exam as a legacy concept.
# Disable ACLs by setting ownership to BucketOwnerEnforced
aws s3api put-bucket-ownership-controls \
--bucket my-bucket \
--ownership-controls '{"Rules":[{"ObjectOwnership":"BucketOwnerEnforced"}]}'Origin Access Control for CloudFront
When serving S3 content through CloudFront, you want the bucket private but CloudFront able to fetch objects. Use Origin Access Control (OAC)—the modern replacement for Origin Access Identity (OAI). OAC creates a CloudFront identity that you grant s3:GetObject permission in the bucket policy, while keeping Block Public Access enabled. This way, users must go through CloudFront (for caching, WAF, HTTPS), and cannot access the bucket directly—a common secure architecture pattern on the SAA-C03 exam.
# Bucket policy granting CloudFront OAC access
{
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Principal': {
'Service': 'cloudfront.amazonaws.com'
},
'Action': 's3:GetObject',
'Resource': 'arn:aws:s3:::my-bucket/*',
'Condition': {
'StringEquals': {
'AWS:SourceArn': 'arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE'
}
}
}]
}Cross-Account S3 Access
There are two ways to grant another AWS account access to your S3 bucket. Option 1 — Bucket policy: add a statement with the external account's ARN as the Principal and the desired S3 actions. The external account's IAM users/roles still need IAM permissions to call S3, plus the bucket policy must Allow them. Option 2 — IAM role with trust policy: create a role in your account trusted by the external account; the external account's identities assume the role and get your bucket's permissions. Bucket policy is simpler for read-only scenarios; roles are better for operational access.
CORS Configuration for Web Applications
Cross-Origin Resource Sharing (CORS) allows a web application hosted on one domain to make JavaScript fetch requests to an S3 bucket on a different domain. Without a CORS configuration, browsers block these requests for security. You add a CORS configuration to the bucket that specifies allowed origins, HTTP methods, and headers. CORS is commonly needed when a React SPA hosted on example.com fetches images or files directly from an S3 bucket URL.
# Apply a CORS configuration
aws s3api put-bucket-cors \
--bucket my-website-bucket \
--cors-configuration '{"CORSRules":[{"AllowedOrigins":["https://example.com"],"AllowedMethods":["GET"],"AllowedHeaders":["*"],"MaxAgeSeconds":3600}]}'Pre-Signed URLs for Temporary Access
A pre-signed URL grants time-limited access to a private S3 object (for GET or PUT) without changing any bucket or object permissions. The URL embeds your credentials and an expiry time—anyone with the URL can access the object until it expires. Use pre-signed URLs to: let authenticated users in your app download private files, allow clients to upload directly to S3 without going through your backend, or share reports temporarily. The expiry can range from 1 second to 7 days (when using STS temporary credentials the max is 12 hours).
# Generate a pre-signed GET URL valid for 24 hours
aws s3 presign s3://my-private-bucket/reports/invoice.pdf \
--expires-in 86400
# Generate a pre-signed PUT URL (for client uploads)
aws s3 presign s3://my-private-bucket/uploads/new-file.pdf \
--expires-in 3600 \
--method PUTBucket Policy Conditions for Security
Use bucket policy conditions to add context-based security. Common patterns: aws:SourceIp restricts access to specific IP ranges (e.g., VPC endpoints or corporate networks); aws:SecureTransport: true forces HTTPS by denying requests over HTTP (a best practice for all buckets storing sensitive data); s3:x-amz-server-side-encryption ensures objects must be uploaded with server-side encryption; and aws:PrincipalOrgID restricts access to principals within your AWS Organisation, preventing data exfiltration to external accounts.
# Deny non-HTTPS access to the bucket
{
'Effect': 'Deny',
'Principal': '*',
'Action': 's3:*',
'Resource': [
'arn:aws:s3:::my-secure-bucket',
'arn:aws:s3:::my-secure-bucket/*'
],
'Condition': {
'Bool': {'aws:SecureTransport': 'false'}
}
}S3 VPC Endpoints for Private Access
By default, EC2 instances in a private subnet access S3 over the internet (via NAT gateway), incurring NAT costs and exposing traffic to the public internet. S3 Gateway Endpoints provide private connectivity to S3 from within a VPC without a NAT gateway, at no extra charge. You add the Gateway Endpoint to your route table; traffic to S3 is automatically routed through AWS's private network. You can also add bucket policy conditions using aws:SourceVpce to restrict access to requests coming through the endpoint only.
# Create an S3 gateway endpoint and associate with route tables
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-12345678Quick Check
Test your understanding of AWS Solutions Architect (SAA-C03) concepts from this lesson.
Lesson Recap
In this lesson you learned: bucket policies are resource-based JSON documents that control cross-account and service access to S3, S3 Block Public Access is a safety override that prevents accidental public exposure, and pre-signed URLs, VPC endpoints, and CORS configurations address specific access patterns securely. Next up we cover S3 versioning, MFA Delete, and replication.
Frequently asked questions
Is the “S3 Access Control: Bucket Policies and ACLs” lesson free?
Yes — the full text of “S3 Access Control: Bucket Policies and ACLs” is free to read here on the web, and the AWS Solutions Architect course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AWS Solutions Architect course, upgrade to CoddyKit PRO.
What will I learn in “S3 Access Control: Bucket Policies and ACLs”?
Write bucket policies, compare them with ACLs, and configure public access block settings for secure hosting. You practise AWS Solutions Architect with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AWS Solutions Architect?
No prior experience is required. AWS Solutions Architect on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “S3 Access Control: Bucket Policies and ACLs” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AWS Solutions Architect lesson?
Yes. Every AWS Solutions Architect lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Buckets, Objects, and Regions
- S3 Access Control: Bucket Policies and ACLs
- Versioning, MFA Delete, and Replication
- Storage Classes and Lifecycle Policies