10.2
Who Can Read My Bucket? IAM, Bucket Policies and Block Public Access
User vs resource policies, cross-account access, ACLs and Object Ownership, and the settings that prevent leaks.
26 min Reading + animations
Tip: tap or hover any dotted-underlined term to see what it means.
By the end of this lesson you will be able to
- Name the tools that control access to S3 and say which ones grant and which ones only limit: IAM policies, bucket policies, ACLs, Block Public Access and Object Ownership.
- Read and write a bucket policy: Effect, Principal, Action, Resource, Condition, and the difference between the bucket ARN and the object ARN.
- Decide any S3 request by hand: same account (one Allow is enough), another account (both sides must allow), anonymous (only the bucket can allow), with explicit Deny and Block Public Access on top.
- Pick the right pattern for the classic cases: a public website, an application on EC2, a partner account, a whole organization, HTTPS only, access only from your VPC.
- Explain each of the four Block Public Access settings, the levels they apply at, and why a public policy can stop working without anyone touching it.
- Explain Object Ownership and why ACLs have been off by default since April 2023, and find exposed buckets with IAM Access Analyzer for S3.
#Every bucket starts private
#Two questions on every request
Every request that reaches S3 is answered in two steps, exactly like every other AWS request (lesson 2.2):
- Who is asking? S3 looks at the signature. A signed request comes from a principal: an IAM user, a role session (an EC2 instance, a Lambda function, a person who assumed a role), the root user, or an AWS service. A request with no signature is anonymous: a browser opening a link,
curlwith no credentials. - Is this principal allowed to do this action on this resource? S3 collects every policy that applies and decides Allow or Deny.
A brand-new bucket answers the second question with no for everyone except its own account’s principals that have IAM permissions. Nobody outside the account, and no anonymous visitor, can read anything until someone deliberately grants it. Since April 2023 every new bucket also comes with two extra locks already closed: Block Public Access is on, and ACLs are disabled. Both are explained later in this lesson.
#The tools at a glance
| Tool | Attached to | Grants access? | Typical use |
|---|---|---|---|
| IAM policy (user-based, identity-based) | A user, group or role | Yes | Your own people and applications |
| Bucket policy (resource-based) | The bucket | Yes | Public read, other accounts, organization-wide sharing, guard rails with Deny |
| ACL (bucket ACL, object ACL) | The bucket or one object | Yes, but disabled by default | Legacy; rare per-object cases |
| Block Public Access | Organization, account, bucket, access point | No, only stops public grants | Leak prevention |
| Object Ownership | The bucket | No, decides who owns uploaded objects and whether ACLs count | Keep Bucket owner enforced |
| Access point policy | An access point | Yes | Per-team entry points to a shared bucket (lesson 10.9) |
| VPC endpoint policy | A gateway or interface endpoint | Limits only | “From this VPC, only these buckets” (lesson 10.10) |
| SCP and RCP | Organization, OU, account | Limits only | Company-wide limits (lesson 2.2, chapter 20) |
| KMS key policy | A KMS key | Yes, for the key | Objects encrypted with SSE-KMS also need the key (lesson 10.8) |
The slides sum it up as user-based (IAM policies: which API calls this user may make) and resource-based (bucket policies, object ACLs, bucket ACLs). The rest of this lesson takes them one by one, then puts them together.
#User-based access: IAM policies
An IAM policy is attached to a user, a group or a role and says which actions that identity may perform on which resources. You met the JSON in lesson 2.1. For S3 the important detail is which ARN each action needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListTheInvoicesPrefix",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::shop-media-example",
"Condition": { "StringLike": { "s3:prefix": "invoices/*" } }
},
{
"Sid": "ReadAndWriteInvoices",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::shop-media-example/invoices/*"
}
]
}
- Bucket-level actions such as s3:ListBucket act on the bucket ARN,
arn:aws:s3:::shop-media-example, with no slash. - Object-level actions such as s3:GetObject, s3:PutObject and s3:DeleteObject act on object ARNs,
arn:aws:s3:::shop-media-example/invoices/*. - Put both forms in a policy when you need both. A policy with only
…/*lets you read files whose names you already know, but listing fails with AccessDenied. A policy with only the bucket ARN lets you list, but reading fails. - S3 ARNs have no Region and no account ID, because bucket names are global (lesson 10.1).
#Applications get roles, never keys
The slides show two pictures for user-based access: an IAM user with an IAM policy, and an EC2 instance with an instance role. The second one is the pattern to remember. An application on EC2 never gets an access key copied onto the disk; it gets an IAM role through an instance profile, and the SDK picks up short-lived credentials automatically (lesson 2.4). Lambda functions, ECS tasks and other services work the same way with their execution or task roles.
#Resource-based access: bucket policies
#What a bucket policy looks like
A bucket policy is a JSON policy attached to the bucket itself. Because it sits on the resource, it must say who it is about: it has a Principal element, which IAM policies do not have.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadForProductPhotos",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shop-media-example/photos/*"
}
]
}
| Element | Meaning | Examples |
|---|---|---|
| Effect | Allow or Deny | "Allow" |
| Principal | Whom the statement is about | "*" (everyone, including anonymous), {"AWS": "arn:aws:iam::444455556666:root"} (an account), {"AWS": "arn:aws:iam::111122223333:role/shop-web-role"}, {"Service": "logging.s3.amazonaws.com"} |
| Action | The S3 API calls | "s3:GetObject", "s3:*" |
| Resource | The bucket and/or its objects | arn:aws:s3:::shop-media-example, arn:aws:s3:::shop-media-example/photos/* |
| Condition (optional) | Extra tests on the request | aws:SecureTransport, aws:SourceIp, aws:SourceVpce, aws:PrincipalOrgID, s3:x-amz-server-side-encryption |
A few rules worth knowing:
- A bucket has one bucket policy, up to 20 KB. When it grows too big because many teams share the bucket, the answer is S3 Access Points (lesson 10.9).
- The Resource in a bucket policy must be this bucket or objects inside it.
Principal: "*"really means everyone, signed or not. Use it only for truly public data, or together with a Condition that narrows it, such as an organization ID.- Only the bucket owner’s account (with s3:PutBucketPolicy permission) can change the policy.
#What bucket policies are used for
The slides and the notes list the jobs a bucket policy does. Each one has its own section below:
- grant public access, for example read-only for a static website;
- grant access to another account (cross-account);
- force HTTPS and force encryption on upload;
- restrict by network: only from certain IP ranges, or only through your VPC endpoint;
- share with a whole organization.
#ACLs, the old way
Before bucket policies existed (2006), S3 had access control lists. An ACL is a list of grants on one bucket or one object: “account X may READ”, “the AllUsers group may READ”. ACLs are coarse (READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL), cannot have conditions, cannot Deny, hold at most 100 grants, and live on every object separately, which makes them very hard to audit. The slides call object ACLs “finer grain” because you can set one per object; that is precisely what makes them hard to keep track of.
Two predefined groups caused many famous leaks: AllUsers (anyone on the internet) and AuthenticatedUsers (anyone with any AWS account in the world, not just your users). S3 treats a grant to either group as public.
Today AWS recommends keeping ACLs disabled and doing everything with policies. New buckets have ACLs disabled by default; the section on Object Ownership explains what that means.
#How S3 decides
#The rules from lesson 2.2, applied to S3
S3 uses the same evaluation logic as the rest of AWS (lesson 2.2), with a few extra inputs. The slides compress it into one sentence: a principal can access an object if the IAM permissions allow it OR the resource policy allows it, AND there is no explicit Deny. That sentence is true inside one account. The full picture:
- Block Public Access first switches off any public grant it is set to block (details later). It never grants.
- Explicit Deny anywhere wins. A Deny in the caller’s IAM policy, the bucket policy, an SCP or RCP, or an endpoint policy ends the evaluation. No Allow can undo it, not even an administrator’s.
- Then S3 needs Allows, and how many depends on who is asking:
| Who is asking | What must allow it |
|---|---|
| A principal in the bucket owner’s account | The IAM policy or the bucket policy (union) |
| A principal in another account | The caller’s IAM policy and the bucket policy (both) |
| Anonymous | The bucket policy (or, with ACLs enabled, an ACL), since there is no IAM side at all |
| Nobody allows it | Implicit deny: 403 AccessDenied |
On top of that, organization SCPs and RCPs, permissions boundaries and session policies can only limit (lesson 2.2), and for objects encrypted with SSE-KMS the caller also needs permission on the KMS key (lesson 10.8).
Who is asking? Four principals, one bucket
The shop’s bucket lives in account 111122223333. An IAM user and an EC2 instance in the same account, an anonymous visitor on the internet, and a user in a partner account all send the same kind of request: read one object. Which policy decides each one?
Scenario
User-based access: the user’s own IAM policy allows it.
Principal
rina
Request
GET invoices/…/inv-1043.pdf
Decided by
…
Result
…
Rina, an IAM user in the shop’s account, asks for invoices/2026/10/inv-1043.pdf. She signs the request with her credentials, so S3 knows exactly who she is.
#Why cross-account needs both sides
The rule “both sides must allow” protects both companies:
- The bucket owner must agree, otherwise anyone could write an IAM policy for someone else’s bucket and read it.
- The caller’s account must agree, otherwise a stranger’s bucket policy could hand your employees permissions your security team never approved.
When a bucket policy names a whole account (arn:aws:iam::444455556666:root), it means “I trust that account to decide which of its users and roles get this”. It does not give every user in that account access automatically. Their administrator still has to grant it with IAM.
Cross-account access: both halves
A user in the courier’s account reads labels/ in the shop’s bucket. Within one account a single Allow is enough; across accounts S3 needs an Allow from the caller’s account and from the bucket owner.
Scenario
Half one, in the caller’s account: the courier’s admin attaches an IAM policy that allows s3:GetObject on arn:aws:s3:::shop-media-example/labels/*. Without it, the courier account has not allowed its own user to go anywhere.
#The 403 that hides a 404
If a principal may not list the bucket (no s3:ListBucket) and asks for a key that does not exist, S3 answers 403 AccessDenied, not 404 Not Found. This is deliberate: otherwise an outsider could probe which file names exist. When you troubleshoot a 403 on a GET, first check that the key really exists. With s3:ListBucket allowed, a missing key gives 404 NoSuchKey.
Self-checkQuestion
A user in account 444455556666 tries to read an object in a bucket owned by account 111122223333. The bucket policy allows s3:GetObject for account 444455556666. The user has no IAM policies at all. What happens?
#The classic access patterns
#Public read for a website
The slides’ first example: an anonymous visitor on the web reads files from a bucket, allowed by a bucket policy. The shop does it for product photos, with the policy shown earlier (Principal: "*", s3:GetObject, photos/*). Three things must be true:
- Block Public Access must not block it, at the bucket and account level (and organization, if one is set).
- The bucket policy grants s3:GetObject to
"*"on the right prefix. Grant read, never write: a public s3:PutObject turns your bucket into free hosting for anyone’s files, on your bill. - The Resource is exact.
photos/*is public;invoices/stays private. Writingshop-media-example/*by mistake is how leaks happen.
Website hosting itself (endpoints, index and error documents, the 403 trap) is lesson 10.3. Today the better design for most websites is a private bucket with CloudFront in front, using Origin Access Control, so the bucket itself never becomes public; that comes in chapter 11.
#An application on EC2
Covered above: an IAM role on the instance with an IAM policy for exactly the prefixes it needs. Same account, so no bucket policy is required. Add a bucket-policy Deny only if you want a guard rail (for example “only through our VPC endpoint”).
#Another account: two patterns
The slides show cross-account access with a bucket policy. In practice there are two ways, and the exam expects you to know both:
| Bucket policy + IAM policy | Role in the bucket’s account | |
|---|---|---|
| How | The bucket policy names the other account (or role); its admin grants its users with IAM | The bucket owner creates a role that the other account may assume (lesson 2.4) |
| The caller acts as | Itself, from its own account | A role session of the bucket owner’s account |
| Policies needed | Both sides | Trust policy + role’s permissions; no bucket policy |
| Good for | Services in the other account that cannot switch roles; many buckets shared to one partner | Keeping all permissions, audit and session limits in the owner’s account |
In both patterns the owner should grant the smallest set: one prefix, read-only, maybe with a condition.
#A whole organization
Listing 40 account IDs in a bucket policy, and updating it every time an account is added, does not scale. Use the condition key aws:PrincipalOrgID:
{
"Sid": "AllAccountsInOurOrganization",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shop-shared-config-example/*",
"Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-a1b2c3d4e5" } }
}
Principal: "*" with a fixed organization ID is not public in S3’s eyes, so Block Public Access does not stop it. Every account in the organization, including accounts added next year, can read, provided its own IAM policies allow it too.
#Guard rails: Deny statements in a bucket policy
A bucket policy can take access away as well as give it. Because an explicit Deny beats every Allow, a few Deny statements turn the bucket into its own security guard, whatever IAM policies people write later.
#HTTPS only (encryption in transit)
The notes’ main exam trick:
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::shop-media-example",
"arn:aws:s3:::shop-media-example/*"
],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
aws:SecureTransport is true when the request arrived over TLS (HTTPS) and false for plain HTTP. The Deny catches every plain-HTTP request, from anyone. Lesson 10.8 covers TLS and S3 encryption in depth.
#Only from our network
aws:SourceVpce: only requests that came through a specific VPC endpoint. The usual way to say “invoices can be read only from inside our VPC” (gateway endpoints are in lesson 10.10).aws:SourceVpcdoes the same for any endpoint in one VPC.aws:SourceIp: only requests from certain public IP ranges, such as the office’s 198.51.100.0/24. It does not work for traffic that arrives through a VPC endpoint, because such a request carries no public address for it to match; use the endpoint keys there.
Write these as Deny … unless (StringNotEquals, NotIpAddress) rather than “Allow only if”: an Allow with a condition does nothing against someone whose IAM policy already allows s3:*, while a Deny stops everyone.
Deny statements as guard rails
A bucket policy can also take access away. The shop adds two Deny statements to its invoices bucket policy: refuse plain HTTP, and refuse requests that do not come through the VPC endpoint. A Deny beats every Allow, even an administrator’s.
Scenario
Deny when aws:SecureTransport is false.
Statement: Effect Deny, Principal "*", Action s3:*, Resource the bucket and its objects, Condition Bool aws:SecureTransport "false". It matches every request that did not use TLS.
#Do not lock yourself out
A Deny with Action: "s3:*" also denies s3:PutBucketPolicy and s3:DeleteBucketPolicy. If the condition excludes the people who maintain the bucket, nobody can fix the policy any more. Two habits prevent it:
- Add an exception for an admin role:
"Condition": {"ArnNotEquals": {"aws:PrincipalArn": "arn:aws:iam::111122223333:role/storage-admin"}}next to the other condition. - If it happens anyway, the account’s root user can always delete the bucket policy. In an AWS Organizations member account without root credentials, the management account can do it as a centrally managed privileged root task.
#Block Public Access
#Why it exists
For years, S3 data leaks made the news: voter rolls, hospital records, defence files, all in buckets someone had made public with one wrong policy or one AllUsers ACL. In November 2018 AWS added S3 Block Public Access: settings that override policies and ACLs so that public access cannot happen, however the bucket is configured. As the slide says: if you know a bucket should never be public, leave them on.
#The four settings
| Setting | Acts when | What it does |
|---|---|---|
| BlockPublicAcls | Someone writes an ACL | Rejects PutBucketAcl, PutObjectAcl and uploads that carry a public ACL. Existing ACLs are untouched. |
| IgnorePublicAcls | A request is evaluated | S3 ignores every public ACL on the bucket and its objects, as if it were not there. |
| BlockPublicPolicy | Someone writes a bucket (or access point) policy | Rejects s3:PutBucketPolicy if the new policy is public. Existing policies are untouched. |
| RestrictPublicBuckets | A request is evaluated | If the bucket’s policy is public, only AWS service principals and principals of the owner’s own account can use it. Anonymous users and all other accounts are refused. |
Two pairs: one setting in each pair stops new public grants, the other disarms existing ones. The settings are independent, and the console offers one checkbox, “Block all public access”, that turns on all four.
#What counts as public
S3 starts by assuming a bucket policy is public and then looks for a reason it is not. A statement is not public if it grants access only to fixed values of:
- specific principals: an account, a user, a role, a service principal;
aws:PrincipalOrgID(your organization);aws:SourceIpwith a range no wider than /8 for IPv4 (private RFC 1918 ranges are fine);aws:SourceVpc,aws:SourceVpce,aws:SourceArn,aws:SourceAccount,aws:SourceOwner,aws:userid,s3:DataAccessPointArn,s3:DataAccessPointAccount.
So Principal: "*" on its own is public; Principal: "*" with aws:SourceVpce = vpce-1a2b3c4d is not. An ACL is public if it grants anything to AllUsers or AuthenticatedUsers.
#Levels and precedence
You can set Block Public Access at four levels:
- Access point: set when the access point is created; it cannot be changed later.
- Bucket: applies to the bucket and its access points.
- Account: applies to every bucket and access point the account owns, now and in the future, in all Regions (it can take a little time to reach every Region).
- Organization (new, November 2025): an AWS Organizations policy that turns on all four settings for chosen accounts or OUs, all or nothing, and new member accounts inherit it.
S3 always applies the most restrictive combination. If the account says “block” and the bucket says “allow public”, the bucket is blocked. To publish a public website you must allow it at every level that applies. AWS recommends turning all four on at the account level, and keeping the few public buckets you really need in a separate account.
Defaults: since April 2023 all four settings are on for every new bucket (console, CLI, SDK and CloudFormation alike). Existing buckets were not changed. The account-level settings are not turned on for you; that is your job.
Block Public Access in action
Four switches that never grant anything: they only stop or ignore public grants. Two act when someone tries to write a public policy or ACL, two act when a request is evaluated.
Scenario
BlockPublicPolicy rejects the write: the policy is never saved.
A new bucket, all four settings on (the default since April 2023). A developer pastes a public-read policy to share one file quickly.
#Object Ownership and ACLs
#The problem it solves
With ACLs, the account that uploads an object owns it, even in your bucket. In the old days, when a partner uploaded a file into your bucket, the file belonged to the partner, and its default ACL gave full control only to the partner. You paid for storing a file you could not read. The workaround was to force every uploader to send the bucket-owner-full-control ACL.
S3 Object Ownership (2020, with the “ACLs disabled” option added in November 2021) fixes this with one bucket setting:
| Setting | ACLs | Who owns new objects |
|---|---|---|
| Bucket owner enforced (default since April 2023) | Disabled: they no longer affect access at all | The bucket owner owns every object, new and existing |
| Bucket owner preferred | Enabled | The bucket owner, if the upload carries bucket-owner-full-control; otherwise the writer |
| Object writer | Enabled | The uploading account (the original behaviour) |
With Bucket owner enforced:
- policies alone decide access: IAM, bucket policies, access point policies, endpoint policies, SCPs, RCPs;
- uploads with no ACL, or with
bucket-owner-full-control, are accepted; any other ACL fails with 400 AccessControlListNotSupported; - reading ACLs still works (you see full control for the owner only), and you can re-enable ACLs later, which restores the old ACLs.
Before switching an old bucket, move any access that ACLs still give (for example a public-read ACL on some files, or log delivery to the bucket) into the bucket policy, otherwise that access stops. AWS recommends keeping ACLs disabled except in the rare case where you must control access for each object individually. A few older features still require ACLs on the target bucket, for example CloudFront’s legacy standard logging; you will meet that in chapter 11.
Who owns an uploaded object?
The courier’s system uploads delivery proofs into the shop’s bucket. With the old Object writer setting the courier would own those files; with Bucket owner enforced, the default since April 2023, the shop owns everything and ACLs are switched off.
Scenario
The uploader owns the object, and the bucket owner cannot read it.
An old bucket uses Object Ownership “Object writer”. The bucket policy lets the courier account upload to proofs/. The courier uploads a photo without any ACL.
#Finding exposed buckets: IAM Access Analyzer for S3
Policies, ACLs, access points and settings interact, and people make mistakes. IAM Access Analyzer for S3 shows you, in the S3 console, every general purpose bucket that can be reached from outside your account:
- Public access findings: anyone on the internet can reach the bucket.
- Cross-account access findings: other AWS accounts can, including accounts outside your organization.
For each bucket it shows how it is shared (bucket policy, bucket ACL, access point policy, Multi-Region Access Point policy) and the access level (list, read, write, permissions, tagging). From there you can:
- block all public access to a bucket with one click;
- archive a finding that is intended (the public website, the courier share), which records that someone reviewed it; archived buckets stay listed so you always know they are shared;
- download a CSV report for auditors.
It costs nothing extra, but it needs an external access analyzer created in IAM Access Analyzer in each Region where you have buckets (with the account as the zone of trust). New or changed bucket policies and ACLs show up in findings within about 30 minutes; account-level setting changes can take up to 6 hours.
#Try the decision yourself
Pick a scenario, then change one switch at a time: the caller, the IAM policy, the bucket policy, HTTPS, ACLs and the four Block Public Access settings. Watch which check decides.
Will S3 return the file? Decide it yourself
One GET for one object in shop-media-example (account 111122223333). Pick a scenario, or set every switch yourself, and watch which check decides and why.
1. Block Public Access
no public grant to act on
2. Any Deny?
no Deny anywhere
3. Bucket side
no Allow for this caller
4. Caller side
IAM policy allows
5. Combine
same account: either is enough
Decision: Allowed: 200 OK
The web app’s role has an IAM policy that allows s3:GetObject. Same account, so this Allow alone is enough; the bucket policy does not need to mention the role. The caller is in the bucket owner’s account, so the bucket side and the caller side are a union: one Allow, and no Deny, is enough.
#What goes wrong: a 403 checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| Website or public link returns 403 | Block Public Access on at bucket or account level, or no public-read policy, or the policy’s Resource misses the prefix | Allow at every level, fix the Resource; or use CloudFront |
Can read files, aws s3 ls gives AccessDenied | Policy has only bucket/*, no s3:ListBucket on the bucket ARN | Add s3:ListBucket on arn:aws:s3:::bucket |
| Can list, cannot read | Policy has only the bucket ARN | Add the object ARN bucket/* |
| GET of a missing key gives 403, not 404 | No s3:ListBucket, so S3 hides whether the key exists | Check the key; grant s3:ListBucket if appropriate |
| Partner gets 403 | Missing one half: caller’s IAM policy or the bucket policy | Both sides must allow |
| Partner lost access after a security change | RestrictPublicBuckets turned on and the bucket policy has a public statement | Remove the public statement; name the partner |
| Everyone, even admins, gets 403 | An explicit Deny matches: HTTP instead of HTTPS, wrong VPC endpoint, IP condition | Fix the client, or add an exception with aws:PrincipalArn |
| Owner cannot read objects a partner uploaded | Object Ownership is Object writer, partner owns the objects | Bucket owner enforced |
| Uploads fail with AccessControlListNotSupported | ACLs disabled, client sends an ACL | Remove the ACL or send bucket-owner-full-control |
| 403 only on some objects, all SSE-KMS | Caller has no kms:Decrypt on the key | Grant the key (lesson 10.8) |
#Access control in system design
- Private by default, public by exception. Keep Block Public Access on at the account level everywhere; put the few truly public buckets in a separate account, or better, serve them through CloudFront from a private bucket.
- Least privilege by prefix. Design key prefixes around who needs what (
photos/,invoices/,labels/) so policies can grant exactly one prefix. - Roles for workloads. EC2, Lambda and containers get IAM roles; no long-term keys anywhere.
- Bucket policy as the guard rail. Keep the Allows mostly in IAM, and use the bucket policy for what must hold no matter what IAM says: HTTPS only, only through the VPC endpoint, only the organization.
- Cross-account by name. Name the partner’s account or role; never use
Principal: "*"to share with one partner. - ACLs off. Bucket owner enforced everywhere; move any remaining ACL grants into policies.
- Watch continuously. IAM Access Analyzer findings, plus the AWS Security Hub control that checks Block Public Access, catch the mistakes people will make anyway.
- Temporary links instead of public objects. For “let one customer download one invoice”, use a pre-signed URL (lesson 10.9 and lesson 10.12), not a public object.
#Try it yourself
The commands use the shop’s made-up bucket shop-media-example in account 111122223333.
# 1. See the four Block Public Access settings: bucket level, then account level
aws s3api get-public-access-block --bucket shop-media-example
aws s3control get-public-access-block --account-id 111122223333
# 2. Turn all four on for the whole account (recommended)
aws s3control put-public-access-block --account-id 111122223333 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# 3. Object Ownership: check it, and disable ACLs on an old bucket
aws s3api get-bucket-ownership-controls --bucket shop-media-example
aws s3api put-bucket-ownership-controls --bucket shop-media-example \
--ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'
# 4. Save a bucket policy from a file, read it back, and ask S3 whether it is public
aws s3api put-bucket-policy --bucket shop-media-example --policy file://policy.json
aws s3api get-bucket-policy --bucket shop-media-example --query Policy --output text
aws s3api get-bucket-policy-status --bucket shop-media-example
# -> "PolicyStatus": { "IsPublic": false }
# 5. Test the HTTPS-only rule: the first fails with 403, the second works
curl -s -o /dev/null -w "%{http_code}\n" http://shop-media-example.s3.ap-southeast-1.amazonaws.com/photos/saree-red.jpg
curl -s -o /dev/null -w "%{http_code}\n" https://shop-media-example.s3.ap-southeast-1.amazonaws.com/photos/saree-red.jpg
# 6. Simulate an IAM principal against S3 before you change anything
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/shop-web-role \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::shop-media-example/invoices/2026/10/inv-1043.pdf
# 7. IAM Access Analyzer: create the analyzer once per Region, then list S3 findings
aws accessanalyzer create-analyzer --analyzer-name account-external --type ACCOUNT
aws accessanalyzer list-findings-v2 \
--analyzer-arn arn:aws:access-analyzer:ap-southeast-1:111122223333:analyzer/account-external \
--filter '{"resourceType": {"eq": ["AWS::S3::Bucket"]}}'
The policy.json for step 4 can be any of the policies in this lesson. Try saving the public-read policy while BlockPublicPolicy is on: the call fails with AccessDenied, and that is the setting doing its job.
#Summary
Key takeaways
- Every bucket starts private. Since April 2023 new buckets also have Block Public Access on and ACLs disabled (Bucket owner enforced).
- User-based: IAM policies on users, groups and roles; applications on EC2 get an IAM role, never keys. Resource-based: the bucket policy (JSON with a Principal, one per bucket, 20 KB), and legacy ACLs.
- Bucket-level actions (s3:ListBucket) use
arn:aws:s3:::bucket; object actions (s3:GetObject) usearn:aws:s3:::bucket/*. - Explicit Deny wins everywhere. Same account: IAM or bucket policy. Cross-account: both the caller’s IAM policy and the bucket policy. Anonymous: only the bucket can allow.
- Bucket policy jobs: public read (
Principal: "*", read only, exact prefix), cross-account, organization (aws:PrincipalOrgID), HTTPS only (Deny whenaws:SecureTransportis false: in transit), forcing an encryption type on upload (at rest, lesson 10.8), network limits (aws:SourceVpce,aws:SourceIp). - Block Public Access never grants: BlockPublicAcls and BlockPublicPolicy reject new public grants; IgnorePublicAcls and RestrictPublicBuckets disarm existing ones (the latter also blocks other accounts when the policy is public). Levels: access point, bucket, account, organization (since November 2025); the most restrictive wins.
- Object Ownership: Bucket owner enforced (default, ACLs off, owner owns everything), Bucket owner preferred, Object writer. Uploads with other ACLs fail with AccessControlListNotSupported.
- IAM Access Analyzer for S3 lists public and cross-account buckets, blocks public access in one click, archives intended findings; needs an analyzer per Region.
Answer on your own first, then read the explanation, even when you got it right.
Self-checkQuestion
Which element must a bucket policy statement have that an IAM identity-based policy statement does not?
Self-checkQuestion
A policy allows s3:GetObject and s3:ListBucket, both on the resource arn:aws:s3:::reports-example/*. The user can download a file whose exact name she knows, but aws s3 ls s3://reports-example fails. Why?
Self-checkQuestion
An application on EC2 instances must upload files to an S3 bucket in the same account. What is the most secure way to give it access?
Self-checkQuestion
Inside one account, a user has no IAM policy that mentions S3, but the bucket policy allows her ARN to call s3:GetObject. Nothing denies it. What happens?
Self-checkQuestion
Which two statements about cross-account access to an S3 bucket using a bucket policy are true?
Choose 2
Self-checkQuestion
An administrator in account A writes an IAM policy that allows s3:* on arn:aws:s3:::partner-data-example/*, a bucket owned by account B. Account B has no bucket policy. What can A’s users do?
Self-checkQuestion
A company with 60 AWS accounts in AWS Organizations wants every account, including ones created later, to read a shared configuration bucket, without listing account IDs. What should the bucket policy use?
Self-checkQuestion
A security team must make sure that every request to a bucket uses HTTPS, including requests from administrators. Which bucket policy statement achieves this?
Self-checkQuestion
Study notes say: “Prevent unencrypted uploads → add aws:SecureTransport to the bucket policy.” What is accurate?
Self-checkQuestion
Invoices may only be read by workloads inside the company’s VPC, which reaches S3 through gateway endpoint vpce-1a2b3c4d. Which condition should a Deny statement in the bucket policy use?
Self-checkQuestion
An engineer adds a bucket policy statement: Deny s3:* for Principal * unless aws:SourceVpce equals the VPC endpoint. Now nobody can edit the bucket policy from the console. What is the way out, and how should the statement have been written?
Self-checkQuestion
Which Block Public Access setting makes S3 reject a PutBucketPolicy call whose policy grants access to Principal *?
Self-checkQuestion
An old bucket has a public-read bucket policy. An administrator turns on only BlockPublicPolicy for the account. Can anonymous visitors still read objects?
Self-checkQuestion
Which two settings take effect on requests against public grants that already exist, rather than on attempts to create new ones?
Choose 2
Self-checkQuestion
A bucket policy has three statements: allow CloudTrail’s service principal to write logs, allow partner account 444455556666 to read reports/*, and allow Principal * to read public/*. RestrictPublicBuckets is turned on. Who can still use the policy’s grants?
Self-checkQuestion
A team turns off all four Block Public Access settings on a bucket to publish a static website, saves a public-read policy, and gets AccessDenied. The bucket settings are definitely off. What is the most likely reason?
Self-checkQuestion
Which two statements about Block Public Access levels and defaults are true?
Choose 2
Self-checkQuestion
Which of these bucket policy statements does S3 consider NOT public?
Self-checkQuestion
An object ACL grants read to the AuthenticatedUsers group. A developer says this is safe because only users of our account are authenticated. What is true?
Self-checkQuestion
A logistics partner uploads files into your bucket from its own account. The bucket uses the Object writer setting. Your administrators get AccessDenied when reading the files. What is the simplest lasting fix?
Self-checkQuestion
After a bucket is switched to Bucket owner enforced, an old upload tool fails with HTTP 400 and the code AccessControlListNotSupported. What does the tool do?
Self-checkQuestion
Which two statements about S3 Object Ownership are true?
Choose 2
Self-checkQuestion
A security team wants a list of every S3 bucket in the account that is reachable by the public or by other AWS accounts, showing whether access comes from a bucket policy, an ACL or an access point. Which tool fits best?
Self-checkQuestion
Which two statements about IAM Access Analyzer for S3 are true?
Choose 2
Self-checkQuestion
A static website bucket has the correct public-read policy and Block Public Access is off everywhere. Visitors get photos, but a request for photos/missing.jpg returns 403 instead of 404. Why?
Self-checkQuestion
A courier company needs to read labels/ in your bucket. You prefer that all of its access is governed and audited in your own account, with sessions that expire. Which pattern fits?
Self-checkQuestion
An IAM user in the bucket owner’s account has AdministratorAccess. The bucket policy contains: Deny s3:GetObject on invoices/* for Principal * when aws:SourceIp is not 198.51.100.0/24. The admin is working from home at 203.0.113.40 over HTTPS. Can she read an invoice?
Self-checkQuestion
An anonymous visitor requests an object. The bucket has no bucket policy, ACLs are disabled, and Block Public Access is off everywhere. What happens?
Self-checkQuestion
Which tool on the list does NOT grant access to S3 data on its own?
Self-checkQuestion
A company wants to guarantee that no account in its AWS Organization can ever make an S3 bucket public, including accounts created next year, and that account administrators cannot undo it. What is the best approach?