def amazon_security_group(name, vpc): """Create a security group authorizing access to aws services. :param vpc: vpc in which to create the group :type vpc: VPC :return: a security group :rtype: SecurityGroup """ ip_ranges = requests.get(IP_RANGES_URL).json()['prefixes'] # Retrieve first the complete list of ipv4 ip ranges for a given region amazon_ip_ranges = { k['ip_prefix'] for k in ip_ranges if k['region'] == vpc.region and 'ip_prefix' in k and k['service'] == 'AMAZON' } # Sustract the list of ip ranges corresponding to EC2 instances ec2_ip_ranges = { k['ip_prefix'] for k in ip_ranges if k['region'] == vpc.region and 'ip_prefix' in k and k['service'] == 'EC2' } amazon_ip_ranges -= ec2_ip_ranges # Authorize https on the resulting list of ip ranges # Note: the limit of rules per security group is set to 50 at AWS. # In case the number of ip ranges returned by Amazon would be greater # than that there would be need to split into several security groups sg = SecurityGroup(name, vpc, description='Allow acces to amazon services') for ip_range in amazon_ip_ranges: sg.add_rule(Ipv4EgressRule('https', ip_range)) return sg
def github_security_groups(name, vpc, protocol): """Create a dict of security group authorizing access to github services. As the number of rules per security group is limited to 50, we create blocks of 50 rules. :param vpc: vpc in which to create the group :type vpc: VPC :param protocol: protocol to allow (https, ssh) :type protocol: str :return: a dict of security groups indexed by name :rtype: dict(str, SecurityGroup) """ ip_ranges = requests.get("https://api.github.com/meta").json()["git"] # Authorize ssh on the resulting list of ip ranges sgs = {} i = 0 limit = 50 sg_name = name + str(i) sg = SecurityGroup(sg_name, vpc, description="Allow access to github") sgs[sg_name] = sg for ip_range in ip_ranges: if len(sg.egress + sg.ingress) == limit: i += 1 sg_name = name + str(i) sg = SecurityGroup( sg_name, vpc, description=f"Allow access to GitHub {protocol}") sgs[sg_name] = sg sg.add_rule(Ipv4EgressRule(protocol, ip_range)) return sgs
def amazon_security_groups(name, vpc): """Create a dict of security group authorizing access to aws services. As the number of rules per security group is limited to 60, we create blocks of 60 rules. :param vpc: vpc in which to create the group :type vpc: VPC :return: a dict of security groups indexed by name :rtype: dict(str, SecurityGroup) """ def select_region(ip_range_record): """Select the VPN region and the us-east-1 region. Note that some global interface (e.g. sts) are only available in the us-east-1 region. """ return ip_range_record["region"] in (vpc.region, "us-east-1") ip_ranges = requests.get(IP_RANGES_URL).json()["prefixes"] # Retrieve first the complete list of ipv4 ip ranges for a given region amazon_ip_ranges = { k["ip_prefix"] for k in ip_ranges if select_region(k) and "ip_prefix" in k and k["service"] == "AMAZON" } # Substract the list of ip ranges corresponding to services # that we do no need to access to or that we access through VPC # endpoints to limit the number of security groups and rules. services_used = ("AMAZON", "S3") removable_ip_ranges = { k["ip_prefix"] for k in ip_ranges if select_region(k) and "ip_prefix" in k and k["service"] not in services_used } amazon_ip_ranges -= removable_ip_ranges # Authorize https on the resulting list of ip ranges sgs = {} i = 0 limit = 60 sg_name = name + str(i) sg = SecurityGroup(sg_name, vpc, description="Allow access to amazon services") sgs[sg_name] = sg for ip_range in amazon_ip_ranges: if len(sg.egress + sg.ingress) == limit: i += 1 sg_name = name + str(i) sg = SecurityGroup(sg_name, vpc, description="Allow acces to amazon services") sgs[sg_name] = sg sg.add_rule(Ipv4EgressRule("https", ip_range)) return sgs
def add_network_access(self, protocol, cidr_block='0.0.0.0/0'): """Authorize some ooutbound protocols for internal servers. :param protocol: protocol name :type protocol: str :param cidr_block: allowed IP range (default is all) :type cird_block: str """ self[self.name + 'InternalSG'].add_rule( Ipv4EgressRule(protocol, cidr_block))
def amazon_security_groups(name, vpc): """Create a dict of security group authorizing access to aws services. As the number of rules per security group is limited to 50, we create blocks of 50 rules. :param vpc: vpc in which to create the group :type vpc: VPC :return: a dict of security groups indexed by name :rtype: dict(str, SecurityGroup) """ ip_ranges = requests.get(IP_RANGES_URL).json()['prefixes'] # Retrieve first the complete list of ipv4 ip ranges for a given region amazon_ip_ranges = { k['ip_prefix'] for k in ip_ranges if k['region'] == vpc.region and 'ip_prefix' in k and k['service'] == 'AMAZON' } # Sustract the list of ip ranges corresponding to EC2 instances ec2_ip_ranges = { k['ip_prefix'] for k in ip_ranges if k['region'] == vpc.region and 'ip_prefix' in k and k['service'] == 'EC2' } amazon_ip_ranges -= ec2_ip_ranges # Authorize https on the resulting list of ip ranges sgs = {} i = 0 limit = 50 sg_name = name + str(i) sg = SecurityGroup(sg_name, vpc, description='Allow acces to amazon services') sgs[sg_name] = sg for ip_range in amazon_ip_ranges: if len(sg.egress + sg.ingress) == limit: i += 1 sg_name = name + str(i) sg = SecurityGroup(sg_name, vpc, description='Allow acces to amazon services') sgs[sg_name] = sg sg.add_rule(Ipv4EgressRule('https', ip_range)) return sgs
def add_network_access( self, protocol: str, cidr_block: str = "0.0.0.0/0", from_port: Optional[int] = None, to_port: Optional[int] = None, ) -> None: """Authorize some outbound protocols for internal servers. :param protocol: protocol name :param cidr_block: allowed IP range (default is all) :param from_port: optional starting port :param to_port: optional ending port """ self[self.name + "InternalSG"].add_rule( Ipv4EgressRule(protocol, cidr_block, from_port=from_port, to_port=to_port))
def __init__( self, name, internal_server_policy, bastion_ami=None, allow_ssh_from=None, description=None, vpc_cidr_block="10.10.0.0/16", private_cidr_block="10.10.0.0/17", public_cidr_block="10.10.128.0/18", aws_endpoints_cidr_block="10.10.192.0/18", ): """Create a VPC Fortress. This create a vpc with a public and a private subnet. Servers in the private subnet are only accessible through a bastion machine declared in the public subnet. An additional subnet is created to host AWS services endpoints network interfaces. :param name: stack name :type name: str :param internal_server_policy: policy associated with instance role of private servers :type internal_server_policy: Policy :param bastion_ami: AMI used for the bastion server. If None no bastion is setup :type bastion_ami: AMI | None :param allow_ssh_from: ip ranges from which ssh can be done to the bastion. if bastion_ami is None, parameter is discarded :type allow_ssh_from: str | None :param vpc_cidr_block: ip ranges for the associated vpc :type vpc_cidr_block: str :param private_cidr_block: ip ranges (subset of vpc_cidr_block) used for private subnet :type private_cidr_block: str :param public_cidr_block: ip ranges (subset of vpc_cidr_block) used for public subnet :type public_cidr_block: str :param aws_endpoints_cidr_block: ip ranges (subset of vpc_cidr_block) used for aws endpoints :type aws_endpoints_cidr_block: str """ super().__init__(name, description) # Create VPC along with the three subnets self.add(VPCStack(self.name + "VPC", vpc_cidr_block)) self.vpc.add_subnet(self.name + "PublicNet", public_cidr_block, is_public=True, use_nat=True) self.vpc.add_subnet(self.name + "PrivateNet", private_cidr_block, nat_to=self.name + "PublicNet") self.vpc.add_subnet(self.name + "AWSEndpointsNet", aws_endpoints_cidr_block) self.amazon_groups = {} self.github_groups = {} if bastion_ami is not None: # Allow ssh to bastion only from a range of IP address self.add( SecurityGroup( self.name + "BastionSG", self.vpc.vpc, description="security group for bastion servers", rules=[ Ipv4IngressRule("ssh", cidr) for cidr in allow_ssh_from ], )) # Create the bastion self.add(Instance(self.name + "Bastion", bastion_ami)) self.bastion.tags["Name"] = "Bastion (%s)" % self.name self.bastion.add( EC2NetworkInterface( self.public_subnet.subnet, public_ip=True, groups=[self[self.name + "BastionSG"]], )) # Create security group for internal servers self.add( SecurityGroup( self.name + "InternalSG", self.vpc.vpc, description=("Allow ssh inside VPC and allow https " "to VPC endpoints subnet"), rules=[ Ipv4IngressRule("ssh", self.public_subnet.cidr_block), Ipv4EgressRule("https", self.aws_endpoints_subnet.cidr_block), ], )) else: # If no bastion is used do not authorize ssh inside the vpc self.add( SecurityGroup( self.name + "InternalSG", self.vpc.vpc, description=("Do not allow ssh inside VPC but allow https " "to the VPC endpoints subnet."), rules=[ Ipv4EgressRule("https", self.aws_endpoints_subnet.cidr_block) ], )) # Create security group for endpoints self.add( SecurityGroup( self.name + "InterfaceEndpointsSG", self.vpc.vpc, description=("Allow https from the private subnet"), rules=[ Ipv4IngressRule("https", self.private_subnet.cidr_block) ], )) ir = InstanceRole(self.name + "PrivServerInstanceRole") ir.add_policy(internal_server_policy) self.add(ir)