Amazon S3 permissions decide which principal can perform an action on a bucket or object. Encryption protects stored data, but it does not grant access. For a request to succeed, the relevant policies must allow it, and no applicable policy can explicitly deny it.
This guide explains how identity and resource policies fit together, how to scope bucket listing separately from object reads, and how to share data without relying on public access or legacy ACLs.
S3 access control building blocks
A request includes a principal, an action such as s3:GetObject, a resource, and request context such as network location or encryption headers. IAM evaluates the policies that apply to that request. S3's access-control overview describes the policy types and how they apply to buckets and objects.
Identity and resource-based policies
- Identity-based policies attach to an IAM user, group, or role. They describe what that identity may do, such as read objects in a named bucket.
- Bucket policies attach to a bucket. They can grant or deny access to named principals, including principals in another account, and can add conditions such as an allowed network path.
- Access point policies attach to an S3 Access Point and govern requests made through that endpoint. They work with the underlying bucket's policy; an access point does not bypass bucket permissions.
For requests within one account, an applicable allow can grant access, subject to other policy controls. AWS starts with an implicit deny, and an applicable explicit deny overrides any allow. Permissions boundaries, session policies, service control policies (SCPs), and resource control policies (RCPs) can also limit the effective permissions of a request. See AWS's policy evaluation logic.
Object Ownership and ACLs
For new general purpose buckets, S3 defaults to Bucket owner enforced Object Ownership. This setting disables ACLs and makes the bucket owner own every object in the bucket, including objects uploaded by another account. Access is then managed with IAM and resource policies.
AWS recommends keeping ACLs disabled for most use cases. Consider enabling them only when a workload specifically depends on per-object ACLs or an older integration requires them. Before changing an existing bucket to Bucket owner enforced, check whether clients send ACL headers or call ACL APIs; those requests may need to be updated. Read AWS's guide to S3 Object Ownership and disabling ACLs.
Write least-privilege S3 policies
Grant only the actions the workload needs, on only the bucket or object keys it needs. S3 uses different resource ARNs for bucket operations and object operations: s3:ListBucket applies to a bucket ARN, while s3:GetObject applies to object ARNs.
This identity policy lets an application list and read objects under reports/team-a/ in one bucket. Attach it to the IAM role used by that application, replacing the example bucket name if you adapt it:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListTeamAReports",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::example-reports",
"Condition": {
"StringLike": {
"s3:prefix": [
"reports/team-a/*"
]
}
}
},
{
"Sid": "ReadTeamAReportObjects",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-reports/reports/team-a/*"
}
]
}
The list condition limits list requests to the prefix; the object ARN separately limits reads. S3 prefixes are part of object key names, not physical folders, so use both controls when an application needs to list and read a subset of keys. AWS explains the S3 action-to-resource mapping and provides policy examples. A console workflow may also need supporting permissions such as s3:GetBucketLocation; add those only if that workflow requires them.
Before attaching a policy, use IAM Access Analyzer policy validation to catch malformed policy elements and review security warnings. To review whether general purpose buckets are shared publicly or with other AWS accounts, use IAM Access Analyzer for S3. These checks help review policy structure and external sharing; they do not decide which access your application requires.
Share S3 data across accounts
For direct cross-account access, authorization must be allowed on both sides: the requesting account must permit its user or role to make the S3 request, and the bucket owner's policy must allow that external principal. Other applicable controls, including organization policies and an SSE-KMS key policy, can still deny the request. See AWS's guidance on cross-account resource access.
A common alternative is for the bucket-owning account to create a role with the required S3 permissions and trust the external account. A user or workload in the external account assumes that role and receives temporary credentials. This centralizes the resource permissions in the account that owns the data. Scope the trust policy to the intended account or principals, and scope the role permissions to the required bucket and keys.
For applications running on EC2, Lambda, ECS, or similar AWS compute, use an IAM role and temporary credentials from the service instead of embedding long-term access keys. The AWS SDK credential providers can obtain and refresh role credentials. AWS describes temporary credentials and common role scenarios.
For an example that attaches a role to EC2 and keeps S3 permissions separate from network connectivity, see our guide to connecting EC2 workloads to S3 with an IAM role.
Keep Block Public Access enabled
S3 Block Public Access has four independent settings that block public access granted through policies or ACLs. New general purpose buckets have all four enabled by default. S3 combines settings across the organization, account, bucket, and access point and applies the most restrictive result. For private application data, keep all four settings on at the account and bucket levels where possible. These settings block public access; they do not grant permissions to an IAM principal or replace least-privilege policies.
Before deliberately allowing public access for a specific use case, confirm that the bucket contains only content intended for the public and review the effective settings at every level. See AWS's documentation for blocking public access to S3.
Use Access Points for separate access paths
An S3 Access Point gives an application or team a named endpoint with its own policy and optional network restrictions. It can make permissions easier to manage when several applications share a bucket. An allow in the access point policy must also be permitted by the underlying bucket policy, either directly or through a bucket policy that delegates access control to the access point. Requests made directly to the bucket are not governed by restrictions that exist only in an access point policy.
Use an access point when distinct endpoints or network controls simplify a real access pattern. For a single workload and bucket, a narrowly scoped IAM policy and bucket policy may be simpler. AWS explains how S3 Access Points work with bucket policies.
Share one object with a presigned URL
A presigned URL lets someone use a specific signed S3 operation, such as downloading one object, without giving them AWS credentials. Treat the URL like a bearer token: anyone who has it can use it until it expires, subject to the signer's permissions and any applicable policies. Set a short expiration that fits the task. A URL cannot grant broader access than the identity that signed it, and it can expire sooner if the signing credentials expire or are revoked.
For an object encrypted with a customer-managed KMS key, the signer also needs the relevant KMS permission. See AWS's guidance on presigned URLs.
Encryption and S3 permissions are separate
Server-side encryption protects object data at rest. It does not decide who may call S3 APIs, make an object private, or override an IAM or bucket-policy denial. S3 authorization and encryption-key authorization are separate checks.
When an object uses SSE-KMS, the caller needs the required S3 permission and permission to use the KMS key. For example, reading an SSE-KMS object requires s3:GetObject and kms:Decrypt; writing one with SSE-KMS requires s3:PutObject and kms:GenerateDataKey. The KMS key policy must also allow the principal or delegate access appropriately. AWS lists the permissions for SSE-KMS requests.
Troubleshoot an S3 Access Denied error
Check the failing request in this order:
- Confirm which credentials the application used, including the role session or assumed role.
- Identify the exact S3 action and resource. A list request needs
s3:ListBucket on the bucket ARN; an object read needs s3:GetObject on the object ARN.
- Check the identity policy and the bucket or access point policy. For cross-account access, confirm that both accounts allow the request, or that the caller can assume the intended role.
- Search applicable policies for explicit denies and conditions involving prefixes, principals, TLS, source VPCs, or organization membership. Check permissions boundaries, session policies, and AWS Organizations controls too.
- If the object uses SSE-KMS, check the KMS key's region and permissions. If the request uses a VPC endpoint, review its endpoint policy as well.
- Keep Block Public Access enabled for private data; it does not cause a signed-in principal to receive permissions.
Compare the actual request with the policy that is supposed to allow it. This is often faster than adding broader access, and it keeps the repair limited to the missing action or resource.
Build a clear S3 permission boundary
For most S3 workloads, use IAM roles with temporary credentials, keep ACLs disabled with Bucket owner enforced, scope bucket listing and object actions to their correct resource ARNs, and keep Block Public Access enabled. Use bucket policies for resource-side controls and cross-account sharing, Access Points when separate access paths help, and presigned URLs for brief access to individual objects. When SSE-KMS is involved, authorize both the S3 operation and the KMS key use.