When AWS evaluates permissions for S3 access, it follows a strict hierarchy:
- Explicit DENY: Any explicit deny in either policy overrides all allows
- IAM Policy: User/role permissions are evaluated first
- Bucket Policy: Resource-based permissions are evaluated second
For your specific case of restricting access to a VPC while maintaining granular IAM permissions, here's the optimal approach:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::xyz",
"arn:aws:s3:::xyz/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-12345678"
}
}
}
]
}
Your existing IAM policy grants specific permissions to users, while the bucket policy adds a network-layer restriction. The key points:
- The bucket policy uses DENY to block all non-VPC traffic
- IAM policies still control which actions users can perform
- No permission duplication needed
To verify the setup works as intended:
aws s3 ls s3://xyz --vpc-endpoint-id vpce-12345678 # Should work
aws s3 ls s3://xyz # Should fail
aws s3 cp test.txt s3://xyz/ --vpc-endpoint-id vpce-12345678 # Should work for users with put permissions
When combining these policies:
- Never use conflicting DENY rules in both policies
- Avoid overlapping ALLOW rules that might bypass restrictions
- Remember that bucket policies have a 20KB size limit
For time-bound VPC access combined with IAM permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::xyz",
"arn:aws:s3:::xyz/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-12345678"
},
"DateGreaterThan": {
"aws:CurrentTime": "2023-12-31T23:59:59Z"
}
}
}
]
}
When both IAM policies and bucket policies exist, AWS evaluates them differently:
- Explicit Deny: Any explicit "Deny" in either policy overrides all "Allow" statements
- IAM Policy: Evaluates permissions granted to the principal (user/role)
- Bucket Policy: Evaluates resource-based permissions
For your scenario combining IAM user permissions with VPC restrictions, here's the complete solution:
// Bucket Policy (VPC restriction)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictToVPC",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::xyz",
"arn:aws:s3:::xyz/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-12345678"
}
}
}
]
}
- The bucket policy's
Deny
statement will override any IAM Allow
when the VPC condition isn't met
- For requests coming from the allowed VPC, the IAM policy's permissions will apply normally
- Using
s3:*
in the bucket policy's Deny statement ensures comprehensive VPC protection
Verify your setup with these AWS CLI commands:
# Test from within allowed VPC
aws s3 ls s3://xyz --vpc-endpoint-id vpce-12345678
# Test from outside (should fail)
aws s3 ls s3://xyz
If access isn't working as expected:
1. Check IAM policy attachment to users
2. Verify VPC ID in bucket policy condition
3. Ensure no conflicting Deny statements exist
4. Validate network routing for VPC endpoints
When dealing with multiple accounts, combine both policies like this:
// IAM Policy (cross-account user)
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::other-account-bucket/*"
}
}
// Bucket Policy (in target account)
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::SOURCE_ACCOUNT:user/USERNAME"},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::other-account-bucket/*",
"Condition": {"StringEquals": {"aws:SourceVpc": "vpc-12345678"}}
}
}