Initializing Enclave...

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 the Action block of your VPC Gateway Endpoint policy. Scope it to the specific bucket ARN under Resource.
  • 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 sees Access 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, including s3:DeleteObject, s3:PutBucketPolicy, and s3:GetObject on 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.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →