This tutorial walks through how to create a fully functional Virtual Private Cloud in AWS using CloudFormation. At the end of the tutorial, you will have a reproducible way to create a virtual cloud with three subnets, a security group, and an internet gateway with SSH access for your IP address. I’ve found this template useful for creating an isolated environment to develop and test software.
Full code for this tutorial is available on Github.
Preamble
The only truly required component of a CloudFormation template is the
Resources
field. This field describes all of the AWS infrastructure that
will be created by this template. The remainder of this tutorial will fill
out the Resources
field with all of the infrastructure required to
create a standalone VPC.
---
AWSTemplateFormatVersion: 2010-09-09
Resources:
...
In a real-world environment, you would leverage the Parameters
and
Mappings
sections to make your template flexible enough to be
customized, and you would leverage the Outputs
section to capture any
relevant AWS resource identifiers that have been created during the
deployment. However, to keep things simple for this post, I will stick
with defining AWS resources only.
The VPC
We start by creating the VPC. All CloudFormation resources have the same
basic structure. The logical id of the resource acts as the top-level key
defining the resource, and within this field is a Type
section listing
the CloudFormation resource type to create and Properties
section
defining the parameters to use when creating the Resource. In the
following example, I am creating a Resource with the logical id VPC
, and
the specified Type
and Properties
.
---
AWSTemplateFormatVersion: 2010-09-09
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.31.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
The CloudFormation reference documentationdescribes each of the
available Types
and their Properties
. You will be referring to this
reference a lot as you learn to develop your own CloudFormation templates.
In this example, I am declaring the following properties:
- CidrBlock: The IP address range available to this VPC.
- EnableDnsSupport: If set to true, AWS will resolve DNS hostnames to any instance within the VPC’s IP address.
- EnableDnsHostnames: If set to true, instances get allocated DNS hostnames by default.
- InstanceTenancy: By default, EC2 instances are launched on shared
hardware, you can set this field to
dedicated
to launch instances on dedicated hardware (that is more expensive).
The Internet Gateway
An Internet Gateway is required to allow resources in a VPC to connect to the public Internet. Creating an Internet Gateway resource in CloudFormation is straightforward — there are no properties required.
InternetGateway:
Type: AWS::EC2::InternetGateway
Now, we need to attach the Gateway to our VPC. Each Gateway can be
assigned to one and only one VPC and Amazon manages making the Gateway
available across availability zones. To attach a Gateway to a VPC, you
need to create a VPCGatewayAttachment
resource, and reference the
already created VPC and Internet Gateway resources using the !Ref
intrinsic
function.
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
Subnets
Now, we create the subnets. In this example, I will create three subnets in three different availability zones. This can be adjusted or parameterized depending on your requirements.
SubnetA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: us-east-1a
VpcId: !Ref VPC
CidrBlock: 172.31.0.0/20
MapPublicIpOnLaunch: true
SubnetB:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: us-east-1b
VpcId: !Ref VPC
CidrBlock: 172.31.16.0/20
MapPublicIpOnLaunch: true
SubnetC:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: us-east-1c
VpcId: !Ref VPC
CidrBlock: 172.31.32.0/20
MapPublicIpOnLaunch: true
EC2 instances must be launched within a Subnet, and, by default instances
do not have publicly accessible IP addresses. By setting
MapPublicIpOnLaunch
to true
instances launched into the subnet will be
allocated a public IP address by default. This means that any instances
in this subnet will be reachable from the Internet via the Internet
Gateway attached to the VPC.
I’ve also subdivided the VPCs set of available IPs into three ranges using
the CidrBlock
property.
Routing
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
InternetRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableId: !Ref RouteTable
According to the AWS documentation, any route entries that specify
a gateway must specify a dependency on the gateway attachment resource.
This is done using the DependsOn
attribute.
The DestinationCidrBlock
specifies which traffic we want this route to
be applied to. In this case, we apply it to all traffic using the
0.0.0.0/0
CIDR
block. The
GatewayId
specifies where traffic matching the CIDR block should be
directed.
Finally, we need to associate the route table with the subnets using a SubnetRouteTableAssociation
SubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetA
SubnetBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetB
SubnetCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref SubnetC
The route table associations connect the route table to the subnet. With this in place any instances created within the subnet will be able to route traffic to and from the Internet.
Security Groups
The last required piece is attaching a security group to the subnet. A security group effectively describes the firewall rules to be applied to a subnet.
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: "Internet Group"
GroupDescription: "SSH traffic in, all traffic out."
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Security groups have a number of properties that can be configured:
- GroupDescription: A text field for documenting the behaviour of the security group.
- VpcId: The VPC where this security group will be used.
- SecurityGroupIngress: The incoming traffic we should allow.
- SecurityEgressGroup: The outgoing traffic we should allow.
- IpProtocol: The protocol of traffic to allow.
-1
indicates any protocol. - FromPort and ToPort: Describe a range of allowed ports.
- CidrIp: The range of IP addresses to allow traffic from.
The security group created here allows all incoming traffic on port 22, allowing SSH access to EC2 instances, and allows all outgoing traffic.