Beispiel #1
0
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
Beispiel #2
0
def test_create_instance():
    aws_env = AWSEnv(regions=['us-east-1'], stub=True)
    with default_region('us-east-1'):
        stub = aws_env.stub('ec2', region='us-east-1')
        stub.add_response(
            'describe_images',
            {'Images': [{'ImageId': 'ami-1234',
                         'RootDeviceName': '/dev/sda1'}]},
            {'ImageIds': ANY})

        i = Instance('testmachine', AMI('ami-1234'), disk_size=20)
        assert i.properties

        i.add(EphemeralDisk('/dev/sdb', 0))
        assert i.properties

        vpc = VPC('VPC', '10.10.0.0/16')
        subnet = Subnet('Subnet', vpc, '10.10.10.0/24')
        subnet = Subnet('Subnet2', vpc, '10.10.20.0/24')
        security_group = SecurityGroup('mysgroup', vpc)
        i.add(NetworkInterface(subnet, description='first network interface'))
        i.add(NetworkInterface(subnet,
                               groups=[security_group],
                               description='2nd network interface'))
        i.add(NetworkInterface(subnet,
                               groups=[security_group],
                               description='3rd network interface',
                               device_index=3))
        assert i.properties

        with pytest.raises(AssertionError):
            i.add("non valid ec2 device")
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
def test_create_instance():
    aws_env = AWSEnv(regions=["us-east-1"], stub=True)
    with default_region("us-east-1"):
        stub = aws_env.stub("ec2", region="us-east-1")
        stub.add_response(
            "describe_images",
            {
                "Images": [
                    {"ImageId": "ami-1234", "RootDeviceName": "/dev/sda1", "Tags": []}
                ]
            },
            {"ImageIds": ANY},
        )

        i = Instance("testmachine", AMI("ami-1234"), disk_size=20)
        assert i.properties

        i.add(EphemeralDisk("/dev/sdb", 0))
        assert i.properties

        i.add(EBSDisk("/dev/sdc", size=20, encrypted=True))
        assert i.properties

        vpc = VPC("VPC", "10.10.0.0/16")
        subnet = Subnet("Subnet", vpc, "10.10.10.0/24")
        subnet = Subnet("Subnet2", vpc, "10.10.20.0/24")
        security_group = SecurityGroup("mysgroup", vpc)
        i.add(EC2NetworkInterface(subnet, description="first network interface"))
        i.add(
            EC2NetworkInterface(
                subnet, groups=[security_group], description="2nd network interface"
            )
        )
        i.add(
            EC2NetworkInterface(
                subnet,
                groups=[security_group],
                description="3rd network interface",
                device_index=3,
            )
        )
        assert i.properties

        with pytest.raises(AssertionError):
            i.add("non valid ec2 device")
Beispiel #6
0
def test_security_group():
    vpc = VPC("vpc", cidr_block="10.10.0.0/16")
    rule1 = IngressRule("ssh", "10.10.1.1/32", description="ssh rule")
    rule2 = IngressRule("ip", "10.10.1.1/32", from_port=3389)
    rule2 = IngressRule("ip", "10.10.1.1/32", from_port=5000, to_port=5550)
    sg = SecurityGroup("SecurityGroup",
                       vpc,
                       description="basic security group",
                       rules=[rule1, rule2])
    assert sg.properties

    sg.add_rule(EgressRule("ip", "10.10.1.1/32", from_port=80))
    assert sg.properties

    with pytest.raises(AssertionError):
        sg.add_rule("invalid object")
Beispiel #7
0
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
Beispiel #8
0
def test_create_fortress_with_too_much_sgs():
    aws_env = AWSEnv(regions=["us-east-1"], stub=True)
    with default_region("us-east-1"):
        stub = aws_env.stub("ec2", region="us-east-1")
        stub.add_response(
            "describe_images",
            {
                "Images": [{
                    "ImageId": "ami-1234",
                    "RootDeviceName": "/dev/sda1",
                    "Tags": []
                }]
            },
            {"ImageIds": ANY},
        )

        d = PolicyDocument().append(
            Allow(
                to="s3:GetObject",
                on=["arn:aws:s3:::mybucket", "arn:aws:s3:::mybucket/*"],
            ))
        p = Policy("InternalPolicy", d)
        f = Fortress("myfortress", bastion_ami=None, internal_server_policy=p)

        # Adding 16 extra security groups should raise an exception (The maximum
        # number of security groups is 16 and there is a default InternalSG)
        sg_groups = [
            SecurityGroup(name=f"sg{id}", vpc=f.vpc.vpc) for id in range(16)
        ]
        with pytest.raises(AWSFortressError):
            f.add_private_server(
                AMI("ami-1234"),
                ["server1"],
                amazon_access=False,
                github_access=False,
                extra_groups=sg_groups,
            )
Beispiel #9
0
    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/17'):
        """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 declare
        in the public subnet.

        :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
        """
        super(Fortress, self).__init__(name, description)

        # Create VPC along with the two 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.add(amazon_security_group(self.name + 'AmazonServices',
                                       self.vpc.vpc))

        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(
                NetworkInterface(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',
                rules=[Ipv4IngressRule('ssh', self.public_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'))

        ir = InstanceRole(self.name + 'PrivServerInstanceRole')
        ir.add_policy(internal_server_policy)
        self.add(ir)
Beispiel #10
0
    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)