def add_egress(self, receiver, port):
        """
        Add an egress rule to this SecurityEnabledObject evaluating if it is a Security group or CIDR tuple
        ([0] = title, [1] = ip)
        Creates a Troposphere SecurityGroupEgress object
        AWS Cloud Formation:
        http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-security-group-egress.html
        Troposphere link: https://github.com/cloudtools/troposphere/blob/master/troposphere/ec2.py
        :param receiver: The SecurityEnabledObject that will be receiving traffic to this SecurityEnabledObject
        receiver[0] = title, receiver[1] = ip
        :param port: Port to send, and receive traffic on
        """
        receiver_title = receiver.title if hasattr(receiver, 'title') else receiver['name']

        if port == '-1':
            common = {'IpProtocol': 'tcp', 'FromPort': '0', 'ToPort': '65535', 'GroupId': self.security_group}
            name = self.title + 'AllTo' + receiver_title + 'All'
        else:
            common = {'IpProtocol': 'tcp', 'FromPort': port, 'ToPort': port, 'GroupId': self.security_group}
            name = self.title + port + 'To' + receiver_title + port

        egress = self.template.add_resource(ec2.SecurityGroupEgress(get_cf_friendly_name(name), **common))

        if isinstance(receiver, SecurityEnabledObject):
            egress.DestinationSecurityGroupId = receiver.security_group
        else:
            egress.CidrIp = receiver['cidr']
        self.egress.append(egress)
Example #2
0
    def create_simple_scaling_policy(self, scaling_policy_config):
        """
        Simple scaling policy based upon ec2 metrics

        heavy-load
        cpu > 45 for 1 period of 300 seconds add two instances, 45 second cooldown

        light-load
        cpu <= 15 for 6 periods of 300 seconds remove one instance, 120 second cooldown

        medium-load
        cpu >= 25 for 1 period of 300 seconds add one instance, 45 second cooldown

        [name]
        [metric_name] [comparison_operator] [threshold] [evaluation_periods] [period] [scaling_adjustment] [cooldown]

        https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html
        https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-policy.html
        :param scaling_policy_config: simple scaling policy config object
        """

        cf_name = self.trop_asg.title + get_cf_friendly_name(scaling_policy_config.name)

        scaling_policy = self.template.add_resource(ScalingPolicy(
            title=cf_name + 'Sp',
            AdjustmentType='ChangeInCapacity',
            AutoScalingGroupName=Ref(self.trop_asg),
            Cooldown=scaling_policy_config.cooldown,
            ScalingAdjustment=scaling_policy_config.scaling_adjustment,
        ))

        self.scaling_polices.append(scaling_policy)

        self.cw_alarms.append(self.template.add_resource(Alarm(
            title=cf_name + 'Cwa',
            AlarmActions=[Ref(scaling_policy), self.network_config.sns_topic],
            AlarmDescription=scaling_policy_config.description,
            AlarmName=cf_name,
            ComparisonOperator=scaling_policy_config.comparison_operator,
            Dimensions=[MetricDimension(
                Name='AutoScalingGroupName',
                Value=Ref(self.trop_asg)
            )],
            EvaluationPeriods=scaling_policy_config.evaluation_periods,
            MetricName=scaling_policy_config.metric_name,
            Namespace='AWS/EC2',
            Period=scaling_policy_config.period,
            Statistic='Average',
            Threshold=scaling_policy_config.threshold,
            OKActions=[self.network_config.sns_topic]
        )))
Example #3
0
def test_highly_available_nat_stack():
    """ Test for nat gateway configuration"""

    stack = create_stack(nat_highly_available=True)

    assert_equals(stack.code_deploy_service_role, code_deploy_service_role)
    assert_equals(stack.keypair, keypair)
    assert_equals(stack.availability_zones, availability_zones)
    assert_equals(stack.vpc_cidr, vpc_cidr)
    [
        assert_equals(stack.home_cidrs[num], home_cidrs[num])
        for num in range(len(home_cidrs))
    ]
    assert_equals(stack.public_cidr, {'name': 'PublicIp', 'cidr': '0.0.0.0/0'})

    assert_equals(stack.internet_gateway.title, 'Ig')
    assert_is(type(stack.internet_gateway.Tags), Tags)

    assert_equals(stack.gateway_attachment.title, 'IgAtch')
    assert_is(type(stack.gateway_attachment.VpcId), Ref)
    assert_is(type(stack.gateway_attachment.InternetGatewayId), Ref)

    assert_equals(stack.public_route_table.title, 'PubRouteTable')
    assert_is(type(stack.public_route_table.VpcId), Ref)
    assert_is(type(stack.public_route_table.Tags), Tags)

    for az in availability_zones:
        assert_equals(stack.private_route_tables[az].title,
                      get_cf_friendly_name(az) + 'PriRouteTable')
        assert_is(type(stack.private_route_tables[az].VpcId), Ref)
        assert_is(type(stack.private_route_tables[az].Tags), Tags)

    assert_equals(len(stack.nat_gateways), len(availability_zones))
    assert_equals(stack.jump.single.SourceDestCheck, 'true')

    for num in range(len(availability_zones)):
        # For public subnets
        public_subnet = stack.public_subnets[num]
        assert_equals(public_subnet.CidrBlock,
                      ''.join(['10.0.', str(num), '.0/24']))

        # For private subnets
        private_subnet = stack.private_subnets[num]
        assert_equals(private_subnet.CidrBlock,
                      ''.join(['10.0.', str(num + 100), '.0/24']))

    assert_equals(len(stack.units), 7)
Example #4
0
def test_tree():
    """ Test stack structure
    """
    tee = create_tree()
    assert_equals(tee.keypair, keypair)
    assert_equals(tee.availability_zones, availability_zones)
    assert_equals(tee.vpc_cidr, vpc_cidr)
    [
        assert_equals(tee.home_cidrs[num], home_cidrs[num])
        for num in range(len(home_cidrs))
    ]
    assert_equals(tee.public_cidr, {'name': 'PublicIp', 'cidr': '0.0.0.0/0'})

    assert_equals(tee.internet_gateway.title, 'Ig')
    assert_is(type(tee.internet_gateway.Tags), Tags)

    assert_equals(tee.gateway_attachment.title, 'IgAtch')
    assert_is(type(tee.gateway_attachment.VpcId), Ref)
    assert_is(type(tee.gateway_attachment.InternetGatewayId), Ref)

    assert_equals(tee.public_route_table.title, 'PubRouteTable')
    assert_is(type(tee.public_route_table.VpcId), Ref)
    assert_is(type(tee.public_route_table.Tags), Tags)

    for az in availability_zones:
        assert_equals(tee.private_route_tables[az].title,
                      get_cf_friendly_name(az) + 'PriRouteTable')
        assert_is(type(tee.private_route_tables[az].VpcId), Ref)
        assert_is(type(tee.private_route_tables[az].Tags), Tags)

    assert_equals(tee.nat.single.SourceDestCheck, 'false')
    assert_equals(tee.jump.single.SourceDestCheck, 'true')

    for num in range(len(availability_zones)):
        # For public subnets
        public_subnet = tee.public_subnets[num]
        assert_equals(public_subnet.CidrBlock,
                      ''.join(['10.0.', str(num), '.0/24']))

        # For private subnets
        private_subnet = tee.private_subnets[num]
        assert_equals(private_subnet.CidrBlock,
                      ''.join(['10.0.', str(num + 100), '.0/24']))
def test_highly_available_nat_stack():
    """ Test for nat gateway configuration"""

    stack = create_stack(nat_highly_available=True)

    assert_equals(stack.code_deploy_service_role, code_deploy_service_role)
    assert_equals(stack.keypair, keypair)
    assert_equals(stack.availability_zones, availability_zones)
    assert_equals(stack.vpc_cidr, vpc_cidr)
    [assert_equals(stack.home_cidrs[num], home_cidrs[num]) for num in range(len(home_cidrs))]
    assert_equals(stack.public_cidr, {'name': 'PublicIp', 'cidr': '0.0.0.0/0'})

    assert_equals(stack.internet_gateway.title, 'Ig')
    assert_is(type(stack.internet_gateway.Tags), Tags)

    assert_equals(stack.gateway_attachment.title, 'IgAtch')
    assert_is(type(stack.gateway_attachment.VpcId), Ref)
    assert_is(type(stack.gateway_attachment.InternetGatewayId), Ref)

    assert_equals(stack.public_route_table.title, 'PubRouteTable')
    assert_is(type(stack.public_route_table.VpcId), Ref)
    assert_is(type(stack.public_route_table.Tags), Tags)

    for az in availability_zones:
        assert_equals(stack.private_route_tables[az].title, get_cf_friendly_name(az) + 'PriRouteTable')
        assert_is(type(stack.private_route_tables[az].VpcId), Ref)
        assert_is(type(stack.private_route_tables[az].Tags), Tags)

    assert_equals(len(stack.nat_gateways), len(availability_zones))
    assert_equals(stack.jump.single.SourceDestCheck, 'true')

    for num in range(len(availability_zones)):
        # For public subnets
        public_subnet = stack.public_subnets[num]
        assert_equals(public_subnet.CidrBlock, ''.join(['10.0.', str(num), '.0/24']))

        # For private subnets
        private_subnet = stack.private_subnets[num]
        assert_equals(private_subnet.CidrBlock, ''.join(['10.0.', str(num + 100), '.0/24']))

    assert_equals(len(stack.units), 7)
def test_tree():
    """ Test stack structure
    """
    tee = create_tree()
    assert_equals(tee.keypair, keypair)
    assert_equals(tee.availability_zones, availability_zones)
    assert_equals(tee.vpc_cidr, vpc_cidr)
    [assert_equals(tee.home_cidrs[num], home_cidrs[num]) for num in range(len(home_cidrs))]
    assert_equals(tee.public_cidr, {'name': 'PublicIp', 'cidr': '0.0.0.0/0'})

    assert_equals(tee.internet_gateway.title, 'Ig')
    assert_is(type(tee.internet_gateway.Tags), Tags)

    assert_equals(tee.gateway_attachment.title, 'IgAtch')
    assert_is(type(tee.gateway_attachment.VpcId), Ref)
    assert_is(type(tee.gateway_attachment.InternetGatewayId), Ref)

    assert_equals(tee.public_route_table.title, 'PubRouteTable')
    assert_is(type(tee.public_route_table.VpcId), Ref)
    assert_is(type(tee.public_route_table.Tags), Tags)

    for az in availability_zones:
        assert_equals(tee.private_route_tables[az].title, get_cf_friendly_name(az) + 'PriRouteTable')
        assert_is(type(tee.private_route_tables[az].VpcId), Ref)
        assert_is(type(tee.private_route_tables[az].Tags), Tags)

    assert_equals(tee.nat.single.SourceDestCheck, 'false')
    assert_equals(tee.jump.single.SourceDestCheck, 'true')

    for num in range(len(availability_zones)):
        # For public subnets
        public_subnet = tee.public_subnets[num]
        assert_equals(public_subnet.CidrBlock, ''.join(['10.0.', str(num), '.0/24']))

        # For private subnets
        private_subnet = tee.private_subnets[num]
        assert_equals(private_subnet.CidrBlock, ''.join(['10.0.', str(num + 100), '.0/24']))
Example #7
0
    def __init__(self, keypair, availability_zones, vpc_cidr, home_cidrs,
                 public_cidr, jump_image_id, jump_instance_type, nat_image_id,
                 nat_instance_type, public_hosted_zone_name,
                 private_hosted_zone_name, iam_instance_profile_arn,
                 owner_emails, nat_highly_available, ec2_scheduled_shutdown):
        """
        Create a vpc, nat, jumphost, internet gateway, public/private route tables, public/private subnets
         and collection of Amazonia units
        AWS CloudFormation -
         http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
        Troposphere - https://github.com/cloudtools/troposphere/blob/master/troposphere/ec2.py
        :param keypair: ssh keypair to be used throughout stack
        :param availability_zones: availability zones to use
        :param vpc_cidr: cidr pattern for vpc
        :param home_cidrs: a list of tuple objects of 'title'(0) and 'ip'(1) to be used
         to create ingress rules for ssh to jumpboxes from home/office/company premises
        :param public_cidr: a cidr to be treated as a public location. (eg 0.0.0.0/0)
        :param jump_image_id: AMI for jumphost
        :param jump_instance_type: instance type for jumphost
        :param nat_image_id: AMI for nat
        :param nat_instance_type: instance type for nat
        :param public_hosted_zone_name: A string containing the name of the Route 53 hosted zone to create public record
        sets in.
        :param private_hosted_zone_name: name of private hosted zone to create
        :param iam_instance_profile_arn: the ARN for an IAM instance profile that enables cloudtrail access for logging
        :param owner_emails: a list of emails for owners of this stack. Used for alerting.
        :param nat_highly_available: True/False for whether or not to use a series of NAT gateways or a single NAT
        :param ec2_scheduled_shutdown: True/False for whether to schedule shutdown for EC2 instances outside work hours
        """

        super(Network, self).__init__()
        # set parameters
        self.keypair = keypair
        self.availability_zones = availability_zones
        self.vpc_cidr = vpc_cidr
        self.home_cidrs = home_cidrs
        self.public_cidr = public_cidr
        self.public_hosted_zone_name = public_hosted_zone_name
        self.private_hosted_zone_name = private_hosted_zone_name
        self.jump_image_id = jump_image_id
        self.jump_instance_type = jump_instance_type
        self.nat_image_id = nat_image_id
        self.nat_instance_type = nat_instance_type
        self.owner_emails = owner_emails if owner_emails else []
        self.nat_highly_available = nat_highly_available
        self.iam_instance_profile_arn = iam_instance_profile_arn
        self.ec2_scheduled_shutdown = ec2_scheduled_shutdown

        # initialize object references
        self.template = Template()
        self.private_subnets = []
        self.public_subnets = []
        self.public_subnet_mapping = {}
        self.vpc = None
        self.private_hosted_zone = None
        self.internet_gateway = None
        self.gateway_attachment = None
        self.public_route_table = None
        self.private_route_tables = {}
        self.nat = None
        self.nat_gateways = []
        self.jump = None
        self.private_route = None
        self.public_route = None
        self.sns_topic = None

        # Add VPC and Internet Gateway with Attachment
        vpc_name = 'Vpc'
        self.vpc = Ref(
            self.template.add_resource(
                ec2.VPC(vpc_name,
                        CidrBlock=self.vpc_cidr['cidr'],
                        EnableDnsSupport='true',
                        EnableDnsHostnames='true',
                        Tags=Tags(Name=Join(
                            '', [Ref('AWS::StackName'), '-', vpc_name])))))
        self.private_hosted_zone = HostedZone(self.template,
                                              self.private_hosted_zone_name,
                                              vpcs=[self.vpc])
        ig_name = 'Ig'
        self.internet_gateway = self.template.add_resource(
            ec2.InternetGateway(
                ig_name,
                Tags=Tags(
                    Name=Join('', [Ref('AWS::StackName'), '-', ig_name])),
                DependsOn=vpc_name))

        self.gateway_attachment = self.template.add_resource(
            ec2.VPCGatewayAttachment(self.internet_gateway.title + 'Atch',
                                     VpcId=self.vpc,
                                     InternetGatewayId=Ref(
                                         self.internet_gateway),
                                     DependsOn=self.internet_gateway.title))

        # Add Public Route Table
        public_rt_name = 'PubRouteTable'
        self.public_route_table = self.template.add_resource(
            ec2.RouteTable(
                public_rt_name,
                VpcId=self.vpc,
                Tags=Tags(Name=Join(
                    '', [Ref('AWS::StackName'), '-', public_rt_name]))))

        # Add Public and Private Subnets and Private Route Table
        for az in self.availability_zones:
            private_rt_name = get_cf_friendly_name(az) + 'PriRouteTable'
            private_route_table = self.template.add_resource(
                ec2.RouteTable(
                    private_rt_name,
                    VpcId=self.vpc,
                    Tags=Tags(Name=Join(
                        '', [Ref('AWS::StackName'), '-', private_rt_name]))))
            self.private_route_tables[az] = private_route_table

            self.private_subnets.append(
                Subnet(template=self.template,
                       route_table=private_route_table,
                       az=az,
                       vpc=self.vpc,
                       is_public=False,
                       cidr=self.generate_subnet_cidr(
                           is_public=False)).trop_subnet)
            public_subnet = Subnet(
                template=self.template,
                route_table=self.public_route_table,
                az=az,
                vpc=self.vpc,
                is_public=True,
                cidr=self.generate_subnet_cidr(is_public=True)).trop_subnet
            self.public_subnets.append(public_subnet)
            self.public_subnet_mapping[az] = Ref(public_subnet)

        self.sns_topic = SNS(self.template)

        for email in self.owner_emails:
            self.sns_topic.add_subscription(email, 'email')

        jump_config = SingleInstanceConfig(
            keypair=self.keypair,
            si_image_id=self.jump_image_id,
            si_instance_type=self.jump_instance_type,
            subnet=self.public_subnet_mapping[availability_zones[0]],
            vpc=self.vpc,
            public_hosted_zone_name=self.public_hosted_zone_name,
            instance_dependencies=self.gateway_attachment.title,
            iam_instance_profile_arn=self.iam_instance_profile_arn,
            is_nat=False,
            sns_topic=self.sns_topic,
            availability_zone=availability_zones[0],
            ec2_scheduled_shutdown=self.ec2_scheduled_shutdown)

        # Add Jumpbox and NAT and associated security group ingress and egress rules
        self.jump = SingleInstance(title='Jump',
                                   template=self.template,
                                   single_instance_config=jump_config)

        [
            self.jump.add_ingress(sender=home_cidr, port='22')
            for home_cidr in self.home_cidrs
        ]
        self.jump.add_egress(receiver=self.public_cidr, port='-1')

        if self.nat_highly_available:
            for public_subnet in self.public_subnets:
                az = public_subnet.AvailabilityZone
                ip_address = self.template.add_resource(
                    EIP(get_cf_friendly_name(az) + 'NatGwEip',
                        DependsOn=self.gateway_attachment.title,
                        Domain='vpc'))

                nat_gateway = self.template.add_resource(
                    NatGateway(get_cf_friendly_name(az) + 'NatGw',
                               AllocationId=GetAtt(ip_address, 'AllocationId'),
                               SubnetId=Ref(public_subnet),
                               DependsOn=self.gateway_attachment.title))
                self.nat_gateways.append(nat_gateway)

                self.template.add_resource(
                    ec2.Route(get_cf_friendly_name(az) + 'PriRoute',
                              NatGatewayId=Ref(nat_gateway),
                              RouteTableId=Ref(self.private_route_tables[az]),
                              DestinationCidrBlock=self.public_cidr['cidr'],
                              DependsOn=self.gateway_attachment.title))

        else:
            nat_config = SingleInstanceConfig(
                keypair=self.keypair,
                si_image_id=self.nat_image_id,
                si_instance_type=self.nat_instance_type,
                subnet=self.public_subnet_mapping[availability_zones[0]],
                vpc=self.vpc,
                is_nat=True,
                instance_dependencies=self.gateway_attachment.title,
                iam_instance_profile_arn=self.iam_instance_profile_arn,
                public_hosted_zone_name=None,
                sns_topic=self.sns_topic,
                availability_zone=availability_zones[0],
                ec2_scheduled_shutdown=self.ec2_scheduled_shutdown)

            self.nat = SingleInstance(title='Nat',
                                      template=self.template,
                                      single_instance_config=nat_config)

            self.nat.add_egress(receiver=self.public_cidr, port='-1')
            self.nat.add_ingress(sender=self.vpc_cidr, port='-1')
            for az in self.availability_zones:
                self.template.add_resource(
                    ec2.Route(get_cf_friendly_name(az) + 'PriRoute',
                              InstanceId=Ref(self.nat.single),
                              RouteTableId=Ref(self.private_route_tables[az]),
                              DestinationCidrBlock=self.public_cidr['cidr'],
                              DependsOn=self.gateway_attachment.title))
        # Add Public Route
        self.public_route = self.template.add_resource(
            ec2.Route('PubRoute',
                      GatewayId=Ref(self.internet_gateway),
                      RouteTableId=Ref(self.public_route_table),
                      DestinationCidrBlock=self.public_cidr['cidr'],
                      DependsOn=self.gateway_attachment.title))
    def __init__(self, keypair, availability_zones, vpc_cidr, home_cidrs, public_cidr, jump_image_id,
                 jump_instance_type, nat_image_id, nat_instance_type, public_hosted_zone_name, private_hosted_zone_name,
                 iam_instance_profile_arn, owner_emails, nat_highly_available, ec2_scheduled_shutdown):
        """
        Create a vpc, nat, jumphost, internet gateway, public/private route tables, public/private subnets
         and collection of Amazonia units
        AWS CloudFormation -
         http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
        Troposphere - https://github.com/cloudtools/troposphere/blob/master/troposphere/ec2.py
        :param keypair: ssh keypair to be used throughout stack
        :param availability_zones: availability zones to use
        :param vpc_cidr: cidr pattern for vpc
        :param home_cidrs: a list of tuple objects of 'title'(0) and 'ip'(1) to be used
         to create ingress rules for ssh to jumpboxes from home/office/company premises
        :param public_cidr: a cidr to be treated as a public location. (eg 0.0.0.0/0)
        :param jump_image_id: AMI for jumphost
        :param jump_instance_type: instance type for jumphost
        :param nat_image_id: AMI for nat
        :param nat_instance_type: instance type for nat
        :param public_hosted_zone_name: A string containing the name of the Route 53 hosted zone to create public record
        sets in.
        :param private_hosted_zone_name: name of private hosted zone to create
        :param iam_instance_profile_arn: the ARN for an IAM instance profile that enables cloudtrail access for logging
        :param owner_emails: a list of emails for owners of this stack. Used for alerting.
        :param nat_highly_available: True/False for whether or not to use a series of NAT gateways or a single NAT
        :param ec2_scheduled_shutdown: True/False for whether to schedule shutdown for EC2 instances outside work hours
        """

        super(Network, self).__init__()
        # set parameters
        self.keypair = keypair
        self.availability_zones = availability_zones
        self.vpc_cidr = vpc_cidr
        self.home_cidrs = home_cidrs
        self.public_cidr = public_cidr
        self.public_hosted_zone_name = public_hosted_zone_name
        self.private_hosted_zone_name = private_hosted_zone_name
        self.jump_image_id = jump_image_id
        self.jump_instance_type = jump_instance_type
        self.nat_image_id = nat_image_id
        self.nat_instance_type = nat_instance_type
        self.owner_emails = owner_emails if owner_emails else []
        self.nat_highly_available = nat_highly_available
        self.iam_instance_profile_arn = iam_instance_profile_arn
        self.ec2_scheduled_shutdown = ec2_scheduled_shutdown

        # initialize object references
        self.template = Template()
        self.private_subnets = []
        self.public_subnets = []
        self.public_subnet_mapping = {}
        self.vpc = None
        self.private_hosted_zone = None
        self.internet_gateway = None
        self.gateway_attachment = None
        self.public_route_table = None
        self.private_route_tables = {}
        self.nat = None
        self.nat_gateways = []
        self.jump = None
        self.private_route = None
        self.public_route = None
        self.sns_topic = None

        # Add VPC and Internet Gateway with Attachment
        vpc_name = 'Vpc'
        self.vpc = Ref(self.template.add_resource(
            ec2.VPC(
                vpc_name,
                CidrBlock=self.vpc_cidr['cidr'],
                EnableDnsSupport='true',
                EnableDnsHostnames='true',
                Tags=Tags(
                    Name=Join('', [Ref('AWS::StackName'), '-', vpc_name])
                )
            )))
        self.private_hosted_zone = HostedZone(self.template, self.private_hosted_zone_name, vpcs=[self.vpc])
        ig_name = 'Ig'
        self.internet_gateway = self.template.add_resource(
            ec2.InternetGateway(ig_name,
                                Tags=Tags(Name=Join('', [Ref('AWS::StackName'), '-', ig_name])),
                                DependsOn=vpc_name))

        self.gateway_attachment = self.template.add_resource(
            ec2.VPCGatewayAttachment(self.internet_gateway.title + 'Atch',
                                     VpcId=self.vpc,
                                     InternetGatewayId=Ref(self.internet_gateway),
                                     DependsOn=self.internet_gateway.title))

        # Add Public Route Table
        public_rt_name = 'PubRouteTable'
        self.public_route_table = self.template.add_resource(
            ec2.RouteTable(public_rt_name, VpcId=self.vpc,
                           Tags=Tags(Name=Join('', [Ref('AWS::StackName'), '-', public_rt_name]))))

        # Add Public and Private Subnets and Private Route Table
        for az in self.availability_zones:
            private_rt_name = get_cf_friendly_name(az) + 'PriRouteTable'
            private_route_table = self.template.add_resource(
                ec2.RouteTable(private_rt_name, VpcId=self.vpc,
                               Tags=Tags(Name=Join('', [Ref('AWS::StackName'), '-', private_rt_name]))))
            self.private_route_tables[az] = private_route_table

            self.private_subnets.append(Subnet(template=self.template,
                                               route_table=private_route_table,
                                               az=az,
                                               vpc=self.vpc,
                                               is_public=False,
                                               cidr=self.generate_subnet_cidr(is_public=False)).trop_subnet)
            public_subnet = Subnet(template=self.template,
                                   route_table=self.public_route_table,
                                   az=az,
                                   vpc=self.vpc,
                                   is_public=True,
                                   cidr=self.generate_subnet_cidr(is_public=True)).trop_subnet
            self.public_subnets.append(public_subnet)
            self.public_subnet_mapping[az] = Ref(public_subnet)

        self.sns_topic = SNS(self.template)

        for email in self.owner_emails:
            self.sns_topic.add_subscription(email, 'email')

        jump_config = SingleInstanceConfig(
            keypair=self.keypair,
            si_image_id=self.jump_image_id,
            si_instance_type=self.jump_instance_type,
            subnet=self.public_subnet_mapping[availability_zones[0]],
            vpc=self.vpc,
            public_hosted_zone_name=self.public_hosted_zone_name,
            instance_dependencies=self.gateway_attachment.title,
            iam_instance_profile_arn=self.iam_instance_profile_arn,
            is_nat=False,
            sns_topic=self.sns_topic,
            availability_zone=availability_zones[0],
            ec2_scheduled_shutdown=self.ec2_scheduled_shutdown
        )

        # Add Jumpbox and NAT and associated security group ingress and egress rules
        self.jump = SingleInstance(
            title='Jump',
            template=self.template,
            single_instance_config=jump_config
        )

        [self.jump.add_ingress(sender=home_cidr, port='22') for home_cidr in self.home_cidrs]
        self.jump.add_egress(receiver=self.public_cidr, port='-1')

        if self.nat_highly_available:
            for public_subnet in self.public_subnets:
                az = public_subnet.AvailabilityZone
                ip_address = self.template.add_resource(
                    EIP(get_cf_friendly_name(az) + 'NatGwEip',
                        DependsOn=self.gateway_attachment.title,
                        Domain='vpc'
                        ))

                nat_gateway = self.template.add_resource(NatGateway(get_cf_friendly_name(az) + 'NatGw',
                                                                    AllocationId=GetAtt(ip_address, 'AllocationId'),
                                                                    SubnetId=Ref(public_subnet),
                                                                    DependsOn=self.gateway_attachment.title
                                                                    ))
                self.nat_gateways.append(nat_gateway)

                self.template.add_resource(ec2.Route(get_cf_friendly_name(az) + 'PriRoute',
                                                     NatGatewayId=Ref(nat_gateway),
                                                     RouteTableId=Ref(self.private_route_tables[az]),
                                                     DestinationCidrBlock=self.public_cidr['cidr'],
                                                     DependsOn=self.gateway_attachment.title))

        else:
            nat_config = SingleInstanceConfig(
                keypair=self.keypair,
                si_image_id=self.nat_image_id,
                si_instance_type=self.nat_instance_type,
                subnet=self.public_subnet_mapping[availability_zones[0]],
                vpc=self.vpc,
                is_nat=True,
                instance_dependencies=self.gateway_attachment.title,
                iam_instance_profile_arn=self.iam_instance_profile_arn,
                public_hosted_zone_name=None,
                sns_topic=self.sns_topic,
                availability_zone=availability_zones[0],
                ec2_scheduled_shutdown=self.ec2_scheduled_shutdown
            )

            self.nat = SingleInstance(
                title='Nat',
                template=self.template,
                single_instance_config=nat_config
            )

            self.nat.add_egress(receiver=self.public_cidr, port='-1')
            self.nat.add_ingress(sender=self.vpc_cidr, port='-1')
            for az in self.availability_zones:
                self.template.add_resource(ec2.Route(get_cf_friendly_name(az) + 'PriRoute',
                                                     InstanceId=Ref(self.nat.single),
                                                     RouteTableId=Ref(self.private_route_tables[az]),
                                                     DestinationCidrBlock=self.public_cidr['cidr'],
                                                     DependsOn=self.gateway_attachment.title))
        # Add Public Route
        self.public_route = self.template.add_resource(ec2.Route('PubRoute',
                                                                 GatewayId=Ref(self.internet_gateway),
                                                                 RouteTableId=Ref(self.public_route_table),
                                                                 DestinationCidrBlock=self.public_cidr['cidr'],
                                                                 DependsOn=self.gateway_attachment.title))