SAABlog
StorageIntermediate

S3 Bucket Policies: How Are They Different from IAM Policies?

Learn the differences between S3 bucket policies and IAM policies, JSON syntax, and cross-account access configuration with practical examples.

PHILOLAMB-Updated: January 31, 2026
S3Bucket PolicyIAMPermissionsCross-Account

Related Exam Domains

  • Domain 1: Design Secure Architectures

Key Takeaway

Bucket policies define "who can access this bucket," while IAM policies define "what this user can do." Use bucket policies for cross-account access or public access, use IAM policies for managing internal user permissions.

Exam Tip

Exam Essential: Bucket policies require Principal (who). IAM policies don't have Principal (the attached user is the implicit principal). For cross-account access, both bucket policy AND target IAM policy must Allow for access.

FeatureBucket PolicyIAM Policy
Attached ToS3 bucket (resource)IAM users/roles/groups
Principal ElementRequired (who accesses)None (implicit from attachment)
Public AccessPossible ("Principal": "*")Not possible
Cross-AccountStandalone possibleRequires role assumption
ScopeSpecific bucket onlyAll resources for the user

Bucket Policy vs IAM Policy: When to Use What?

When to Use Bucket Policy

Bucket Policy Use Cases:
├── Public website hosting (allow all users read)
├── Cross-account access (grant permissions to other AWS accounts)
├── Allow access only from specific IP/VPC
├── CloudFront OAC integration
└── Service Principal access (CloudTrail, ELB logs, etc.)

When to Use IAM Policy

IAM Policy Use Cases:
├── User/role permission management within same account
├── Manage permissions for multiple S3 buckets in one place
├── EC2 instance profile S3 access
├── Lambda function execution role S3 access
└── Fine-grained per-user permission control

Selection Flowchart

Need to set S3 permissions?
│
├── Same account internal users?
│   └── YES → IAM policy (+ bucket policy if needed)
│
├── Access from different AWS account?
│   └── YES → Bucket policy required (+ target IAM policy)
│
├── Need public access?
│   └── YES → Bucket policy required (+ Block Public Access OFF)
│
└── AWS service needs access? (CloudTrail, ELB, etc.)
    └── YES → Bucket policy (use Service Principal)

Bucket Policy JSON Structure

Basic Structure

{
  "Version": "2012-10-17",
  "Id": "ExampleBucketPolicy",
  "Statement": [
    {
      "Sid": "StatementIdentifier",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::bucket-name/*",
      "Condition": {}
    }
  ]
}

Element Descriptions

ElementRequiredDescription
VersionPolicy language version (always use "2012-10-17")
IdPolicy identifier (optional)
StatementPermission rule array (multiple allowed)
SidStatement identifier (for readability)
EffectAllow or Deny
PrincipalPermission target (who)
ActionActions to allow/deny (what)
ResourceTarget resource (where)
ConditionConditional application (under what conditions)

Principal Syntax

Principal Types with Examples

// 1. All users (public)
"Principal": "*"

// 2. Specific AWS account
"Principal": {"AWS": "arn:aws:iam::123456789012:root"}

// 3. Specific IAM user
"Principal": {"AWS": "arn:aws:iam::123456789012:user/username"}

// 4. Specific IAM role
"Principal": {"AWS": "arn:aws:iam::123456789012:role/rolename"}

// 5. AWS service
"Principal": {"Service": "cloudfront.amazonaws.com"}

// 6. Multiple Principals
"Principal": {
  "AWS": [
    "arn:aws:iam::111111111111:root",
    "arn:aws:iam::222222222222:root"
  ]
}

Exam Tip

Exam Point: "Principal": "*" means all users (including anonymous). "Principal": {"AWS": "*"} means all authenticated AWS accounts. They're different!


Action Syntax

Commonly Used S3 Actions

ActionDescriptionApplies To
s3:GetObjectRead objectsObject
s3:PutObjectUpload objectsObject
s3:DeleteObjectDelete objectsObject
s3:ListBucketList objects in bucketBucket
s3:GetBucketLocationGet bucket regionBucket
s3:*All S3 operationsAll

Action and Resource Mapping

// Object-level actions → Resource needs /*
{
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::bucket-name/*"
}

// Bucket-level actions → Resource is bucket only
{
  "Action": "s3:ListBucket",
  "Resource": "arn:aws:s3:::bucket-name"
}

// When both needed → Use Resource array
{
  "Action": ["s3:ListBucket", "s3:GetObject"],
  "Resource": [
    "arn:aws:s3:::bucket-name",
    "arn:aws:s3:::bucket-name/*"
  ]
}

Exam Tip

Exam Trap: s3:ListBucket applies to the bucket, s3:GetObject applies to objects. Wrong Resource ARN = permissions won't work!


Practical Bucket Policy Examples

1. Static Website Public Read

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-website-bucket/*"
    }
  ]
}

2. Cross-Account Access

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CrossAccountAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:root"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::shared-bucket",
        "arn:aws:s3:::shared-bucket/*"
      ]
    }
  ]
}

3. Allow Only Specific IP Range

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "IPAllow",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::secure-bucket",
        "arn:aws:s3:::secure-bucket/*"
      ],
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": ["192.168.0.0/24", "10.0.0.0/8"]
        }
      }
    }
  ]
}

4. Allow Only VPC Endpoint Access

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VPCEndpointOnly",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::private-bucket",
        "arn:aws:s3:::private-bucket/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "vpce-1234567890abcdef0"
        }
      }
    }
  ]
}

5. CloudFront OAC Integration

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOAC",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::cdn-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE"
        }
      }
    }
  ]
}

6. Enforce HTTPS (Block HTTP)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyHTTP",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::secure-bucket",
        "arn:aws:s3:::secure-bucket/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

Condition Keys

Commonly Used Condition Keys

Condition KeyPurposeExample Value
aws:SourceIpIP address restriction"192.168.1.0/24"
aws:SourceVpceVPC Endpoint restriction"vpce-xxx"
aws:SourceVpcVPC restriction"vpc-xxx"
aws:SecureTransportEnforce HTTPS"true" / "false"
s3:x-amz-aclACL condition"bucket-owner-full-control"
s3:prefixPrefix restriction"folder/"

Condition Operators

OperatorDescription
StringEqualsExact string match
StringNotEqualsString doesn't match
StringLikeWildcard match (*, ?)
IpAddressWithin IP range
NotIpAddressOutside IP range
BoolBoolean condition

Cross-Account Access Configuration

Both Sides Need Configuration

Cross-account access requires configuration in both the bucket owner account and the accessing account.

Cross-Account Access Setup:

[Account A: Bucket Owner]
├── S3 Bucket Policy
│   └── Principal: Allow Account B
│   └── Action: Allow needed actions
│
[Account B: Accessor]
├── IAM Policy (attached to user/role)
│   └── Resource: Account A's bucket ARN
│   └── Action: Allow needed actions

Example: Account B Accessing Account A's Bucket

Account A (Bucket Owner) - Bucket Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::222222222222:role/DataAccessRole"},
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::account-a-bucket",
        "arn:aws:s3:::account-a-bucket/*"
      ]
    }
  ]
}

Account B (Accessor) - IAM Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::account-a-bucket",
        "arn:aws:s3:::account-a-bucket/*"
      ]
    }
  ]
}

Exam Tip

Exam Essential: Cross-account requires bucket policy Allow + IAM policy Allow = Access granted. Missing either = access denied! Within the same account, bucket policy alone can grant access.


Policy Evaluation Logic

Allow and Deny Priority

Policy Evaluation Order:
1. Is there an explicit Deny? → Always denied (highest priority)
2. Is there an explicit Allow? → Allowed
3. Neither exists? → Implicit deny (default)

Key: Deny ALWAYS takes precedence over Allow!

When Multiple Policies Exist

Same Account:
├── IAM Allow + No bucket policy → Allowed
├── No IAM + Bucket policy Allow → Allowed
├── IAM Allow + Bucket policy Allow → Allowed
└── Deny anywhere → Denied

Cross-Account:
├── Bucket policy Allow + IAM Allow → Allowed
├── Bucket policy Allow + No IAM → Denied
├── No bucket policy + IAM Allow → Denied
└── Both must Allow!

SAA-C03 Exam Focus Points

Common Question Types

TypeKey Point
Principal differenceOnly bucket policies have Principal, IAM policies don't
Cross-accountBoth sides must Allow (bucket policy + IAM policy)
Resource ARNListBucket = bucket, GetObject = objects (/*)
HTTPS enforcementUse aws:SecureTransport condition
VPC restrictionUse aws:SourceVpce or aws:SourceVpc
Deny precedenceExplicit Deny overrides all Allow statements

Common Traps

❌ Specify Principal in IAM policies
   → IAM policies don't have Principal element

❌ Apply s3:GetObject to bucket ARN
   → Must apply to object ARN (bucket/*)

❌ Cross-account only needs bucket policy
   → Target account's IAM policy also needed

❌ Allow overrides Deny
   → Explicit Deny ALWAYS takes precedence

❌ Set public access with IAM policy
   → Needs bucket policy + Block Public Access OFF

Frequently Asked Questions

Q: Should I check bucket policy or IAM policy first?

Check both. Within the same account, either one allowing is sufficient, but cross-account requires both to Allow. Deny anywhere is applied with highest priority.

Q: Is there a bucket policy size limit?

20KB maximum. For complex policies, separate into IAM policies or use S3 Access Points.

Q: Should I use bucket policies or ACLs?

Use bucket policies. ACLs are legacy, and since 2023, new buckets have ACLs disabled by default. Bucket policies are more granular and easier to manage.

Q: How do I troubleshoot "Access Denied" errors?

  1. Check Block Public Access settings (for public access)
  2. Verify bucket policy Principal, Action, Resource
  3. Check IAM policy (both sides for cross-account)
  4. Look for explicit Deny statements
  5. Check VPC Endpoint policy (for VPC internal access)

Q: Can I put multiple Statements in one policy?

Yes, Statement is an array so you can include multiple rules in one policy. Use Sid to clearly distinguish each Statement for readability and management.


Summary

S3 bucket policies are essential for resource-based permission management. Key points:

  1. Bucket Policy: "Who can access this bucket" (Principal required)
  2. IAM Policy: "Where can this user access" (no Principal)
  3. Cross-Account: Both sides must Allow
  4. Deny Precedence: Explicit Deny overrides all Allow statements
  5. Resource ARN: Distinguish bucket vs object


References