Software Factory
AWS Cloudformation – Part 2
05 Aug 2019
by
Olivier Robert
In Part1, we worked on a template to create a VPC. We will extend this template with subnets and route tables. We’ll have public and private subnets.
Private subnets will be used for everything we want to shield from a direct internet access.
Public subnets means we want to be able to connect to services directly over an internet connection.
Our VPC will need an internet gateway. The default routing for our public subnet will be the Intenet Gateway.
Private subnets can be accessed from public subnets or services if this is what we want, but they will have no routing to the Internet Gateway and thus not internet access inbound, or outbound. An EC2 instance deployed in a private subnet will not be able to get updates from the internet for example. To remedy this situation, we can use a NAT Gateway.
The Nat Gateway, deployed in the public subnet, will relay internet connections initiated from the private subnet to the internet via the Internet Gateway. But there is still no way to directly connect from the internet to the private subnet. This is what we want.
Routing. We will have to create route tables and default routings and then associate public and private subnets to specific routing tables.
If you have read until here, have a look at this AWS scenario. It will clear things up.

Mmmm, one more thing, we will use Availability Zones to our advantage and distribute our subnets so that they are not all created in the same AZ.
Adding an Internet Gateway is straight forward:
Resources:
[...]
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "Project X"
The NAT Gateway will need an Elastic IP allocated to it.
Resources:
[...]
EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Domain is set to vpc
to allocate the address for use with instances in a VPC. In our case, the NAT Gateway.
Resources:
[...]
NatGW:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- EIP
- AllocationId
SubnetId: !Ref VPCPublicSubnet1
Tags:
- Key: Name
Value: "Project X"
We use the intrinsic function GetAtt
to retrieve the allocation ID attribute from the Elastic IP resource we named EIP.
For the subnet in which the NAT Gateway should be deployed in, we use the Ref
intrinsic function to reference a public subnet. That public subnet has not been defined yet, but we will name it "VPCPublicSubnet1".
For now, we'll continue with the NAT Gateway and attach it to our VPC and specify the Internet Gateway.
Resources:
[...]
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref IGW
VpcId: !Ref myVPC
Notice that we use resource references.
If you wonder how resources are created, Cloudformation creates independent resources in parallel. Implicit dependencies are detected when the property of one resource is referring to another resource (for instance an Internet Gateway in a VPCGatewayAttachment). Explicit dependencies can be declared with the DependsOn attribute. When we are using Ref to reference resources, we are creating implicit dependencies. The referenced resources should be created first. Sometimes, when things get complicated, explicit dependencies can solve issues.
In public subnets, we'll want automatic public IP allocations and as we are going to create 3 public subnets, we will distribute them over Availability Zones.
Resources:
[...]
VPCPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub1
VpcId: !Ref myVPC
VPCPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub2
VpcId: !Ref myVPC
VPCPublicSubnet3:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet3CIDR
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub3
VpcId: !Ref myVPC
Remember our NAT Gateway resource? Now the reference to VPCPublicSubnet1 is valid.
MapPublicIpOnLaunch is useful because instances launched in public subnets will get a public IP automatically.
The GetAZs
intrisic function returns an array of Availability Zones. We just pick one in the array for each subnet with Select
.
We introduced PublicSubnet<#>CIDR
which does not exist yet! These are references to parameters. Let's sort this out.
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Network Configuration
Parameters:
- VPCCIDR
- PublicSubnet1CIDR
- PublicSubnet2CIDR
- PublicSubnet3CIDR
ParameterLabels:
VPCCIDR:
default: VPC CIDR
PublicSubnet1CIDR:
default: Public subnet 1 CIDR
PublicSubnet2CIDR:
default: Public subnet 2 CIDR
PublicSubnet3CIDR:
default: Public subnet 3 CIDR
Parameters:
[...]
PublicSubnet1CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.1.0/24
Description: CIDR Block for the public DMZ subnet 1 located in Availability Zone 1
Type: String
PublicSubnet2CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.2.0/24
Description: CIDR Block for the public DMZ subnet 2 located in Availability Zone 2
Type: String
PublicSubnet3CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.3.0/24
Description: CIDR Block for the public DMZ subnet 3 located in Availability Zone 3
Type: String
I added parameter labels as well in the metadata section.
Next, private subnets where we do not need public adresses.
So far, our entire template looks like this:
AWSTemplateFormatVersion: 2010-09-09
Description: VPC template for Project X
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Network Configuration
Parameters:
- VPCCIDR
- PublicSubnet1CIDR
- PublicSubnet2CIDR
- PublicSubnet3CIDR
- PrivateSubnet1CIDR
- PrivateSubnet2CIDR
- PrivateSubnet3CIDR
ParameterLabels:
VPCCIDR:
default: VPC CIDR
PublicSubnet1CIDR:
default: Public subnet 1 CIDR
PublicSubnet2CIDR:
default: Public subnet 2 CIDR
PublicSubnet3CIDR:
default: Public subnet 3 CIDR
PrivateSubnet1CIDR:
default: Private subnet 1 CIDR
PrivateSubnet2CIDR:
default: Private subnet 2 CIDR
PrivateSubnet3CIDR:
default: Private subnet 3 CIDR
Parameters:
VPCCIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.0.0/16
Description: CIDR Block for the VPC
Type: String
PublicSubnet1CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.1.0/24
Description: CIDR Block for the public DMZ subnet 1 located in Availability Zone 1
Type: String
PublicSubnet2CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.2.0/24
Description: CIDR Block for the public DMZ subnet 2 located in Availability Zone 2
Type: String
PublicSubnet3CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.3.0/24
Description: CIDR Block for the public DMZ subnet 3 located in Availability Zone 3
Type: String
PrivateSubnet1CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.10.0/24
Description: CIDR block for private subnet 1 located in Availability Zone 1
Type: String
PrivateSubnet2CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.11.0/24
Description: CIDR block for private subnet 2 located in Availability Zone 2
Type: String
PrivateSubnet3CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.12.0/24
Description: CIDR block for private subnet 3 located in Availability Zone 3
Type: String
Resources:
myVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: True
InstanceTenancy: "default"
Tags:
- Key: Name
Value: "Project X"
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "Project X"
EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGW:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- EIP
- AllocationId
SubnetId: !Ref VPCPublicSubnet1
Tags:
- Key: Name
Value: "Project X"
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref IGW
VpcId: !Ref myVPC
VPCPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub1
VpcId: !Ref myVPC
VPCPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub2
VpcId: !Ref myVPC
VPCPublicSubnet3:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet3CIDR
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub3
VpcId: !Ref myVPC
VPCPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPriv1
VpcId: !Ref myVPC
VPCPrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet2CIDR
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPriv2
VpcId: !Ref myVPC
VPCPrivateSubnet3:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet3CIDR
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPriv3
VpcId: !Ref myVPC
Note that we have indicated default CIDR values for the subnets to avoid typing all parameters by hand in the web interface. The only thing we need to enter manually when creating the stack is a stack name.
It's time to get the routing part done. We will create a route table and a default route. Our route table only needs a reference to a VPC.
Resources:
[...]
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref myVPC
Tags:
- Key: Name
Value: ProjectXPubRT
Our default route will route any traffic to the internet gateway:
Resources:
[...]
PublicDefaultRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref IGW
RouteTableId: !Ref PublicRouteTable
This public route table (containing the default route) needs to be associated with the public subnets.
Resources:
[...]
PublicRouteSubnetAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref VPCPublicSubnet1
PublicRouteSubnetAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref VPCPublicSubnet2
PublicRouteSubnetAssociation3:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref VPCPublicSubnet3
We will do the same for the private subnet except these subnet will have a default route routing to the NAT Gateway.
Our final VPC template is this:
AWSTemplateFormatVersion: 2010-09-09
Description: VPC template for Project X
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Network Configuration
Parameters:
- VPCCIDR
- PublicSubnet1CIDR
- PublicSubnet2CIDR
- PublicSubnet3CIDR
- PrivateSubnet1CIDR
- PrivateSubnet2CIDR
- PrivateSubnet3CIDR
ParameterLabels:
VPCCIDR:
default: VPC CIDR
PublicSubnet1CIDR:
default: Public subnet 1 CIDR
PublicSubnet2CIDR:
default: Public subnet 2 CIDR
PublicSubnet3CIDR:
default: Public subnet 3 CIDR
PrivateSubnet1CIDR:
default: Private subnet 1 CIDR
PrivateSubnet2CIDR:
default: Private subnet 2 CIDR
PrivateSubnet3CIDR:
default: Private subnet 3 CIDR
Parameters:
VPCCIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.0.0/16
Description: CIDR Block for the VPC
Type: String
PublicSubnet1CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.1.0/24
Description: CIDR Block for the public DMZ subnet 1 located in Availability Zone 1
Type: String
PublicSubnet2CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.2.0/24
Description: CIDR Block for the public DMZ subnet 2 located in Availability Zone 2
Type: String
PublicSubnet3CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.3.0/24
Description: CIDR Block for the public DMZ subnet 3 located in Availability Zone 3
Type: String
PrivateSubnet1CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.10.0/24
Description: CIDR block for private subnet 1 located in Availability Zone 1
Type: String
PrivateSubnet2CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.11.0/24
Description: CIDR block for private subnet 2 located in Availability Zone 2
Type: String
PrivateSubnet3CIDR:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: Must be a valid IP range in x.x.x.x/x notation
Default: 10.0.12.0/24
Description: CIDR block for private subnet 3 located in Availability Zone 3
Type: String
Resources:
myVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: True
InstanceTenancy: "default"
Tags:
- Key: Name
Value: "Project X"
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "Project X"
EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGW:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- EIP
- AllocationId
SubnetId: !Ref VPCPublicSubnet1
Tags:
- Key: Name
Value: "Project X"
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref IGW
VpcId: !Ref myVPC
VPCPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub1
VpcId: !Ref myVPC
VPCPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub2
VpcId: !Ref myVPC
VPCPublicSubnet3:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: True
CidrBlock: !Ref PublicSubnet3CIDR
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPub3
VpcId: !Ref myVPC
VPCPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPriv1
VpcId: !Ref myVPC
VPCPrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet2CIDR
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPriv2
VpcId: !Ref myVPC
VPCPrivateSubnet3:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet3CIDR
AvailabilityZone:
Fn::Select:
- 2
- Fn::GetAZs: ""
Tags:
- Key: Name
Value: ProjectXPriv3
VpcId: !Ref myVPC
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref myVPC
Tags:
- Key: Name
Value: ProjectXPubRT
PublicDefaultRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref IGW
RouteTableId: !Ref PublicRouteTable
PublicRouteSubnetAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref VPCPublicSubnet1
PublicRouteSubnetAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref VPCPublicSubnet2
PublicRouteSubnetAssociation3:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref VPCPublicSubnet3
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref myVPC
Tags:
- Key: Name
Value: ProjectXPrivRT
PrivateDefaultRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NatGW
RouteTableId: !Ref PrivateRouteTable
PrivateRouteSubnetAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref VPCPrivateSubnet1
PrivateRouteSubnetAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref VPCPrivateSubnet2
PrivateRouteSubnetAssociation3:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable
SubnetId: !Ref VPCPrivateSubnet3
If you haven't uploaded the template to Cloudformation, here is the parameters section you'd get before launching it.

Stay tuned for Part 3 where we will deploy an EC2 bastion host in the public subnet and an EC2 instance in the private subnets to validate our configuration.