This chapter dives deep into S3 bucket policies versus Access Control Lists (ACLs), two fundamental mechanisms for securing Amazon S3 resources. Understanding the difference is critical for the SAA-C03 exam, where questions on S3 security appear in roughly 15–20% of all questions. You will learn the exact evaluation order, when to use each, and common exam traps. By the end, you will be able to confidently choose the right tool for any access control scenario.
Jump to a section
Imagine a corporate office building with multiple tenants. Each tenant occupies a different floor. The building has a main entrance with a security guard who checks a master list of who is allowed to enter the building (this is the bucket policy — a central list of permissions for everyone accessing the building). Additionally, each individual office door inside the building has its own lock that can be opened only with a specific key (this is an ACL — a per-object access control). The security guard at the main entrance can grant or deny access to the entire building based on the visitor's identity and the master list. For example, the guard may allow anyone with a company ID to enter, but deny contractors. However, even after entering the building, a visitor might find that a particular office door is locked and requires a separate key (ACL) that they do not have. Conversely, the guard could deny entry to someone even if they have a key to an individual office. The key insight: bucket policies control access at the perimeter (the building entrance), while ACLs control access at the individual resource level (each office door). In AWS S3, bucket policies are evaluated first; if the bucket policy denies access, the request is rejected regardless of ACLs. If the bucket policy allows (or is absent), ACLs are then checked. The ACL can further restrict or allow access, but it cannot override a bucket policy deny. This layered approach lets you set broad rules centrally and fine-tune per object.
What Are S3 Bucket Policies and ACLs?
Amazon S3 provides two primary methods for controlling access to objects: bucket policies and Access Control Lists (ACLs). Both are resource-based policies, meaning they are attached directly to the S3 resource (bucket or object) rather than to a user identity. However, they differ in scope, granularity, and use cases.
Bucket policies are JSON-based policy documents attached to a bucket. They define permissions for the bucket and all objects within it (unless overridden by object ACLs). Bucket policies can grant or deny access to principals (users, roles, accounts, or federated users) for specific actions (e.g., s3:GetObject, s3:PutObject). They support conditions (IP address, VPC, MFA, etc.) and can be used to enforce cross-account access, public access, or complex rules.
ACLs are legacy access control mechanisms that grant basic read/write permissions to AWS accounts or predefined groups (e.g., AllUsers, AuthenticatedUsers). ACLs can be attached to buckets (bucket ACL) or individual objects (object ACL). They offer limited permissions: READ, WRITE, READ_ACP, WRITE_ACP, and FULL_CONTROL. ACLs do not support conditions or deny statements; they are purely allow-based.
How They Work Internally: Evaluation Order
When a request is made to S3, the following evaluation order applies:
User Identity Policy: If the principal is an IAM user/role, their identity-based policy is evaluated.
Bucket Policy: The bucket policy is evaluated. If it contains an explicit deny, the request is denied immediately (regardless of other policies). If it contains an explicit allow, the request is allowed (unless later denied by ACL — but ACL deny is not possible). If the bucket policy is absent or does not match, evaluation continues.
ACLs: Bucket ACL and object ACL (if applicable) are evaluated. If the ACL grants the required permission, the request is allowed. If the ACL does not grant permission (or grants only to a different principal), the request is denied.
Key Rule: The default is implicit deny. An explicit deny in any policy overrides any allow. ACLs cannot deny; they can only allow or remain silent. Therefore, if a bucket policy explicitly denies s3:GetObject, even if the object ACL grants FullControl to the user, the request is denied.
Components and Defaults
Bucket Policy Structure:
Version: Always "2012-10-17"
Statement: Array of statements, each with Effect (Allow/Deny), Principal (AWS account/user/role, or "*"), Action (list of S3 actions), Resource (ARN of bucket and/or objects, e.g., "arn:aws:s3:::mybucket/*"), and optional Condition.
ACL Permissions:
READ: Allows listing objects (bucket ACL) or reading object data and metadata (object ACL).
WRITE: Allows creating, overwriting, and deleting objects (bucket ACL only; object ACL does not have WRITE).
READ_ACP: Allows reading the ACL.
WRITE_ACP: Allows modifying the ACL.
FULL_CONTROL: Grants all four above.
Predefined Groups:
AllUsers: Anyone on the internet.
AuthenticatedUsers: Any AWS account authenticated (but not anonymous).
LogDelivery: Used for S3 server access logging.
Default ACLs: When a bucket or object is created, the default ACL grants FULL_CONTROL to the bucket owner. Bucket ACL defaults to private (owner only). Object ACL defaults to private unless the bucket policy or bucket ACL grants public write.
Configuration and Verification
Bucket Policy via AWS Console:
Navigate to S3 bucket > Permissions > Bucket Policy. Paste JSON.
Bucket Policy via AWS CLI:
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.jsonVerify Bucket Policy:
aws s3api get-bucket-policy --bucket my-bucketBucket ACL via CLI:
aws s3api get-bucket-acl --bucket my-bucket
aws s3api put-bucket-acl --bucket my-bucket --grant-read uri=http://acs.amazonaws.com/groups/global/AllUsersObject ACL via CLI:
aws s3api get-object-acl --bucket my-bucket --key my-object
aws s3api put-object-acl --bucket my-bucket --key my-object --grant-full-control emailaddress=user@example.comNote: ACLs are considered legacy. AWS recommends using bucket policies or IAM policies for most use cases. Since April 2023, new S3 buckets have ACLs disabled by default (unless you explicitly enable them).
Interaction with Related Technologies
IAM Policies: IAM policies control what a user/role can do. They work together with bucket policies: the effective permission is the intersection of all allow policies minus any deny. For example, if an IAM policy allows s3:GetObject on all buckets, but the bucket policy denies access to that user, access is denied. If both allow, access is allowed.
Pre-signed URLs: These generate temporary URLs for specific objects. They do not require bucket policies or ACLs; they use the signer's IAM permissions. However, if the bucket policy denies access to the signer, the pre-signed URL will not work.
S3 Object Ownership: This setting controls whether objects uploaded by other AWS accounts retain their own ACLs. With Object Ownership set to "BucketOwnerEnforced", ACLs are disabled, and bucket policies alone control access. This is the recommended setting for new buckets.
S3 Block Public Access: These are account-level or bucket-level settings that override any bucket policy or ACL that would grant public access. There are four settings: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, and RestrictPublicBuckets. They act as a safety net.
When to Use Which
Use bucket policies when you need:
Cross-account access (grant access to another AWS account).
Conditional access (IP address, VPC, MFA, time).
Complex rules with multiple statements and deny effects.
Centralized management for a bucket and all its objects.
Use ACLs only when:
You need to grant permissions on individual objects (object ACL).
You are using legacy systems that require ACLs.
You need to grant basic permissions to predefined groups (AllUsers for public read).
Important: For new applications, avoid ACLs. Use bucket policies and IAM policies. The exam often tests this recommendation.
Request arrives at S3
A user or application sends an HTTP request to an S3 endpoint. The request includes the action (e.g., GET /mybucket/myobject), authentication credentials (signature or IAM role), and any headers. S3 first authenticates the request, verifying the signature using the secret key associated with the AWS account or IAM user. If authentication fails, S3 returns 403 AccessDenied immediately without evaluating any policies.
Evaluate identity-based policies
If the request is authenticated, S3 determines the principal (the AWS account or IAM role/user making the request). It then evaluates the identity-based policies attached to that principal. This includes IAM user policies, group policies, and role trust policies. If any identity policy contains an explicit deny for the requested action, the request is denied. If it contains an allow, evaluation continues to resource-based policies.
Evaluate bucket policy
S3 retrieves the bucket policy (if any) attached to the target bucket. It checks each statement in order (though order does not matter because explicit deny overrides allow). If a statement matches the principal, action, resource, and conditions, and has Effect: Deny, the request is denied immediately (regardless of ACLs). If a statement has Effect: Allow, the request is allowed provisionally, but ACLs may still deny. If no statement matches, evaluation continues.
Evaluate bucket ACL
If the bucket policy did not result in an explicit deny or allow (or allowed provisionally), S3 checks the bucket ACL. The bucket ACL grants permissions to specific AWS accounts or predefined groups. If the principal is granted the required permission (e.g., READ for listing objects), the request is allowed. If not, the request proceeds to object ACL evaluation. Note: bucket ACL does not control object-level operations like GET or PUT; those are controlled by object ACLs.
Evaluate object ACL
For actions on individual objects (GET, PUT, DELETE, HEAD), S3 checks the object ACL after the bucket ACL. The object ACL specifies permissions for the object. If the principal has the required permission (e.g., READ for GET), the request is allowed. If not, the request is denied. If the bucket policy already allowed the request and the object ACL also allows, the request succeeds. If the object ACL does not grant permission, the request fails even if the bucket policy allowed.
Enterprise Scenario 1: Cross-Account Data Sharing
A large enterprise has multiple AWS accounts: one for production and one for analytics. The analytics team needs read-only access to a specific bucket in the production account. The security team wants to enforce that access is only allowed from the analytics account's VPC. Solution: attach a bucket policy to the production bucket that grants s3:GetObject to the analytics account's root user (or a specific IAM role), with a condition that restricts access to a specific VPC ID. Bucket policies are ideal because they support conditions and cross-account principals. ACLs cannot achieve this. In production, the bucket policy is managed via Infrastructure as Code (Terraform/CloudFormation) and version-controlled. A common mistake is forgetting to add the condition for VPC, which would allow access from anywhere in the analytics account. Performance is not an issue because policy evaluation is fast. Misconfiguration could lead to data exposure if the condition is omitted.
Enterprise Scenario 2: Public Read-Only Website Hosting
A company hosts a static website on S3. They want the entire bucket to be publicly readable (for the website) but not writable. They use a bucket policy that grants s3:GetObject to Principal "*" (everyone) with a condition that the request must come from a specific CloudFront distribution (or no condition for direct S3 access). Alternatively, they could use a bucket ACL granting READ to AllUsers, but bucket policies are more flexible. The recommended approach is to block public ACLs at the account level and use a bucket policy. A common pitfall: enabling public ACLs and forgetting to block public access, leading to accidental public write. The S3 Block Public Access settings are used as a safety net.
Enterprise Scenario 3: Fine-Grained Object-Level Permissions
A media company stores thousands of videos in a single bucket. Each video should be accessible only by the user who uploaded it. Using bucket policies alone would require a separate policy statement per user, which is impractical. Instead, they use pre-signed URLs (which rely on IAM permissions) combined with a bucket policy that denies all access except via pre-signed URLs. Alternatively, they could use object ACLs to grant access to specific users, but this is legacy and hard to manage at scale. The modern approach is to use S3 Object Lambda or S3 Access Points with per-object permissions. The exam tests that ACLs are not recommended for fine-grained access at scale.
SAA-C03 Exam Focus
The SAA-C03 exam tests your ability to choose the correct access control mechanism for a given scenario. The relevant objective is Domain 1: Secure Architectures, Objective 1.5: "Determine how to implement security controls for AWS services." Specifically, you must understand the differences between bucket policies, ACLs, and IAM policies.
Common Wrong Answers and Why
"Use an ACL to grant cross-account access to a bucket." – Wrong. ACLs can grant cross-account access (by specifying the canonical user ID of the other account), but this is legacy and less secure. Bucket policies are preferred because they support conditions and are more flexible. The exam expects you to choose bucket policies for cross-account access.
"ACLs can deny access to specific users." – Wrong. ACLs are purely allow-based; they cannot deny. Only bucket policies and IAM policies can have deny effects. Candidates often confuse ACLs with IAM policies.
"Bucket policies override object ACLs." – Partially true. A bucket policy deny overrides any ACL allow. But a bucket policy allow does not override an object ACL that does not grant permission. The effective permission is the most restrictive combination. Candidates often think bucket policy allow automatically allows access, ignoring object ACLs.
"ACLs are the best way to grant public read access." – Wrong. While you can use an ACL to grant public read, it is not recommended. AWS recommends using bucket policies with a deny-by-default approach and S3 Block Public Access. The exam tests that ACLs are legacy and should be avoided.
Specific Numbers and Terms
Default ACL: grants FULL_CONTROL to bucket owner only.
Predefined groups: AllUsers (URI: http://acs.amazonaws.com/groups/global/AllUsers), AuthenticatedUsers.
ACL permissions: READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL.
Bucket policy Version: "2012-10-17".
S3 Block Public Access settings: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets.
Object Ownership: BucketOwnerPreferred, ObjectWriter, BucketOwnerEnforced.
Edge Cases the Exam Loves
**Bucket policy with Principal "*" and Condition**: The exam may ask how to restrict public access to specific IPs or VPCs. ACLs cannot do this; only bucket policies can.
Object ACLs when bucket policy allows: A bucket policy allows s3:GetObject for user A, but the object ACL grants only READ to user B. User A will be denied because the object ACL does not grant permission. The exam tests that both must allow.
IAM policy + bucket policy: The effective permission is the union of allows minus any deny. The exam may ask what happens when an IAM policy allows s3:GetObject on all buckets but the bucket policy denies access to the user. Answer: denied.
How to Eliminate Wrong Answers
If the question mentions cross-account access, eliminate ACLs unless it's a legacy scenario. Choose bucket policy.
If the question mentions conditions (IP, VPC, MFA), eliminate ACLs.
If the question mentions denying access, eliminate ACLs (they cannot deny).
If the question mentions fine-grained per-object permissions, consider object ACLs only if the scenario is small and legacy; otherwise, consider pre-signed URLs or S3 Access Points.
If the question mentions public access, look for S3 Block Public Access settings and bucket policies, not ACLs.
Bucket policies are JSON documents attached to a bucket; ACLs are legacy access lists attached to buckets or objects.
Bucket policies support conditions (IP, VPC, MFA); ACLs do not.
ACLs cannot deny access; only allow. Bucket policies can explicitly deny.
Evaluation order: identity policy → bucket policy → ACLs. An explicit deny anywhere overrides allows.
For cross-account access, use bucket policies, not ACLs.
Since April 2023, new S3 buckets have ACLs disabled by default. Use bucket policies or IAM policies instead.
S3 Block Public Access settings can override bucket policies and ACLs to prevent public access.
These come up on the exam all the time. Here's how to tell them apart.
S3 Bucket Policy
JSON-based policy document attached to a bucket.
Supports conditions (IP, VPC, MFA, time).
Can explicitly Deny access.
Supports cross-account access with conditions.
Recommended for modern applications.
S3 ACL
Legacy XML-based or API-based access control list.
No support for conditions.
Only allows; cannot deny.
Can grant cross-account access but without conditions.
Legacy; not recommended for new use cases.
Mistake
ACLs can deny access to specific principals.
Correct
ACLs are purely allow-based. They cannot deny. To deny access, you must use a bucket policy or IAM policy with an explicit Deny effect.
Mistake
Bucket policies and ACLs are evaluated independently; the most permissive wins.
Correct
The evaluation follows a strict order: identity policy, then bucket policy, then ACLs. An explicit deny in any policy overrides any allow. The default is implicit deny. The effective permission is the most restrictive combination.
Mistake
Object ACLs can grant WRITE permission.
Correct
Object ACLs do not have a WRITE permission. WRITE is only valid for bucket ACLs. To write an object, you need WRITE on the bucket ACL (or bucket policy). Object ACLs control read, read ACP, write ACP, and full control.
Mistake
ACLs are the recommended way to manage S3 permissions.
Correct
AWS recommends using bucket policies and IAM policies instead of ACLs. Since April 2023, ACLs are disabled by default for new buckets. Use ACLs only for backward compatibility.
Mistake
A bucket policy that allows public read automatically makes all objects publicly readable.
Correct
The bucket policy must be combined with object ACLs that grant read access to the public (or the objects must be owned by the bucket owner with appropriate settings). If object ACLs are private, the bucket policy allow may not be sufficient. However, with Object Ownership set to BucketOwnerEnforced, ACLs are disabled and bucket policy alone controls access.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
A bucket policy is a JSON-based resource policy attached to a bucket that defines permissions for the bucket and its objects. It supports conditions, deny effects, and cross-account access. An ACL (Access Control List) is a legacy mechanism attached to a bucket or object that grants basic read/write permissions to AWS accounts or predefined groups. ACLs do not support conditions or deny. AWS recommends using bucket policies over ACLs.
Yes, you can use both. However, the evaluation order means that if the bucket policy denies access, the request is denied regardless of ACLs. If the bucket policy allows, the ACL is then checked. The ACL must also grant the permission for the request to succeed. AWS recommends disabling ACLs and using only bucket policies.
Object ACLs are useful when you need to grant permissions on individual objects to different AWS accounts or predefined groups, and you cannot use a bucket policy because it applies to all objects. However, this is a legacy pattern. Modern alternatives include pre-signed URLs, S3 Access Points, or S3 Object Lambda. Object ACLs do not support conditions or deny.
Attach a bucket policy with a statement that allows s3:GetObject for Principal "*" (everyone). For example: {"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3:GetObject","Resource":"arn:aws:s3:::your-bucket/*"}]}. Ensure that S3 Block Public Access settings are not blocking public policies.
No, an ACL cannot override a bucket policy deny. If the bucket policy explicitly denies an action, the request is denied regardless of ACLs. However, if the bucket policy allows (or is absent), the ACL can further restrict access. The ACL is evaluated after the bucket policy.
The predefined groups are: AllUsers (anyone on the internet), AuthenticatedUsers (any AWS account authenticated), and LogDelivery (used for S3 server access logging). These are identified by URIs like http://acs.amazonaws.com/groups/global/AllUsers.
S3 Object Ownership determines who owns objects uploaded by other accounts. There are three settings: BucketOwnerPreferred (bucket owner becomes object owner), ObjectWriter (uploader owns object), and BucketOwnerEnforced (ACLs are disabled and bucket policy controls all access). BucketOwnerEnforced is the recommended setting for new buckets.
You've just covered S3 Bucket Policies vs ACLs — now see how well it sticks with free SAA-C03 practice questions. Full explanations included, no account needed.
Done with this chapter?