AWS VPC Network Structure

I recently started playing around with AWS. One of the really cool things about AWS is the Virtual Private Cloud (VPC) features. By creating a VPC, you create a private network for yourself complete with NAT gateways, custom route tables, firewalls and more.

VPC Components

A VPC is made up of several main components:

CIDR Block

When you create your VPC, you are asked to define a CIDR block, which is the range of IP addresses you want your VPC to use. For example, 10.0.0.0/16 means you can use 10.0.0.0 - 10.0.255.255 (65,534 addresses).

Subnets

A subnet is a CIDR block within your VPC that you want to group together. Instances you launch into your VPC are assigned a subnet, and the subnet defines how network traffic is routed. For example, if a subnet has a route table that is able to route traffic through the internet gateway, then it's public because it's connected to the internet. Otherwise it's private.

Security Groups & Network ACLs

Both Security Groups and Network ACLs are collections of firewall rules that define which ingress and egress network traffic is allowed.

There are a few differences between them:

  • Security Groups are applied to specific instances and can be stateful (similar to iptables). Security Groups filter traffic to and from instances.
  • Network ACLs provide stateless filtering on traffic entering or leaving a subnet. Traffic that stays within the same subnet is not filtered.

So both have their place. I usually just use Security Groups.

Route Tables

Route tables are assigned to subnets and define how traffic within the subnet is routed to various destinations. For example, a route table defines if and how traffic is routed to the wider internet.

Internet Gateways

The Internet Gateway is how your VPC connects to the internet. You use an Internet Gateway with a route table to tell the VPC how internet traffic gets to the internet.

An Internet Gateway appears in the VPC as just a name. Amazon manages the gateway and there's nothing you really have a say in (other than to use it or not; remember that you might want a completely segmented subnet that cannot access the internet at all).

Elastic IPs

An Elastic IP is a static IP address you reserve and can attach to specific network interfaces.

An EIP is the only way to get a static IP within AWS.

NAT Gateways

A NAT Gateway allows you to have private instances who can connect to the greater internet (that is, it performs NAT translation) through one of your elastic IPs.

Availability Zones

A VPC is region-specific, but covers all availability zones within the region.

Each subnet you define is assigned to a specific zone. This factors into your subnet design.

While each subnet is assigned a specific zone, you can of course communicate across the network to instances in any zone.

Subnet Design

One of the most important things to think about when creating your VPC is the design of your subnets. This matters a lot because you cannot change your subnets (well, not without disrupting your services and configuration!).

Your goal is about designing the subnets such that you solve todays problems, but leaving yourself enough room to grow to accommodate tomorrows problems. This basically comes down to limiting the size of your subnets (you can always add new subnets later).

Here's my advice for a "small" (relatively) VPC:

  • Start with two availability zones and dedicate /21 blocks to each (2048 addresses).
  • In each zone, define subnets for public and private traffic. For each of those subnets, dedicate a /23 block (512 addresses).

So with this means you'll define 4 subnets (2 per zone), but leave yourself loads of room for expansion if you need it.

  • Zone A - 10.0.0.0/21 (10.0.0.0 - 10.0.7.255)
    • Subnet A Public - 10.0.0.0/23 (10.0.0.0 - 10.0.1.255)
    • Subnet A Private - 10.0.2.0/23 (10.0.2.0 - 10.0.3.255)
    • The rest of the block is unused, so could be used for something else later.
  • Zone B - 10.0.8.0/21 (10.0.8.0 - 10.0.15.255)
    • Subnet B Public - 10.0.8.0/23 (10.0.8.0 - 10.0.9.255)
    • Subnet B Private - 10.0.10.0/23 (10.0.10.0 - 10.0.11.255)
    • The rest of the block is unused, so could be used for something later.
  • The rest of the VPC block is unused, so you have lots of room for expansion later.

Remember that some of this is just logical grouping of IP addresses (i.e. deciding that 10.0.0.0/21 is all "Zone A"), some of it is practical (subnets have ACLs, route tables, etc) and some of it is just leaving yourself space to grow.

What you DON'T want to do is go crazy and use the entire VPC block of addresses in a single subnet. Leave yourself plenty of free space, you never know when you might need to create a new subnet.

Public vs Private and NAT Gateways

A public subnet means a subnet that has internet traffic routed through AWS's Internet Gateway. Any instance within a public subnet can have a public IP assigned to it (e.g. an EC2 instance with "associate public ip address" enabled).

A private subnet means the instances are not publicly accessible from the internet. They do NOT have a public IP address. For example, you cannot access them directly via SSH. Instances on private subnets may still access the internet themselves though (i.e. by using a NAT Gateway).

A NAT Gateway is comprised of:

  • The NAT Gateway itself. This is a highly available instance managed by AWS. You could create an EC2 instance and manage it yourself, but the NAT Gateway is usually the simpler option.
  • An Elastic IP. This is the public IP that your gateway uses to communicate with the outside world.

To use a NAT Gateway:

  1. Create an Elastic IP
  2. Create a NAT Gateway
    • Assign the Elastic IP you just created
    • Set the Subnet to your public subnet. (It must be on the public subnet or else it won't be routable).
  3. Create a new Route Table
    • Add a new route with destination 0.0.0.0/0 and target being your new NAT Gateway.
  4. Assign this route table to your private subnet.

Accessing Private Instances via a Bastion Host

Instances that exist on a private subnet are inaccessible directly from the internet. Instances can use the internet via the gateway, but there's no way for you to access the instance from the internet. For example, you can't SSH into it, you can't serve a website from it, etc.

If you are running a web service like a web server, SMTP server, etc, then this is usually fine because you typically use a proxy or node balancer that has a public interface and then passes traffic through to your private subnet.

However, you still often need access to the server for sysadmin type work or debugging. This is done by spinning up a bastion host.

A bastion host is the name for a public instance whose purpose is specifically about providing access to services on an internal network. Since this host is public, it is often security-hardened and expected to withstand attack.

In our VPC scenario, we typically only need a bastion host so we can SSH into the servers in the private subnet. For this purpose, the bastion doesn't need to be big; a t2.micro instance is likely enough for most workloads. All you need it for is to act as a middle-man for SSH:

You > SSH into bastion > SSH into server

A t2.micro is usually enough to support a dozen connections.

SSH and Bastion Hosts

EC2 instances by default use key files to authenticate SSH sessions. Obviously you want to keep this as it's the most secure method.

But this presents a problem. You can connect to your bastion server with a key -- that's fine. But then your bastion server needs to have the keys to connect to every other server on your VPC private network. In other words, your bastion server has the keys to everything. That's not so good.

The best way I've found to square this problem is to use SSH proxying. I've got another article about that here: SSH with a bastion host.

Christopher Nadeau

London, UK