Tutorial: Setting up a private subnet on AWS
For growing companies that offer services over the internet, there often comes the point where they need to distinguish between what services they offer to the public and what services to offer only internally.
Especially when you’re building a micro-service infrastructure, you want that most services can talk to each other, but not that the entire internet can talk to them. The most typical way of doing this, is to ask the user for some kind of authentication, e.g. a password. The first downsides that come with passwords is that you have to management (e.g. when employees are joining and leaving your company) and they don’t protect you from attacks like DDoS.
One solution that I would like to talk about here are private subnets:
You want some of your machines/services to be accessible from the public internet, e.g. your web servers, but want to shield other machines, e.g. databases form direct web access. Often it is helpful to allow services in the private subnet to “call-out”, but not to allow systems from the outside to “call in” (see the red vs green-dashed arrows in the depiction above).
Your account
Since this is a tutorial on setting up a private subnet on AWS, you should be logged into the AWS user interface. I will assume that you have broad access rights in your account (e.g. you’re an administrator) and will not go into detail which rights you will need.
If you are worried that you might break a running system that already is in your account you have 2 choices. Create a new account for this tutorial or pick a region where there are no production systems running (the latter is obviously much less work!)
Pick a region
In AWS there are many different regions and probably you have already decided for one. If not here are my 2 cents on this topic:
- You should pick a region that is close to your customers and therefore reduce latency
- Picking the “older” and more established regions often gives you early access to new product launches and in some cases less headaches (e.g. new S3 authentication mechanisms). Good candidates are:
us-east-1
andeu-west-1
Creating new subnets
We will create 2 new subnets in this tutorial one public, one private. If you have a completely new AWS account, you could also just use the already existing subnet as the public subnet, but I will assume you create a new one.
In the AWS Web UI go to Services
→ VPC (virtual private cloud)
→ Subnets
- Click the blue
Create Subnet
button at the top of the page - You can chose what ever name you like, I would suggest the following naming scheme:
REGION-PURPOSE-ACCESS-AZ
in my case this would be:
region:us-east-1
purpose:general
access:public
availability zone:1a
so I called it:us-east-1-general-public-1a
- Chose your VPC — typically you only have 1 at this stage
- Availability zone: I would suggest you pick
a
, but in most cases it doesn’t really matter — for better fault tolerance, you should probably create at least one more subnet that is connected tob
after this tutorial - CIDR: This is basically the IP address range for your subnet. As a very short tutorial:
172.0.0.0/16
defines the range of IP address from172.0.0.0
to172.0.255.255
. The/16
at the end defines how manybits
from the start of the IP address are fixed (the same for all addresses in the range). In this case 16 of 32 are fixed. Since IP addresses are written in blocks of 8 bits, this means that the first 2 blocks172
and0
must be present in all IP addresses in the range, but the remaining 2 blocks are allowed to change!
Here some more examples:172.0.0.0/28
ranges from172.0.0.0
to172.0.0.255
172.0.1.0/28
ranges from172.0.1.0
to172.0.1.255
172.0.0.0/28
ranges from172.0.0.0
to172.0.0.15
172.0.0.16/28
ranges from172.0.0.16
to172.0.0.31
ranges from
172.0.0.0/20172.0.0.0
to172.0.31.255
To find a suitable CIDR for your case, you have to pick a sub-range of your VPC’s CIDR (it’s show a few lines earlier in the UI) and ideally follows you existing subnets. Without looking at the other subnets in your account, an easy way to find a CIDR would be to take your VPC’s CIDR, e.g.172.0.0.0/16
and take172.0.X.0/20
as your subnet CIDR. For X you can try through numbers0, 16, 32, 64, …
until the AWS UI stops complaining ;) - Repeat all steps from 1–5 for the
private
subnet (replacing thepublic
withprivate
in the name) - After having created both subnets, select the
public
subnet and above the list selectActions
→Modify auto-assign IP settings
and set the checkbox: Auto-assign IPv4 to Enable auto-assign public IPv4 address
Done! You now have 2 fresh subnets.
Creating a NAT
In order to allow services from your private subnet to talk to the internet, we need a Network Address Translation device. You could use an EC2 instance for this, but let’s use a preconfigured one from AWS:
In the AWS Web UI go to Services
→ VPC
→ NAT Gateways
- Click
Create NAT Gateway
- Select your
public
subnet that we just created — you can type the name of it or copy&paste the id. In my case I picked:us-east-1-general-public-1a
- Create a new EIP: an Elastic IP is basically just an IP address that stays the same — so that you can reboot the machine and IP stays the same. In our case we will just create a new one (you could also reuse an existing one).
After creating the EIP, I would just that you copy and save the EIP’s identifier, in order to give it a name later, for keeping your account clean. - Click
Create NAT Gateway
Your NAT Gateway is created! The following step is just the before mentioned cleanup. - Go to
Services
→EC2
→Elastic IPs
and filter for the EIP’s id you saved. Rename it for instance toNAT Gateway
just to keep track what it’s being used for
Routing your traffic
Now in the last step of the setup process, we need to route where the traffic is going. For this we need to go to: Services
→ VPC
→ Route Tables
- You can reuse the existing one, but in order to reduce the risk of breaking a running application, let’s create a new one. Click
Create Route Table
. - Pick a name: In my case I just called it
private-route-table
- Pick the same VPC where you created the subnets
- Click
Create
- Now back in the
Route Table
overview, select the table you just created and in the information view at the bottom of the screen selectRoutes
→Edit Routes
- Click
Add Route
- Under Destination type:
0.0.0.0/0
and under target selectNAT Gateway
and pick the one that you just created (typically you only have one!)
The destination0.0.0.0/0
means that when trying to talk to any address on the internet (range0.0.0.0
to255.255.255.255
) your machine should ask the NAT gateway to help! - Click
Save Routes
- Now back in the
Route Table
overview, still with your new route table selected, in the information view at the bottom of the screen selectSubnet Associations
→Edit subnet associations
- Select your private subnet and click
Save
Done! If you have a normal AWS account without making any changes in this section you’re done. To be save, let’s make sure that your public subnet also knows how to talk to the internet:
- In the overview screen on the
Route Table
page find the route table where the columnMain
is equal toYes
and select it. - In the information view on the bottom select
Routes
and check there is a route with Destination0.0.0.0/0
and Targetigw-XXXXXXX
(where X can obviously mean any character) - If this is the case, you are done here!
- If this is not the case, please follow through the steps of creating the private route table, except when choosing the Target, please select Internet Gateway instead of the NAT!
Done…
You are now finished setting everything up — but since there can always be a problem, let’s make sure it works by testing it!
Starting a server in our private subnet
Let’s start a new EC2 machine in our private subnet, by going to: Services
→ EC2
→ Instances
- Click
Launch Instance
- You can launch any image, but I will assume you picked some kind of
Ubuntu Server ...
and pressedSelect
- You can pick any machine size, but I suggest a
t2-micro
ort2-nano
and clickNext: Configure Instance Details
- The only option that we have to change here is to select our
private
subnet we created earlier. Now click:Next: Add Storage
- Nothing to change. Now click:
Next: Add Tags
- I suggest you add a tag called
Name
with valuemy-private-instance
this helps you finding this instance later! Now click:Next: Configure Security Group
- In the security group section, you have to make sure to set a security group that allows ssh access. If you don’t have any existing ones, you have to create a new one. AWS already selected everything for you, I would just suggest to change the name from
launch-wizard-1
tossh-access-only
.
Now click:Preview & Launch
- Now click:
Launch
- AWS will now ask you for an ssh key-pair. Either pick an existing one or create a new one. If you create a new one, please follow these steps:
a) Put in a name, e.g. your name :P
b) PressDownload
c) On a mac or unix machine you would now go to the terminal and run:mv ~/Downloads/FILENAME ~/.ssh/NAME_YOUR_KEY.pem
chmod 600 ~/.ssh/NAME_YOUR_KEY.pem
ssh-add ~/.ssh/NAME_YOUR_KEY.pem
These steps will put the key into the normal folder, reduce access rights so that only you can use it and tells the ssh agent (service running on your machine) that it exists - After having dealt with the SSH Key we can finally
Launch Instances
to start the machines.
We have now started a server in the private subnet — but we should not be able to connect to it via SSH directly, since the private subnet is… private.
In order to connect to it, we must launch another instance in the public subnet. Go through the same steps as launching the private machine, except for selecting the public
subnet in step (4)!
Now you should have 2 instances running — 1 private and 1 public. The public one should also have a public ip and dns. This public server is typically called bastion
and I will refer to it as such in the next steps.
To connect to your private machine, we first have to ssh into the bastion
:
ssh -A ubuntu@PUBLIC_IP_ADDRESS_OF_BASTION
ssh ubuntu@PRIVATE_IP_ADDRESS_OF_PRIVATE_SERVER
After typing the above 2 commands into your terminal, you should now be logged in on your private server. To validate that also the outgoing internet connection is working, just type ping 8.8.8.8
into the terminal and should see output like this:
64 bytes from 8.8.8.8: icmp_seq=34 ttl=108 time=12.6 ms
64 bytes from 8.8.8.8: icmp_seq=35 ttl=108 time=12.6 ms
64 bytes from 8.8.8.8: icmp_seq=36 ttl=108 time=12.6 ms
64 bytes from 8.8.8.8: icmp_seq=37 ttl=108 time=12.5 ms
64 bytes from 8.8.8.8: icmp_seq=38 ttl=108 time=12.5 ms
64 bytes from 8.8.8.8: icmp_seq=39 ttl=108 time=12.5 ms
I hope it does! → if yes, we are done! 🎉🍾
otherwise I’m very sorry and please go back though the steps above to check what may have gone wrong.
Summary
- We created 2 subnets — a public and a private one
- Created a NAT Gateway to allow internet access for the private network
- Routed the traffic in the networks to get internet access
- Validated everything is working
Next Steps
Obviously this is only the start of the journey, but where can you go from here?
- If you want to connect to these private subnets from your office wifi, please go through this explanation: AWS Docs
I will probably also clarify this in another Post, but until then, you can use the link above. - You can now launch your database or other services with a much better protection layer
- You can launch private serverless (AWS Lambda) functions with regional endpoints. I will soon write a Post on this! This makes building micro-services architectures much easier!