Skip to content

Managing Permissions in Amazon S3

Access to S3 is decided by policy: identity-based policies attached to IAM principals, resource-based policies attached to buckets and access points, and organisation-wide guardrails evaluated alongside them. ACLs are disabled on every bucket created since April 2023 and are not part of the modern picture.

Bucket policies are resource-based. They live on the bucket, name the principals they apply to, and can allow or deny. They are the only way to grant access to a principal in another account, and the natural home for an explicit deny that must hold whatever else is configured.

IAM policies are identity-based. They attach to users, groups and roles, are managed centrally with the rest of an account’s permissions, and scale better than a bucket policy that grows a statement per consumer. Roles supply temporary credentials that are issued on assumption and refreshed automatically, which is why role-based access is preferred to handing out long-lived keys.

One correction worth making explicitly: IAM user access keys do not rotate themselves. Rotating them is a manual customer responsibility, and unrotated keys are a standard audit finding. What is automatic is the expiry and refresh of the temporary credentials behind an assumed role — a reason to use roles rather than users, not a property of IAM keys.

S3 converts the relevant policies into two evaluation contexts.

  1. User context. If the requester is an IAM principal, it must have permission from its own parent account. S3 evaluates that account’s identity policies. If the parent account also owns the bucket, the bucket policy is evaluated here at the same time, and the bucket context is then skipped.
  2. Bucket context. The requester must also have permission from the account that owns the bucket, granted by the bucket policy.

Some consequences that older material gets backwards:

  • An explicit deny always wins, wherever it appears — bucket policy, identity policy, VPC endpoint policy, service control policy or resource control policy.
  • Within a single account, a bucket policy allow is sufficient on its own — but this is because the same account is the context authority for both the principal and the bucket, not because bucket policies outrank IAM.
  • Across accounts, permission is required from both sides. If a user in account A reads a bucket owned by account B, account A must allow the action in its identity policy and account B must allow it in the bucket policy. Either one missing denies the request.
  • Block Public Access is evaluated on top of all of this and can reject a request the policies would otherwise have allowed.

Two patterns, and the second is usually better.

Bucket policy naming the other account. The bucket owner allows the external principal or account directly in the bucket policy, and the external account grants its own principal the matching permission. Simple, and appropriate for straightforward sharing.

Role assumption. The bucket owner’s account creates an IAM role with the required S3 permissions and a trust policy naming the external account. A principal in that account is granted sts:AssumeRole, assumes the role, and operates with temporary credentials issued inside the bucket owner’s account. Anything that can hold credentials can do this: IAM users, EC2 instances, Lambda functions, on-premises applications through federation.

Role assumption is preferable when access is ongoing or fine-grained. It keeps the permissions in one place, produces temporary credentials rather than standing grants, gives session-level attribution in CloudTrail, and can be revoked by editing one trust policy rather than auditing every bucket.

Traffic from a VPC to S3 can travel two ways.

Over the internet gateway. The default for an instance in a public subnet. Traffic to S3 stays on the AWS network even when routed through the internet gateway, but a private subnet needs a NAT device to reach it, and NAT processing plus data transfer is charged per GB.

Over a gateway VPC endpoint. A gateway endpoint gives a VPC private connectivity to S3 (and DynamoDB) with no internet gateway or NAT device involved, and there is no charge for using one.

How it works matters, because it is commonly described wrongly:

  • A gateway endpoint does not use AWS PrivateLink and creates no elastic network interface. It is a route table entry.
  • Creating one means selecting the route tables for the subnets that should use it. S3 adds a route whose destination is an AWS-managed prefix list for the service and whose target is the endpoint. All instances in subnets associated with those route tables then use it automatically.
  • Security groups and network ACLs still have to allow the traffic — a security group rule can reference the prefix list directly.
  • Endpoint routes are Region-specific. Traffic to S3 in another Region still goes out through the internet gateway.
VPC route table ──[ prefix list for S3 ]──> gateway endpoint ──> S3

An interface endpoint is the other option: that one is PrivateLink, provisions an ENI with a private IP in each chosen subnet, is billed per hour and per GB, and is what to use when the traffic originates on premises over Direct Connect or VPN, since a gateway endpoint only serves the VPC’s own route tables.

Gateway endpoints also make access control sharper: a bucket policy can require aws:SourceVpce to match a specific endpoint, so the bucket is unreachable from anywhere else.

  • Use roles and temporary credentials rather than IAM users with long-lived access keys. Where keys are unavoidable, rotate them on a schedule — nothing does it automatically.
  • Keep per-principal permissions in IAM policies; keep cross-account grants, service grants and hard denies in bucket policies.
  • Use explicit denies for the restrictions that must never be overridden — non-TLS requests, access from outside a named VPC endpoint, deletion of objects under a compliance prefix.
  • Use a gateway endpoint for VPC-to-S3 traffic in the same Region: it removes NAT charges, keeps the traffic off the internet path, and gives a condition key to lock the bucket down with.
  • Review access with IAM Access Analyzer for S3 rather than by reading policies one at a time.