S3 Interface VPC Endpoint: Private Access With Fine-Grained IAM Role Restriction

Ned
Ned Cloud Engineer
· Updated · 3 min read
S3 Interface VPC Endpoint: Private Access With Fine-Grained IAM Role Restriction

The Problem

You need to move data from EC2 to an S3 bucket. Requirements:

  • No API calls or data over the public internet
  • Only this specific EC2 instance can upload to the bucket

The Solution

Create an interface VPC endpoint for S3 in the subnet where the EC2 instance lives. Attach a bucket policy that only allows the EC2 instance’s IAM role.

How It Works

Step 1: Create the Interface Endpoint

1
2
3
4
5
6
7
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --service-name com.amazonaws.us-east-1.s3 \
  --vpc-endpoint-type Interface \
  --subnet-ids subnet-app-1a \
  --security-group-ids sg-s3-endpoint \
  --private-dns-enabled

An interface endpoint creates an ENI (elastic network interface) in your subnet with a private IP. All S3 API calls resolve to this private IP via private DNS. Traffic stays on the AWS backbone.

Step 2: Security Group on the Endpoint

1
2
3
4
aws ec2 authorize-security-group-ingress \
  --group-id sg-s3-endpoint \
  --protocol tcp --port 443 \
  --source-group sg-app-instance

Only the app instance’s security group can reach the endpoint. This is a network-level layer on top of IAM.

Step 3: Bucket Policy Restricted to the EC2 IAM Role

1
2
3
4
5
6
7
8
9
10
11
12
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowOnlyAppInstance",
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::123456789012:role/AppInstanceRole"
    },
    "Action": ["s3:PutObject", "s3:GetObject"],
    "Resource": "arn:aws:s3:::app-uploads/*"
  }]
}

Combined with an explicit deny for anyone else:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "Sid": "DenyAllOthers",
  "Effect": "Deny",
  "NotPrincipal": {
    "AWS": "arn:aws:iam::123456789012:role/AppInstanceRole"
  },
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::app-uploads",
    "arn:aws:s3:::app-uploads/*"
  ]
}

Why Interface (Not Gateway) Here?

Gateway endpoints do not support security groups. If you need network-level restriction — like “only this specific security group can talk to S3” — you need an interface endpoint. Gateway endpoints only support route-level access.

Interface endpoints cost roughly $7.30 per endpoint per AZ per month plus $0.01/GB processed. Gateway endpoints are free but less restrictive.

Why Not the Alternatives?

Gateway VPC endpoint + security groups — Gateway endpoints cannot have security groups attached. Security groups only work on interface endpoints.

nslookup for S3’s “private IP” — S3 is a public service. It has no private IP that DNS can return. That is precisely why VPC endpoints exist.

Using ip-ranges.json and static routes — That file contains public IP ranges. Static routes to public IPs still send traffic out through the internet gateway.

Key Takeaways

  • Interface endpoints support security groups for network-level restriction
  • Gateway endpoints are free but only offer route-table-level access control
  • Combine VPC endpoint (network layer) + bucket policy with Principal/NotPrincipal (identity layer) for defense in depth
  • S3 has no discoverable private IP — always use a VPC endpoint for private access
  • Use NotPrincipal in an explicit deny to allowlist exactly one identity
Rating:
Share
Previous PHI-Compliant S3: aws:SecureTransport + SSE-KMS for Encryption in Transit and at Rest Next The Cheapest, Most Resilient Static Website: Private S3 Bucket + CloudFront OAI