Fixing S3 Access Denied on GetObject: VPC Endpoint Policy Missing S3 Actions
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins
TL;DR
- What broke: Your VPC endpoint policy is an implicit deny machine. Even if the S3 bucket policy grants
s3:GetObject, the endpoint policy is a second, independent policy evaluation layer — if it doesn't explicitly allow the action, AWS denies the request. - How to fix it: Add
s3:GetObject(and any other required S3 actions) to theActionblock of your VPC Gateway Endpoint policy. Scope it to the specific bucket ARN underResource. - Shortcut: Use our Client-Side Sandbox above to auto-refactor this — paste both your endpoint policy and bucket policy and get corrected JSON output without leaking your ARNs to a third-party server.
The Incident (What Does the Error Mean?)
You're seeing this in your application logs or AWS CloudTrail:
An error occurred (AccessDenied) when calling the GetObject operation:
Access Denied
CloudTrail event will show:
{
"eventName": "GetObject",
"errorCode": "AccessDenied",
"errorMessage": "Access Denied",
"vpcEndpointId": "vpce-0abc1234def567890",
"sourceIPAddress": "vpce-0abc1234def567890.s3.us-east-1.vpce.amazonaws.com"
}
Immediate consequence: Every EC2 instance, Lambda, or ECS task in your VPC that routes S3 traffic through the endpoint is dead in the water. No objects can be read. If this is a data pipeline, ETL job, or application config loader — it's fully blocked.
AWS evaluates both the VPC endpoint policy and the S3 bucket policy. Both must allow the action. One deny anywhere = full deny. There is no override.
The Attack Vector / Blast Radius
This is a misconfiguration-induced outage, not an external attack — but the blast radius is significant:
- All principals routing through the endpoint are affected, not just one role or service.
- If your endpoint policy uses
"Action": "s3:*"scoped to the wrong resource, or uses"Effect": "Deny"without a matching allow, the failure is total and silent from the application's perspective — it just seesAccess Denied. - Secondary risk: Teams responding to this outage sometimes "fix" it by opening the endpoint policy to
"Action": "*"and"Resource": "*"— this is catastrophic. It allows any principal in the VPC to call any S3 API on any bucket, includings3:DeleteObject,s3:PutBucketPolicy, ands3:GetObjecton buckets outside your account that the endpoint can reach. - Data exfiltration vector: An overly permissive endpoint policy (
s3:*on*) combined with a compromised EC2 instance allows an attacker to exfiltrate data to an attacker-controlled S3 bucket through your own VPC endpoint — bypassing egress controls that only inspect public IPs.
How to Fix It (The Solution)
Basic Fix
Your current broken endpoint policy likely looks like this — either missing S3 actions entirely or scoped incorrectly:
{
"Version": "2012-10-17",
"Statement": [
{
- "Effect": "Allow",
- "Principal": "*",
- "Action": "s3:ListBucket",
- "Resource": "arn:aws:s3:::my-app-bucket"
+ "Effect": "Allow",
+ "Principal": "*",
+ "Action": [
+ "s3:GetObject",
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ "arn:aws:s3:::my-app-bucket",
+ "arn:aws:s3:::my-app-bucket/*"
+ ]
}
]
}
Critical: s3:ListBucket applies to the bucket ARN (arn:aws:s3:::bucket-name). s3:GetObject applies to object ARNs (arn:aws:s3:::bucket-name/*). Getting this wrong is the second most common cause of this exact error.
Enterprise Best Practice
Scope the endpoint policy to specific IAM principals and enforce aws:PrincipalOrgID so only your organization's identities can use the endpoint. Never use "Principal": "*" without a restricting condition.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowScopedS3Access",
"Effect": "Allow",
- "Principal": "*",
- "Action": "s3:*",
- "Resource": "*"
+ "Principal": "*",
+ "Action": [
+ "s3:GetObject",
+ "s3:ListBucket",
+ "s3:PutObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::my-app-bucket",
+ "arn:aws:s3:::my-app-bucket/*"
+ ],
+ "Condition": {
+ "StringEquals": {
+ "aws:PrincipalOrgID": "o-xxxxxxxxxx"
+ }
+ }
},
{
+ "Sid": "DenyExternalBucketAccess",
+ "Effect": "Deny",
+ "Principal": "*",
+ "Action": "s3:*",
+ "NotResource": [
+ "arn:aws:s3:::my-app-bucket",
+ "arn:aws:s3:::my-app-bucket/*"
+ ]
}
]
}
The Deny on NotResource is the critical enterprise control — it prevents the endpoint from being used as a data exfiltration channel to external buckets.
Also lock down the bucket policy to enforce VPC endpoint access only:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonVPCEAccess",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
],
"Condition": {
"StringNotEquals": {
+ "aws:sourceVpce": "vpce-0abc1234def567890"
}
}
}
]
}
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Checkov — catch overly permissive endpoint policies at plan time:
Checkov rule CKV_AWS_123 flags VPC endpoint policies with Action: * or Resource: *. Add to your pipeline:
checkov -d . --check CKV_AWS_123 --framework terraform
2. OPA/Conftest policy to block wildcard actions in endpoint policies:
package aws.vpce
deny[msg] {
stmt := input.resource.aws_vpc_endpoint_policy.statements[_]
stmt.actions[_] == "s3:*"
msg := "VPC endpoint policy must not use wildcard s3:* action. Enumerate required actions explicitly."
}
deny[msg] {
stmt := input.resource.aws_vpc_endpoint_policy.statements[_]
stmt.resources[_] == "*"
msg := "VPC endpoint policy must not use wildcard Resource. Scope to specific bucket ARNs."
}
3. Terraform — use aws_vpc_endpoint_policy resource, not inline policy on aws_vpc_endpoint:
This allows separate state management and policy drift detection via terraform plan.
4. AWS Config Rule: Enable vpc-endpoint-no-unrestricted-access managed rule. Set it to alert on any endpoint policy with Effect: Allow + Principal: * + Action: * in combination.
5. CloudTrail + EventBridge alert: Fire an alert any time ModifyVpcEndpoint is called — endpoint policy changes in production should be gated behind a change approval workflow, not ad-hoc CLI commands.