← Blog
A green paper block passing through a narrow opening in a cream arch on a pale lavender background.
Published on
2026-10-30

S3 bucket policy: examples, IAM differences, and access checks

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.

Bucket policy vs. IAM policy: where the permission lives

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.

Read the policy elements before copying an example

  • Version: the policy-language version. The value 2012-10-17 is not the date you wrote the document.
  • Statement: one or more permission statements. A Sid is an optional label that helps reviewers identify a statement.
  • Effect: Allow or Deny.
  • Principal: the role, account, service, or other principal affected by a resource-policy statement.
  • Action: the permission being evaluated, such as s3:ListBucket or s3:GetObject.
  • Resource: the Amazon Resource Name, or ARN, of the bucket or objects.
  • Condition: an additional test, such as a required listing prefix.

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.

Example: let one role list and read a report prefix

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.

A limited Allow is not a complete access boundary

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.

Keep public access and object ownership separate

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.

Validate the proposed policy before applying it

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.

Test allowed and denied requests with the intended identity

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.

Diagnose AccessDenied without widening access blindly

Start with the failing request and work outward:

  1. Confirm the account, role session, bucket, object key, and requested operation.
  2. Check that the action matches the resource ARN. A listing request and a download require different permissions.
  3. Check conditions, including the exact prefix and any network or transport restrictions.
  4. Look for an explicit Deny or a restriction from a permissions boundary, session policy, organization policy, access point, or VPC endpoint.
  5. For cross-account requests, check both accounts' permission grants.
  6. For KMS-encrypted objects, check the KMS key permissions as well as S3 permissions.

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.

Keep policy changes reviewable

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.

Your next workload belongs on Hivenet.

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.