VPC stack template for AWS Cloudformation¶
This was a template I created with troposphere and launches a VPC stack on AWS via cloudformation.
It consists of a VPC, subnets, route tables and an internet gateway.
Outputs
- VPC (Export)
- A VPC named VPC
- Subnet1 (Export)
- A subnet named Subnet1
- Subnet2 (Export)
- A subnet named Subnet2
Note
- I was testing this in the eu-west-2 (London) region, you may have to modify this to match your geographic region
- To build this template use pip install troposphere and then run the file via python vpc-stack.py
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
from troposphere import Join, Output, GetAZs, Select, Export, Ref, Tags, Template
from troposphere.ec2 import PortRange, NetworkAcl, Route, \
VPCGatewayAttachment, SubnetRouteTableAssociation, Subnet, RouteTable, \
VPC, NetworkAclEntry, SubnetNetworkAclAssociation, InternetGateway
#
# Troposphere Template - https://github.com/cloudtools/troposphere
#
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Template & Parameters │
# └───────────────────────────────────────────────────────────────────────────┘
#
template = Template()
template.add_description("VPC stack | anil.io")
def make_name(component_name):
return Join('', [component_name, ' - ', Ref('AWS::StackName')])
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ VPC │
# └───────────────────────────────────────────────────────────────────────────┘
#
# View: https://www.youtube.com/watch?v=CP7yd7nOb5Q
#
# Create a VPC (A self contained network on AWS isolated from other networks on AWS)
#
# The CIDR block is the IP range that we can create Subnets on
# Each Subnet has it's own pool of IP's it can use to assign to resources in that Subnet
# We must use /16 for a VPC = 256 Subnets Max
#
# In this case, our VPC IP range is 10.0.0.0/16
# We can assign IP's in the 10.0.0.0 - 10.0.255.255 Range (Subnets + Resources)
#
vpc_tags = Tags(Network="public", Name=make_name('VPC'))
VPC = template.add_resource(
VPC('VPC', CidrBlock='10.0.0.0/16', EnableDnsHostnames=True, Tags=vpc_tags)
)
template.add_output(Output("VPC", Description="VPC", Value=Ref(VPC), Export=Export(name="VPC")))
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Subnet │
# └───────────────────────────────────────────────────────────────────────────┘
#
#
# Subnets have a range of IP's within a VPC, We launch resources into our Subnet's
#
# Resources (Instances) within this subnet will have the following IP Ranges.
#
# Subnet 1: 10.0.1.1, 10.0.1.2, 10.0.1.3, etc.
# Subnet 2: 10.0.2.1, 10.0.2.2, 10.0.2.3, etc.
# Subnet 3: 10.0.3.1, 10.0.3.2, 10.0.3.3, etc.
#
# All Subnet IP's must be within our VPC IP range (10.0.0.0 - 10.0.255.255)
#
# - Our load-balancer will distribute traffic to all instances within our Subnet's
# - By default all Subnets have a route that allows traffic to flow between them
#
# Subnet's with a IGW (Internet Gateway) attached (via a Route) is known as a "Public Subnet"
#
# ------------------
# Availability Zones
# ------------------
#
# eu-west-1 EU (Ireland) 3 availability zones
# eu-west-2 EU (London) 2 availability zones
# eu-central-1 EU (Frankfurt) 3 availability zones
# us-east-1 US East (N. Virginia) 6 availability zones
# us-east-2 US East (Ohio) 3 availability zones
# us-west-2 US West (Oregon) 3 availability zones
#
# > View all the AZ's available here
# > aws ec2 describe-availability-zones --region eu-west-1
#
# http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
#
# Hosted in Availability Zone (A)
subnet1 = template.add_resource(
Subnet('Subnet1', CidrBlock='10.0.1.0/24', VpcId=Ref(VPC),
AvailabilityZone=Select(0, GetAZs(Ref('AWS::Region'))),
Tags=Tags(Network="public", Name=make_name('10.0.1.0'))
)
)
# Hosted in Availability Zone (B)
subnet2 = template.add_resource(
Subnet('Subnet2', CidrBlock='10.0.2.0/24', VpcId=Ref(VPC),
AvailabilityZone=Select(1, GetAZs(Ref('AWS::Region'))),
Tags=Tags(Network="public", Name=make_name('10.0.2.0'))
)
)
# Hosted in Availability Zone (C)
# subnet3 = template.add_resource(
# Subnet('Subnet3', CidrBlock='10.0.3.0/24', VpcId=Ref(VPC),
# AvailabilityZone=Select(2, GetAZs(Ref('AWS::Region'))),
# Tags=Tags(Network="public", Name=make_name('10.0.3.0'))
# )
# )
template.add_output([
Output("Subnet1", Description="Subnet 1", Value=Ref(subnet1), Export=Export(name="Subnet1")),
Output("Subnet2", Description="Subnet 2", Value=Ref(subnet2), Export=Export(name="Subnet2"))
])
#
# At this point, our instances are Private (Private IP) to our Subnet's only.
# They can communicate between Instances on the same Subnet's, but you cannot connect to it.
# They do not have access to the internet until we setup an IGW (Internet Gateway) and Elastic IP
# We need to first setup a Route Table (then IGW and EIP)
#
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Route Table │
# └───────────────────────────────────────────────────────────────────────────┘
#
#
# Route tables contain rules which determine where network traffic is to be directed.
# All Subnet's in the VPC must be associated with a Route Table, the table controls the routing for the subnet
# If no subnet associations are explicitly defined, They will default to the "Main" Route Table.
#
# A default Route will have the following to allow all subnet's to communicate with one another.
#
# ┌───────────────────────────────────────────┐
# │Destination | Target | Status | Propagated │
# └───────────────────────────────────────────┘
# │10.0.0.0/16 | local | Active | No │
# └───────────────────────────────────────────┘
#
# We attach our IGW here to allow our Subnets to access the Internet.
#
# ┌───────────────────────────────────────────┐
# │0.0.0.0/0 | igw-0a34346f | Active | No │
# └───────────────────────────────────────────┘
#
# Note: When creating a VPC, a default "Main" route table is also created.
# We can't reference this as of yet: https://forums.aws.amazon.com/thread.jspa?threadID=97060
# Just ignore it, It's managed by cloudformation (and removed when the stack is deleted for us)
#
route_tags = Tags(Name=make_name('Route Table'))
route_table = template.add_resource(RouteTable('RouteTable', VpcId=Ref(VPC), Tags=route_tags))
route = template.add_resource(
Route(
'Route',
DependsOn='AttachGateway',
GatewayId=Ref('InternetGateway'),
DestinationCidrBlock='0.0.0.0/0',
RouteTableId=Ref(route_table)
)
)
# Attach all our subnets to our new Route Table
sub1 = SubnetRouteTableAssociation('Subnet1RouteAssociation', SubnetId=Ref(subnet1), RouteTableId=Ref(route_table))
sub2 = SubnetRouteTableAssociation('Subnet2RouteAssociation', SubnetId=Ref(subnet2), RouteTableId=Ref(route_table))
# sub3 = SubnetRouteTableAssociation('Subnet3RouteAssociation', SubnetId=Ref(subnet3), RouteTableId=Ref(route_table))
template.add_resource(sub1)
template.add_resource(sub2)
# template.add_resource(sub3)
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Internet Gateway │
# └───────────────────────────────────────────────────────────────────────────┘
#
# An Internet Gateway (IGW) will provide Internet Access through your Route Table.
#
# A load balancer will serve traffic through the LB to the IGW to our Instances.
#
# Create the Gateway
igw = InternetGateway('InternetGateway', Tags=Tags(Name=make_name('IGW')))
internet_gateway = template.add_resource(igw)
# Attach it to our VPC - A VPC can only have 1 IGW associated to it.
vpc_gw = VPCGatewayAttachment('AttachGateway', VpcId=Ref(VPC), InternetGatewayId=Ref(internet_gateway))
gateway_attachment = template.add_resource(vpc_gw)
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Network ACL │
# └───────────────────────────────────────────────────────────────────────────┘
#
# We can assign subnet's different network ACL's, our ACL list will
# allow and deny traffic at a network level to a Subnet.
# Security Groups do this on an instance level, N-ACL's do this on a subnet level
#
network_acl = template.add_resource(
NetworkAcl('NetworkAcl', VpcId=Ref(VPC), Tags=Tags(Name=make_name('Network ACL')))
)
subnet1_network_acl_association = template.add_resource(
SubnetNetworkAclAssociation(
'Subnet1NetworkAclAssociation',
SubnetId=Ref(subnet1),
NetworkAclId=Ref(network_acl),
))
subnet2_network_acl_association = template.add_resource(
SubnetNetworkAclAssociation(
'Subnet2NetworkAclAssociation',
SubnetId=Ref(subnet2),
NetworkAclId=Ref(network_acl),
))
# subnet3_network_acl_association = template.add_resource(
# SubnetNetworkAclAssociation(
# 'Subnet3NetworkAclAssociation',
# SubnetId=Ref(subnet3),
# NetworkAclId=Ref(network_acl),
# ))
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Network ACL Entries │
# └───────────────────────────────────────────────────────────────────────────┘
#
# Further Entries for Network ACL Allows (SSH, HTTP, RDP)
# VPC Dashboard - Allow HTTPS Inbound
# http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html
#
# We need our resources to talk between each other, this allows them to do that
# Don't get this confused with "SecurityGroups" which are public facing
# These control our internal traffic (An optional firewall) - Security Groups are public
#
inbound_http_acl = template.add_resource(
NetworkAclEntry(
'InboundHTTPNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='100',
Protocol='6',
PortRange=PortRange(To='80', From='80'),
Egress='false',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
inbound_ssh_acl = template.add_resource(
NetworkAclEntry(
'InboundSSHNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='101',
Protocol='6',
PortRange=PortRange(To='22', From='22'),
Egress='false',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
# These control our internal traffic (An optional firewall) - Security Groups are public
inbound_response_acl = template.add_resource(
NetworkAclEntry(
'InboundResponsePortsNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='102',
Protocol='6',
PortRange=PortRange(To='65535', From='1024'),
Egress='false',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
# HTTPS: Load Balancer issue (Timeout - Not connecting). Allow 443 inbound
inbound_https_acl = template.add_resource(
NetworkAclEntry(
'inboundHttpsPortsNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='103',
Protocol='6',
PortRange=PortRange(To='443', From='443'),
Egress='false',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
outbound_http_acl = template.add_resource(
NetworkAclEntry(
'OutBoundHTTPNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='100',
Protocol='6',
PortRange=PortRange(To='80', From='80'),
Egress='true',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
outbound_https_acl = template.add_resource(
NetworkAclEntry(
'OutBoundHTTPSNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='101',
Protocol='6',
PortRange=PortRange(To='443', From='443'),
Egress='true',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
outbound_response_acl = template.add_resource(
NetworkAclEntry(
'OutBoundResponsePortsNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='102',
Protocol='6',
PortRange=PortRange(To='65535', From='1024'),
Egress='true',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
outbound_ses_mailer_acl = template.add_resource(
NetworkAclEntry(
'OutBoundSESNetworkAclEntry',
NetworkAclId=Ref(network_acl),
RuleNumber='103',
Protocol='6',
PortRange=PortRange(To='587', From='587'),
Egress='true',
RuleAction='allow',
CidrBlock='0.0.0.0/0',
))
#
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ JSON DUMP │
# └───────────────────────────────────────────────────────────────────────────┘
#
with open(os.path.realpath(__file__)[:-2] + 'json', 'w') as the_file:
print(template.to_json(), file=the_file)
print('Generated: ' + __file__)
{
"Description": "VPC stack | anil.io",
"Outputs": {
"Subnet1": {
"Description": "Subnet 1",
"Export": {
"Name": "Subnet1"
},
"Value": {
"Ref": "Subnet1"
}
},
"Subnet2": {
"Description": "Subnet 2",
"Export": {
"Name": "Subnet2"
},
"Value": {
"Ref": "Subnet2"
}
},
"VPC": {
"Description": "VPC",
"Export": {
"Name": "VPC"
},
"Value": {
"Ref": "VPC"
}
}
},
"Resources": {
"AttachGateway": {
"Properties": {
"InternetGatewayId": {
"Ref": "InternetGateway"
},
"VpcId": {
"Ref": "VPC"
}
},
"Type": "AWS::EC2::VPCGatewayAttachment"
},
"InboundHTTPNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "false",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "80",
"To": "80"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "100"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"InboundResponsePortsNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "false",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "1024",
"To": "65535"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "102"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"InboundSSHNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "false",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "22",
"To": "22"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "101"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"InternetGateway": {
"Properties": {
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"IGW",
" - ",
{
"Ref": "AWS::StackName"
}
]
]
}
}
]
},
"Type": "AWS::EC2::InternetGateway"
},
"NetworkAcl": {
"Properties": {
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"Network ACL",
" - ",
{
"Ref": "AWS::StackName"
}
]
]
}
}
],
"VpcId": {
"Ref": "VPC"
}
},
"Type": "AWS::EC2::NetworkAcl"
},
"OutBoundHTTPNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "true",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "80",
"To": "80"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "100"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"OutBoundHTTPSNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "true",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "443",
"To": "443"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "101"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"OutBoundResponsePortsNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "true",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "1024",
"To": "65535"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "102"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"OutBoundSESNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "true",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "587",
"To": "587"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "103"
},
"Type": "AWS::EC2::NetworkAclEntry"
},
"Route": {
"DependsOn": "AttachGateway",
"Properties": {
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {
"Ref": "InternetGateway"
},
"RouteTableId": {
"Ref": "RouteTable"
}
},
"Type": "AWS::EC2::Route"
},
"RouteTable": {
"Properties": {
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"Route Table",
" - ",
{
"Ref": "AWS::StackName"
}
]
]
}
}
],
"VpcId": {
"Ref": "VPC"
}
},
"Type": "AWS::EC2::RouteTable"
},
"Subnet1": {
"Properties": {
"AvailabilityZone": {
"Fn::Select": [
0,
{
"Fn::GetAZs": {
"Ref": "AWS::Region"
}
}
]
},
"CidrBlock": "10.0.1.0/24",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"10.0.1.0",
" - ",
{
"Ref": "AWS::StackName"
}
]
]
}
},
{
"Key": "Network",
"Value": "public"
}
],
"VpcId": {
"Ref": "VPC"
}
},
"Type": "AWS::EC2::Subnet"
},
"Subnet1NetworkAclAssociation": {
"Properties": {
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"SubnetId": {
"Ref": "Subnet1"
}
},
"Type": "AWS::EC2::SubnetNetworkAclAssociation"
},
"Subnet1RouteAssociation": {
"Properties": {
"RouteTableId": {
"Ref": "RouteTable"
},
"SubnetId": {
"Ref": "Subnet1"
}
},
"Type": "AWS::EC2::SubnetRouteTableAssociation"
},
"Subnet2": {
"Properties": {
"AvailabilityZone": {
"Fn::Select": [
1,
{
"Fn::GetAZs": {
"Ref": "AWS::Region"
}
}
]
},
"CidrBlock": "10.0.2.0/24",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"10.0.2.0",
" - ",
{
"Ref": "AWS::StackName"
}
]
]
}
},
{
"Key": "Network",
"Value": "public"
}
],
"VpcId": {
"Ref": "VPC"
}
},
"Type": "AWS::EC2::Subnet"
},
"Subnet2NetworkAclAssociation": {
"Properties": {
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"SubnetId": {
"Ref": "Subnet2"
}
},
"Type": "AWS::EC2::SubnetNetworkAclAssociation"
},
"Subnet2RouteAssociation": {
"Properties": {
"RouteTableId": {
"Ref": "RouteTable"
},
"SubnetId": {
"Ref": "Subnet2"
}
},
"Type": "AWS::EC2::SubnetRouteTableAssociation"
},
"VPC": {
"Properties": {
"CidrBlock": "10.0.0.0/16",
"EnableDnsHostnames": "true",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
"VPC",
" - ",
{
"Ref": "AWS::StackName"
}
]
]
}
},
{
"Key": "Network",
"Value": "public"
}
]
},
"Type": "AWS::EC2::VPC"
},
"inboundHttpsPortsNetworkAclEntry": {
"Properties": {
"CidrBlock": "0.0.0.0/0",
"Egress": "false",
"NetworkAclId": {
"Ref": "NetworkAcl"
},
"PortRange": {
"From": "443",
"To": "443"
},
"Protocol": "6",
"RuleAction": "allow",
"RuleNumber": "103"
},
"Type": "AWS::EC2::NetworkAclEntry"
}
}
}