def init(self): # Network Stack Templates # VPC Stack vpc_config = self.env_ctx.env_region.network.vpc if vpc_config == None: # NetworkEnvironment with no network - serverless return network_config = get_parent_by_interface(vpc_config, schemas.INetwork) vpc_config.resolve_ref_obj = self vpc_config.private_hosted_zone.resolve_ref_obj = self self.vpc_stack = self.add_new_stack( self.region, vpc_config, paco.cftemplates.VPC, stack_tags=StackTags(self.stack_tags), ) # Segments self.segment_list = [] self.segment_dict = {} segments = network_config.vpc.segments for segment in segments.values(): segment.resolve_ref_obj = self segment_stack = self.add_new_stack( self.region, segment, paco.cftemplates.Segment, stack_tags=StackTags(self.stack_tags), stack_orders=[StackOrder.PROVISION], extra_context={'env_ctx': self.env_ctx}, ) self.segment_dict[segment.name] = segment_stack self.segment_list.append(segment_stack) # Security Groups sg_config = network_config.vpc.security_groups self.sg_list = [] self.sg_dict = {} # EC2 NATGateway Security Groups # Creates a security group for each Availability Zone in the segment sg_nat_id = 'bastion_nat_' + utils.md5sum(str_data='gateway')[:8] for nat_config in vpc_config.nat_gateway.values(): if nat_config.is_enabled() == False: continue if nat_config.type == 'EC2': sg_nat_config_dict = {} if sg_nat_id not in sg_config.keys(): sg_config[sg_nat_id] = paco.models.networks.SecurityGroups( sg_nat_id, sg_config) for az_idx in range(1, network_config.availability_zones + 1): sg_nat_config_dict['enabled'] = True sg_nat_config_dict['ingress'] = [] for route_segment in nat_config.default_route_segments: route_segment_id = route_segment.split('.')[-1] az_cidr = getattr( vpc_config.segments[route_segment_id], f"az{az_idx}_cidr") sg_nat_config_dict['ingress'].append({ 'name': 'SubnetAZ', 'cidr_ip': az_cidr, 'protocol': '-1' }) sg_nat_config_dict['egress'] = [{ 'name': 'ANY', 'cidr_ip': '0.0.0.0/0', 'protocol': '-1' }] sg_nat_rule_id = nat_config.name + '_az' + str(az_idx) sg_config[sg_nat_id][ sg_nat_rule_id] = paco.models.networks.SecurityGroup( sg_nat_rule_id, vpc_config) paco.models.loader.apply_attributes_from_config( sg_config[sg_nat_id][sg_nat_rule_id], sg_nat_config_dict) # Declared Security Groups for sg_id in sg_config: # Set resolve_ref_obj for sg_obj_id in sg_config[sg_id]: sg_config[sg_id][sg_obj_id].resolve_ref_obj = self sg_stack = self.add_new_stack( self.region, sg_config[sg_id], paco.cftemplates.SecurityGroups, stack_tags=StackTags(self.stack_tags), extra_context={ 'env_ctx': self.env_ctx, 'template_type': 'Groups' }, ) self.sg_list.append(sg_stack) self.sg_dict[sg_id] = sg_stack # Ingress/Egress Stacks for sg_id in sg_config: self.add_new_stack(self.region, sg_config[sg_id], paco.cftemplates.SecurityGroups, stack_tags=StackTags(self.stack_tags), extra_context={ 'env_ctx': self.env_ctx, 'template_type': 'Rules' }) # Wait for Segment Stacks for segment_stack in self.segment_list: self.add_stack_order(segment_stack, [StackOrder.WAIT]) # VPC Peering Stack if vpc_config.peering != None: peering_config = self.env_ctx.env_region.network.vpc.peering for peer_id in peering_config.keys(): peer_config = vpc_config.peering[peer_id] peer_config.resolve_ref_obj = self # Add role to the target network account if peer_config.network_environment != None and peer_config.peer_type == 'accepter': netenv_ref = Reference(peer_config.network_environment + '.network') requester_netenv_config = netenv_ref.resolve( self.paco_ctx.project) requester_account_id = self.paco_ctx.get_ref( requester_netenv_config.aws_account + '.id') accepter_vpc_id = self.paco_ctx.get_ref( vpc_config.paco_ref + '.id') # Only create the role if we are cross account if self.account_ctx.id != requester_account_id: self.gen_vpc_peering_accepter_role( peer_config, vpc_config, accepter_vpc_id, requester_account_id) self.peering_stack = self.add_new_stack( self.region, vpc_config.peering, paco.cftemplates.VPCPeering, stack_tags=StackTags(self.stack_tags), ) # NAT Gateway self.nat_list = [] for nat_config in vpc_config.nat_gateway.values(): if sg_nat_id in sg_config.keys(): nat_sg_config = sg_config[sg_nat_id] else: nat_sg_config = None # We now disable the NAT Gateway in the template so that we can delete it and recreate it when disabled. nat_stack = self.add_new_stack( self.region, nat_config, paco.cftemplates.NATGateway, stack_tags=StackTags(self.stack_tags), stack_orders=[StackOrder.PROVISION], extra_context={'nat_sg_config': nat_sg_config}, ) self.nat_list.append(nat_stack) for nat_stack in self.nat_list: self.add_stack_order(nat_stack, [StackOrder.WAIT]) # VPC Endpoints vpc_endpoints_stack = self.add_new_stack( self.region, vpc_config, paco.cftemplates.VPCEndpoints, stack_tags=StackTags(self.stack_tags), stack_orders=[StackOrder.PROVISION]) self.add_stack_order(vpc_endpoints_stack, [StackOrder.WAIT])
def ec2_nat_gateway(self, network_config, nat_sg_config, nat_sg_config_ref, nat_config): nat_az = nat_config.availability_zone nat_segment = nat_config.segment.split('.')[-1] ec2_resource = {} for az_idx in range(1, network_config.availability_zones + 1): # Add security groups created for NAT Bastions nat_security_groups = [] nat_security_groups.extend(nat_config.security_groups) if nat_az == 'all': nat_sg_id = nat_config.name + "_az" + str(az_idx) nat_security_groups.append('paco.ref ' + nat_sg_config_ref + '.' + nat_sg_id) elif az_idx == int(nat_config.availability_zone): for nat_sg_id in nat_sg_config.keys(): nat_security_groups.append('paco.ref ' + nat_sg_config_ref + '.' + nat_sg_id) if nat_az == 'all' or nat_az == str(az_idx): security_group_list_param = self.create_cfn_ref_list_param( param_type='List<AWS::EC2::SecurityGroup::Id>', name='NATSecurityGroupListAZ' + str(az_idx), description= 'List of security group ids to attach to the instances.', value=nat_security_groups, ref_attribute='id', ) subnet_id_param = self.create_cfn_parameter( name=self.create_cfn_logical_id_join( str_list=['SubnetIdAZ', str(az_idx), nat_segment], camel_case=True), param_type='String', description='SubnetId to launch an EC2 NAT instance', value=nat_config.segment + '.az' + str(az_idx) + '.subnet_id', ) ref_parts = nat_config.paco_ref_parts.split('.') instance_name = utils.big_join(str_list=[ ref_parts[1], ref_parts[2], 'NGW', nat_config.name, 'AZ' + str(az_idx) ], separator_ch='-', camel_case=True) # ToDo: expose latest ami id as an API and call it directly # SLOW: takes a couple seconds to resolve this every Paco run latest_image_ref = Reference( 'paco.ref function.aws.ec2.ami.latest.amazon-linux-nat') latest_image_ref.set_region(self.aws_region) nat_ami_id = latest_image_ref.resolve(self.paco_ctx.project, self.account_ctx) ec2_resource[az_idx] = troposphere.ec2.Instance( title=self.create_cfn_logical_id_join( str_list=['EC2NATInstance', str(az_idx)], camel_case=True), template=self.template, SubnetId=troposphere.Ref(subnet_id_param), ImageId=nat_ami_id, InstanceType=nat_config.ec2_instance_type, KeyName=self.paco_ctx.get_ref(nat_config.ec2_key_pair + '.keypair_name'), SecurityGroupIds=troposphere.Ref( security_group_list_param), SourceDestCheck=False, Tags=troposphere.ec2.Tags(Name=instance_name)) ec2_instance_id_output = troposphere.Output( title=ec2_resource[az_idx].title + 'Id', Description="EC2 NAT Instance Id", Value=troposphere.Ref(ec2_resource[az_idx])) self.template.add_output(ec2_instance_id_output) troposphere.ec2.EIP(title=self.create_cfn_logical_id_join( str_list=['ElasticIP', str(az_idx)], camel_case=True), template=self.template, Domain='vpc', InstanceId=troposphere.Ref( ec2_resource[az_idx])) self.register_stack_output_config( nat_config.paco_ref_parts + ".ec2.az" + str(az_idx), ec2_instance_id_output.title) # Add DefaultRoute to the route tables in each AZ for segment_ref in nat_config.default_route_segments: segment_id = segment_ref.split('.')[-1] # Routes for az_idx in range(1, network_config.availability_zones + 1): if nat_config.availability_zone == 'all': instance_id_ref = troposphere.Ref(ec2_resource[az_idx]) else: instance_id_ref = troposphere.Ref( ec2_resource[int(nat_az)]) route_table_id_param = self.create_cfn_parameter( name=self.create_cfn_logical_id_join( str_list=['RouteTable', segment_id, 'AZ', str(az_idx)], camel_case=True), param_type='String', description='RouteTable ID for ' + segment_id + ' AZ' + str(az_idx), value=segment_ref + ".az{}.route_table.id".format(az_idx), ) troposphere.ec2.Route( title="EC2NATRouteAZ" + str(az_idx), template=self.template, DestinationCidrBlock="0.0.0.0/0", InstanceId=instance_id_ref, RouteTableId=troposphere.Ref(route_table_id_param))