ACM.64 Automating the creation of subnets an NACLS in AWS CloudFormation
This is a continuation of my series on Automating Cybersecurity Metrics.
We implemented our Public and Private VPCs and added VPC Flow Logs in the last few posts. Now we need to create Subnets and Network Access Control Lists (NACLs).
What are subnets?
They are a smaller network within a larger network. You can divide your VPC ip range up into subnets and then create firewall rules using NACLs to allow or disallow access to portions of your network. To better understand why networking rules help when it comes to cybersecurity refer to my book at the bottom of this post because it’s too long of a topic to explain here in any detail.
Regions and Availability Zones for System Reliability
When you create resources on AWS you can create them in regions which are basically data centers in different geographical locations. The regions have names in AWS like us-east-1 that equates to data centers in Virginia.
Within a region you can add resources to different availability zones (AZs). Think of an AZ like a data center within a region. If you create your architecture to deploy across multiple AZs, then if one goes down your application will still run in the other. If you need even greater reliability, you can deploy your application across multiple regions. There have been rare cases of regional outages in which organizations that did not have an architecture that planned for this occurrence had some downtime.
There’s a lot to that topic but for now just understand that when you create a subnet, you’re going to be creating it in a particular region and availability zone. If you try to deploy certain resources that are created in one AZ and add them to a network in another AZ you’ll get an error message. It’s as if you created a network in one data center and you’re trying to add a computer from another data center directly to that network. It’s not in the same physical location.
If you want your application to have greater reliability, create subnets in different AZs and architect your application to run in multiple AZs so if one has an issue, the resources in the other AZ are still online. If you want to get the EC2 SLA you’ll have to deploy your application in subnets that span two AZs.
Every once in a while you’ll get an error message that certain resources are not available in a certain AZ. I had this happen a few times in less widely used regions where a particular instance type I wanted was not available. I had to use a different AZ. I’ve also gotten errors saying I could not deploy an subnet in a particular AZ and I needed to choose a different one. We’ll want to plan for that possibility when we create our templates.
What subnets are we going to create?
We created two VPCs — one public and one private.
Private VPC Subnets
We want to have two subnets for private resources in our VPC. I going to initially use these subnets for private endpoints. I’m not specifying a particular AZ here just noting they will be different:
Private Endpoints Subnet 1: 10.10.0.0/27 – AZ1
Private Endpoints Subnet 2: 10.10.0.32/27 – AZ2
VPC Route Table
The subnets will inherit the VPC route table which is private. It does not contain an Internet Gateway as I explained in my last post.
Remote Access VPC Subnet
We only really need one subnet at this point since I’m the only developer and I need remote access and for now, I’m using a single account approach. That will probably change later.
Subnet1 CIDR: 10.20.0.0/27 – AZ1
Note that we expand on this base in a later post.
Service Requirements
You need to understand how many IPs each service you use needs that you will deploy in a VPC. If a service has a requirement for a particular number of IP addresses and your subnet is too small, you might hit a limit where your application can’t deploy more resources when it needs to do so. For example: Elastic Load Balancers require 8 available IPs and AWS reservers
Also:
Amazon reserves the first four (4) IP addresses and the last one (1) IP address of every subnet for IP networking purposes.
Enterprise Considerations
We’re going to implement some simple subnets here but there are other considerations for large organizations with multiple accounts, many lines of business, multi-cloud and hybrid cloud architectures that I’m not getting into here. But this will get us started.
CIDR Calculation
I use to have to manage a spreadsheet for VPC and Subnet CIDRs on AWS for Capital one. My job was to assign the next available CIDR for each application. First of all, don’t create a subnet for every application. We learned the hard way. Also, don’t use a spreadsheet. I probably annoyed somebody somewhere but maintaining the spreadsheet was time consuming and error prone.
Do you want a list of CIDRs that exist in your VPC? Query it!
Do you want to know the next available CIDR block or any unused CIDR blocks? Write some code to tell you that!
Then all I was left with was tracking a handful of CIDR blocks that were allocated to projects but not yet deployed. With 11,000 developers across multiple VPCs all jockeying to get their applications deployed first, this was much more efficient and not as error prone as trying to track things in a spreadsheet. I could essentially re-create the spreadsheet.
If you keep your subnets broad and name them appropriately they shouldn’t be too hard to track.
We can use some built in AWS functions that didn’t exist at the time to calculate subnet CIDRs based on VPC CIDR ranges as shown below.
Also, AWS has an IP Address Manager that may help that didn’t exist back then:
Automated Subnet Creation with CloudFormation
We want to create a reusable subnet template. As mentioned we need to account for the fact that we want to create multiple subnets for a specific VPC in different AZs. You can read about some of IP address restrictions at the top of this documentation:
We are going to use the following:
AvailabilityZone: The AZ in which to deploy the subnet.
CIDRBlock: The CIDR blocks above for our subnets.
MapPublicIPonLaunch: For now, we’ll set this to True for our Remote Access subnet and leave as the default (False) for our private subnet.
VPCID: We can reference the VPCID output from the corresponding VPC stack.
Tags: We’ll give our subnets as name as explained in prior posts.
We could create a route table and attach it to our subnets but we are just going to use the default VPC route tables for now. There may be instances where you do something different such as leave the primary VPC route table as private and create public and private subnets within your VPC but we don’t need that at the moment.
In the template above we’ll pass in the VPC export name and using that to get the VPCID from the export of our VPC template.
VPCExportParam:
Type: String
That way whenever I need the VPC ID I can use this:
VpcId:
Fn::ImportValue:
!Sub “${VPCExportParam}”
Per the documentation:
We can get AZs by number instead of ID. For example, each region has a different number of availability zones. Query the number of zones with this command:
aws ec2 describe-availability-zones –region [region here] –query AvailabilityZones[*].ZoneName
For example us-west-2 has 4 AZs. The number of the first AZ starts with 0 (not 1) so we can pass in a number of 0 to 3 to get the corresponding AZ without having to know the exact ID.
I need to pass in the number for the zone I want to use:
ZoneIndexParam:
Type: String
Next I can use the GetAZs function to get the AZs in the current region which we obtain using a pseudo parameter as explained in a prior post. This function will return a list of AZ ids in a list.
Note that I followed the documentation closely for this whole template because I was getting weird errors. For the least pain, follow the documentation exactly, even if it seems inconsistent and not so pretty. 🙁
Fn::GetAZs: !Ref ‘AWS::Region’
I can use the Select function to obtain a value from the AZ list based on an index number. The list index starts with 0 so the first item is 0, the second is 1, etc. I can pass in my zone number parameter as the index like this to get the AZ I want to use for the subnet.
AvailabilityZone: !Select
– !Ref ZoneIndexParam
– Fn::GetAZs: !Ref ‘AWS::Region’
For CIDR blocks we are using the CIDR function provided by AWS to calculate a list of CIDR blocks within our VPC CIDR block for our Subnets.
The CIDR function takes the following parameters:
ipBlock: Our VPC CIDR from the VPC template outputs
count: The number of CIDR blocks to create and return in the list
cidrBits: From the AWS documentation:
To explain further we can go to the ARIN online CIDR calculator and put in the CIDR ending in /24 to get the IP range, 10.0.0.0–10.0.0.255 which gives me a subnet with 255 available IP addresses less the IPs AWS reserves explained earlier.
ARIN – American Registry for Internet Numbers
If we want a subnet mask of /24 we subtract 24 from 32 which is 8 in the example above.
As I mentioned earlier for our private subnet I want to create subnets with /27 which gives me 32 addresses:
So I need to subtract 27 from 32 which is 5. I’ll pass in 5 for cidrBits to get the subnet size I want.
I’ll pass in 2 for count because I want two subnet CIDRs.
The result of that calculation will be a list of subnet CIDRs of the specified size.
!Cidr
– Fn::ImportValue:
!Sub “${VPCCidrExportParam}”
– !Ref SubnetCountParam
– !Ref CidrBitsParam
Note that in the above code I had inadvertently used the wrong export use the wrong export name for the CIDR range and I got the most unhelpful error. Essentially the error below was caused because the value passed in for a CIDR was invalid and I wish AWS would provide an appropriate error message here. This random error just occurred with no line number or indication as to which part of my template was causing the problem.
template error: String index out of range: -1
We’ll use the select function again to select the subnet index we want from the list of CIDRs returned. Note that if we want two subnets we only need to calculate 1 CIDR to create the first subnet and then 2 to get the second subnet.
!Select
– !Ref ${SubnetIndexParam}
–
!Cidr
– !ImportValue !Sub ${VPCCidrExportParam}
– !Ref ${SubnetCountParam}
– !Ref ${CidrBitsParam}
Our whole template looks like this for the moment:
Our function to add subnets looks like this for now:
We can add the following to our deployment script to create a subnet 2 subnets for our private batch jobs VPC and a subnet count of 1 for our remote access VPC. This goes after the creation of each VPC so we can pick up the VPC name and type.
Note that I’m passing in the first zone number in case I ever hit a situation where I am getting errors in a particular zone. I can deploy subnets one at a time and skip the failing zone, or if there are enough zones start at the zone number after the failing zone. I haven’t implemented that functionality yet. Maybe I will the first time I hit that problem. Call it lazy-loaded code. 🙂
Now test. Check to see that your subnets are created with the expected CIDR blocks and names.
Next we need some NACLs. Follow for updates.
Teri Radichel
If you liked this story please clap and follow:
Medium: Teri Radichel or Email List: Teri Radichel
Twitter: @teriradichel or @2ndSightLab
Requests services via LinkedIn: Teri Radichel or IANS Research
© 2nd Sight Lab 2022
All the posts in this series:
Automating Cybersecurity Metrics (ACM)
____________________________________________
Author:
Cybersecurity for Executives in the Age of Cloud on Amazon
Need Cloud Security Training? 2nd Sight Lab Cloud Security Training
Is your cloud secure? Hire 2nd Sight Lab for a penetration test or security assessment.
Have a Cybersecurity or Cloud Security Question? Ask Teri Radichel by scheduling a call with IANS Research.
Cybersecurity & Cloud Security Resources by Teri Radichel: Cybersecurity and Cloud security classes, articles, white papers, presentations, and podcasts
Automated Creation and CIDR Allocation for Subnets on AWS was originally published in Cloud Security on Medium, where people are continuing the conversation by highlighting and responding to this story.