1. Home

Anil Lakhman

< Blog />

VPC stack template for AWS Cloudformation

in

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.

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"
        }
    }
}