
An S3 bucket policy is a JSON document attached to an Amazon S3 bucket. It identifies who can perform which actions on the bucket or its objects, with optional conditions. To give an application access to a report prefix, separate the permission to list object names from the permission to read their contents.
This guide explains bucket policies versus IAM identity policies, shows a scoped read example, and walks through validation and access checks. It covers Amazon S3 general purpose buckets. Other bucket types and S3-compatible services can have different authorization rules.
A bucket policy is a resource-based policy: it is attached to the bucket and includes a Principal identifying the recipient of a permission. An identity-based IAM policy is attached to a user, group, or role. Its statements describe that identity's permissions, so they do not include a Principal element.
For a same-account request, an applicable Allow can come from an identity policy or a resource policy. Adding a narrow bucket-policy Allow does not erase a broader grant elsewhere. Explicit Deny statements and other applicable controls can still block access. AWS documents these interactions in its policy evaluation logic.
Cross-account access adds another requirement: the resource-owning account must permit access, and the requesting identity's account must permit the requested actions. Review both sides, along with role trust and any session restrictions. A bucket policy does not itself let someone assume a role.
If you are still choosing between object, block, and file storage, start with our Amazon cloud storage guide. The policy examples here apply to S3 object access.
A bucket ARN ends at the bucket name. An object ARN continues with an object key or pattern. Putting an object-level action against only a bucket ARN is a common mistake. See AWS's policy elements reference for the action and resource distinction.
Assume an application role named ReportReader needs to list and download current objects under reports/. It does not need to upload files, delete objects, change policies, or retrieve specific historical versions. The example below grants that limited set of permissions.
Replace the account number, role ARN, and bucket name with your own values. The role must exist. Start with a separate test bucket containing harmless files and keep Block Public Access enabled. Save the adapted document as bucket-policy.json.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListReportPrefix",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/ReportReader"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::amzn-s3-demo-reports",
"Condition": {
"StringLike": {
"s3:prefix": "reports/*"
}
}
},
{
"Sid": "ReadReportObjects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/ReportReader"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-reports/reports/*"
}
]
}
The first statement permits ListObjectsV2 requests through s3:ListBucket on the bucket ARN. Its StringLike condition requires a request prefix matching reports/*. The second statement permits s3:GetObject on object keys beginning with reports/.
These are two separate checks. A listing condition does not restrict GetObject by itself. The object ARN does that work for this grant. S3 prefixes are parts of object keys; the folder display in the console does not create an independent permission boundary. AWS's prefix condition examples explain this distinction.
The sample grants neither s3:GetObjectVersion nor s3:ListBucketVersions. A request for a specific version needs the corresponding permission; do not add version access unless the application needs it. See the GetObject permission rules.
This example adds access to reports/. It does not prove that ReportReader cannot access finance/, because another policy might already allow it. Review the role's identity policies, bucket and access-point policies, session settings, and applicable organization or endpoint controls.
If the requirement is an enforced restriction, design and review the complete permission set. An explicitly scoped Deny can override another Allow, but a broad Deny can also break administrative or service access. Specify the principal, action, resource, and exception you intend before adding one. Do not turn a narrow application grant into a bucket-wide deny template.
Likewise, a missing PutObject or DeleteObject action in this document means this document does not grant it. It is not evidence that the role lacks that action everywhere else.
A specific role can receive private access while Block Public Access stays enabled. Do not disable that protection simply because a request returns AccessDenied. A public Allow with a wildcard principal can expose data or be rejected by public-access controls. A wildcard used in a Deny has a different effect, so review the entire statement.
S3 Block Public Access considers the applicable bucket, account, access-point, and organization settings. Changing one bucket setting may not change the effective result.
Also check Object Ownership. New buckets use Bucket owner enforced by default, with ACLs disabled. Older buckets can have different ownership and ACL arrangements. Verify the actual setting instead of assuming every object follows a new-bucket default.
Keep a copy of the existing bucket policy and prepare a reviewed replacement. Saving a new bucket policy replaces the current document, so preserve unrelated statements that the application or administrators still need.
With an authorized reviewer profile and AWS CLI v2 configured, you can request IAM Access Analyzer policy validation:
aws accessanalyzer validate-policy --policy-document file://bucket-policy.json --policy-type RESOURCE_POLICY --validate-policy-resource-type AWS::S3::Bucket --region us-east-1 --profile policy-review
This sends the local policy document to AWS for validation; it does not attach it to the bucket. The profile and Region are examples. Review every returned finding and resolve relevant errors and security warnings. Successful JSON parsing or an empty findings list does not establish that the intended application works, or that every unintended request is blocked.
Apply the reviewed document to the test bucket through your normal authorized change process. This article's example was checked locally for JSON structure and documented action/resource usage. It was not deployed to an AWS account.
Use a dedicated test role without unrelated S3 grants. Have an administrator place known files at reports/sample.txt and finance/sample.txt, and confirm that both exist. The reports-reader profile below must assume that role; the policy's Principal remains the IAM role ARN, even though the caller identity shows an assumed-role session ARN.
First confirm the identity and test the intended read path:
aws sts get-caller-identity --profile reports-reader
aws s3api list-objects-v2 --bucket amzn-s3-demo-reports --prefix reports/ --max-keys 5 --no-paginate --profile reports-reader
aws s3api get-object --bucket amzn-s3-demo-reports --key reports/sample.txt --profile reports-reader report-download.txt
The listing should succeed and the downloaded report should match the fixture. These expectations assume the policy is installed and no other control blocks the request. Use a new local directory so the download filename does not overwrite work you need.
Then check the other prefix and anonymous access:
aws s3api list-objects-v2 --bucket amzn-s3-demo-reports --prefix finance/ --max-keys 5 --no-paginate --profile reports-reader
aws s3api get-object --bucket amzn-s3-demo-reports --key finance/sample.txt --profile reports-reader finance-download.txt
aws s3api get-object --bucket amzn-s3-demo-reports --key reports/sample.txt --no-sign-request anonymous-download.txt
All three requests should be denied in this controlled setup. If the other prefix is readable, investigate other grants instead of assuming this Allow acted as a restriction. If the anonymous request succeeds, inspect the bucket's public-access configuration immediately. A failure caused by an expired session, wrong bucket, or absent object is not a valid permissions test.
Keep the caller identity, requested action and key, response, and policy revision with the test record. If the application also needs writes, add and test that requirement separately using disposable objects. Do not broaden the policy to s3:* just to make a test pass.
Start with the failing request and work outward:
Reading an SSE-KMS object requires the appropriate KMS authorization, including kms:Decrypt. A bucket policy cannot grant permissions on a KMS key. AWS's 403 troubleshooting guide covers the other controls that can deny a request.
For a browser application, CORS configuration is a separate check. A CORS rule does not replace IAM or bucket permissions. Our application storage guide places these permissions in the wider design of uploads, downloads, and recovery.
Store the reviewed policy and its test cases with the application configuration. Repeat the negative checks when you add a role, prefix, access point, or external account. A successful download proves one allowed path; it does not establish who else can read the bucket.
Finally, separate access control from retention. A bucket-policy deny does not stop an S3 Lifecycle rule from expiring or transitioning objects. AWS calls this out in its bucket-policy documentation. Review retention, versioning, and recovery settings alongside permissions when the data must remain recoverable.
Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.