AWS networking · chapter 3 · lab in eu-north-1 (Stockholm)

Private S3 access from a VPC

Chapter 1 built a private street with no road to the gate. Chapter 2 showed a private passage that leads only to S3. This chapter puts the two together: a server on a private street, a door lock that lets SSH in only from inside the VPC, an S3 gateway endpoint, and a bucket that only accepts requests arriving through that endpoint.

We ran this lab in a fresh VPC called revise-vpc. It uses the same 10.0.0.0/16 range and the same private-street idea as learn-vpc, so only the IDs differ from chapter 1.

1. What we built

VPC
vpc-02aaef3395ad8700a · 10.0.0.0/16
Private subnet
subnet-0c55e2a950c52a527
EC2 instance "app"
i-0a7c9294be8dc5013 · t3.micro · 10.0.2.24
Security group
sg-0a90896cc897f783f (app-sg)
Route table
rtb-026300361e33b07b0
S3 gateway endpoint
vpce-08eeb1e7ed36e7973
Instance Connect Endpoint
eice-0f0df3f7760bf2084
Bucket
revise-vpc-lab-k7q3m9x2

2. The big picture

Everything AWS-side lives inside the VPC box. There is no internet gateway or NAT route, so the only ways in or out are the two endpoints: the Instance Connect Endpoint for your SSH session and the S3 gateway endpoint for S3 traffic.

The Instance Connect Endpoint is the one new piece. Like the interface endpoints in chapter 2, it is a network card with a private IP inside your subnet (10.0.2.201). Its only job is to carry your browser's SSH session to the server, so you never need a public IP or an Internet Gateway to log in.

Orange: how you get a shell on the instance. Green: S3 traffic that is allowed. Red dashed: paths that fail.

3. Where the server sits

An EC2 instance is a virtual machine. It has a network card (an ENI) placed in one subnet, and that card gets the private IP 10.0.2.24. It has no public IP, so nothing on the internet can address it directly.

4. The door lock: security groups

Chapter 1 called the security group the lock on each house door. Here is a real one. A security group is a list of allow rules. There are no deny rules: anything not matched is dropped. Each inbound rule has four parts, which are exactly the fields you saw on the Edit inbound rules page.

FieldYour ruleMeaning
TypeSSHA preset that fills in protocol and port.
Protocol / PortTCP / 22Which kind of traffic the rule matches.
Source10.0.0.0/16Who may send it. Here, any address inside the VPC.
Descriptionssh from insideA label for humans. No effect on traffic.

Saving the page replaces the group's inbound list, and the change takes effect within seconds on every instance using that group, with no reboot.

Why this group is needed

Tighter option: set the source to the Instance Connect Endpoint's own security group instead of the whole VPC range. Then only the endpoint can SSH in, not every machine in the VPC.

5. How an S3 request travels

When you ran aws s3 cp t.txt s3://revise-vpc-lab-k7q3m9x2/ on the instance, this is what happened:

  1. DNS. The CLI looks up the regional S3 hostname. It still gets a normal public S3 IP address. A gateway endpoint doesn't change DNS.
  2. Leaving the instance. The packet is headed for a public IP, and the security group's default outbound rule allows it.
  3. Route lookup. The route table finds the most specific match. That IP is in the S3 prefix list (pl-…), and that row points at vpce-08eeb1e7ed36e7973. This is the prefix-list route from chapter 2. AWS added it when you created the endpoint and picked this route table.
  4. Gateway endpoint. The traffic goes to S3 over the AWS network, never touching the internet. S3 sees that the request came through this endpoint, which sets the aws:SourceVpce value the bucket policy checks.
  5. Authorization. S3 checks the instance's IAM role, the endpoint policy (default: full access), and the bucket policy. All three allow it, so the upload succeeds.
How we created the gateway endpoint
  1. VPC console › Endpoints › Create endpoint.
  2. Type: AWS services. Service: com.amazonaws.eu-north-1.s3, the one with type Gateway.
  3. VPC: revise-vpc. Route tables: tick rtb-026300361e33b07b0.
  4. Policy: Full access › Create endpoint.
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-02aaef3395ad8700a \
  --service-name com.amazonaws.eu-north-1.s3 \
  --vpc-endpoint-type Gateway \
  --route-table-ids rtb-026300361e33b07b0

A gateway endpoint has no network card or IP of its own and costs nothing. It only works for S3 (and DynamoDB) in the same region, which is why curl https://www.google.com still times out: Google's IPs aren't in the S3 prefix list, and there's no default route.

6. The bucket's lock

Block Public Access is on for all four settings, so the bucket can never be made public by ACL or policy. The bucket policy adds one deny statement:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyUnlessViaVpceOrAdmin",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": [
      "arn:aws:s3:::revise-vpc-lab-k7q3m9x2",
      "arn:aws:s3:::revise-vpc-lab-k7q3m9x2/*"
    ],
    "Condition": {
      "StringNotEquals": { "aws:SourceVpce": "vpce-08eeb1e7ed36e7973" },
      "ArnNotEquals":    { "aws:PrincipalArn": "arn:aws:iam::270022337485:root" }
    }
  }]
}

Both conditions must be true for the deny to apply. Read it as: deny unless the request came through our endpoint, or the caller is the root user. An explicit deny beats any allow, so even an IAM user with full S3 permissions is blocked from outside the VPC.

7. How it connects to the outside

FromToPathResult
Your browserEC2 shellConsole → Instance Connect Endpoint → SSH :22, allowed by app-sgworks
EC2S3 bucketRoute table → gateway endpoint → S3works
EC2InternetNo 0.0.0.0/0 route, no NAT or internet gatewaytimes out
InternetEC2No public IP; SSH source limited to 10.0.0.0/16unreachable
Laptop as dev-userS3 bucketPublic S3 endpoint, no SourceVpceAccessDenied
Console / CloudShell (root)S3 bucketPublic S3 endpoint, root exemptworks

8. Tests we ran

[ec2-user@ip-10-0-2-24 ~]$ aws s3 cp t.txt s3://revise-vpc-lab-k7q3m9x2/ --region eu-north-1
upload: ./t.txt to s3://revise-vpc-lab-k7q3m9x2/t.txt
[ec2-user@ip-10-0-2-24 ~]$ aws s3 ls s3://revise-vpc-lab-k7q3m9x2/ --region eu-north-1
2026-11-06 18:58:17          6 t.txt

Still to check: run the same aws s3 ls from your laptop as dev-user and confirm it returns AccessDenied.

Remember only this

  1. The route table decides where the server can go: here, only inside the VPC, and to S3 through the endpoint.
  2. The security group decides who can reach the server: here, SSH only from inside the VPC.
  3. The bucket policy decides who can reach the data: here, only requests that came through our endpoint, plus root.

That completes the networking part: one VPC, a public and a private street, two kinds of door, and a private path to S3 that never touches the internet.