def test_build_data_structure(self):
        # Build the entire demo data set
        json_blob = {u'id': 111111111111, u'name': u'demo'}

        outputfilter = {}
        outputfilter["internal_edges"] = True
        outputfilter["read_replicas"] = True
        outputfilter["inter_rds_edges"] = True
        outputfilter["azs"] = False
        outputfilter["collapse_by_tag"] = False
        outputfilter["collapse_asgs"] = False

        config = {"accounts": [{"id": 123456789012, "name": "demo"}], "cidrs": {"1.1.1.1/32": {"name": "SF Office"}, "2.2.2.2/28": {"name": "NY Office"}}}

        cytoscape_json = build_data_structure(json_blob, config, outputfilter)

        # Now check it
        # Check number of connections
        assert_equal(35, len(pyjq.all('.[].data|select(.type == "edge")|keys', cytoscape_json)))

        # Check number of nodes
        assert_equal(2, len(pyjq.all('.[].data|select(.type == "ip")|keys', cytoscape_json)))
        assert_equal(2, len(pyjq.all('.[].data|select(.type == "rds")|keys', cytoscape_json)))
        assert_equal(3, len(pyjq.all('.[].data|select(.type == "ec2")|keys', cytoscape_json)))
        assert_equal(2, len(pyjq.all('.[].data|select(.type == "elb")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "elbv2")|keys', cytoscape_json)))
        assert_equal(4, len(pyjq.all('.[].data|select(.type == "subnet")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "region")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "vpc")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "sqs")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "s3")|keys', cytoscape_json)))
        assert_equal(2, len(pyjq.all('.[].data|select(.type == "redshift")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "elasticsearch")|keys', cytoscape_json)))
        assert_equal(2, len(pyjq.all('.[].data|select(.type == "lambda")|keys', cytoscape_json)))
        assert_equal(1, len(pyjq.all('.[].data|select(.type == "ecs")|keys', cytoscape_json)))

        # Test without internal edges
        outputfilter["internal_edges"] = False
        cytoscape_json = build_data_structure(json_blob, config, outputfilter)

        # Check number of connections
        assert_equal(19, len(pyjq.all('.[].data|select(.type == "edge")|keys', cytoscape_json)))

        # Test with AZs
        outputfilter["azs"] = True
        outputfilter["internal_edges"] = True
        cytoscape_json = build_data_structure(json_blob, config, outputfilter)

        # Check number of connections
        assert_equal(2, len(pyjq.all('.[].data|select(.type == "az")|keys', cytoscape_json)))


        # Test with specific VPC name
        outputfilter["azs"] = False
        outputfilter["vpc-names"] = '\"Prod\"'
        cytoscape_json = build_data_structure(json_blob, config, outputfilter)

        # Check number of connections
        assert_equal(3, len(pyjq.all('.[].data|select(.type == "ec2")|keys', cytoscape_json)))
Exemple #2
0
def public(accounts, config):
    for account in accounts:
        # Get the data from the `prepare` command
        outputfilter = {'internal_edges': False, 'read_replicas': False, 'inter_rds_edges': False, 'azs': False, 'collapse_by_tag': None, 'mute': True}
        network = build_data_structure(account, config, outputfilter)

        # Look at all the edges for ones connected to the public Internet (0.0.0.0/0)
        for edge in pyjq.all('.[].data|select(.type=="edge")|select(.source=="0.0.0.0/0")', network):

            # Find the node at the other end of this edge
            target = {'arn': edge['target'], 'account': account['name']}
            target_node = pyjq.first('.[].data|select(.id=="{}")'.format(target['arn']), network, {})

            # Depending on the type of node, identify what the IP or hostname is
            if target_node['type'] == 'elb':
                target['type'] = 'elb'
                target['hostname'] = target_node['node_data']['DNSName']
            elif target_node['type'] == 'autoscaling':
                target['type'] = 'autoscaling'
                target['hostname'] = target_node['node_data'].get('PublicIpAddress', '')
                if target['hostname'] == '':
                    target['hostname'] = target_node['node_data']['PublicDnsName']
            elif target_node['type'] == 'rds':
                target['type'] = 'rds'
                target['hostname'] = target_node['node_data']['Endpoint']['Address']
            elif target_node['type'] == 'ec2':
                target['type'] = 'ec2'
                dns_name = target_node['node_data'].get('PublicDnsName', '')
                target['hostname'] = target_node['node_data'].get('PublicIpAddress', dns_name)
            else:
                print(pyjq.first('.[].data|select(.id=="{}")|[.type, (.node_data|keys)]'.format(target['arn']), network, {}))

            # Check if any protocol is allowed (indicated by IpProtocol == -1)
            ingress = pyjq.all('.[].IpPermissions[]', edge.get('node_data', {}))
            if pyjq.first('.[]|select(.IpProtocol=="-1")|.IpProtocol', ingress, '1') == '-1':
                log_warning('All protocols allowed access to {}'.format(target))
                range_string = '0-65535'
            else:
                # from_port and to_port mean the beginning and end of a port range
                # We only care about TCP (6) and UDP (17)
                # For more info see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html
                selection = 'select((.IpProtocol=="tcp") or (.IpProtocol=="udp")) | select(.IpRanges[].CidrIp=="0.0.0.0/0")'
                port_ranges = pyjq.all('.[]|{}| [.FromPort,.ToPort]'.format(selection), ingress)
                range_string = port_ranges_string(regroup_ranges(port_ranges))

            target['ports'] = range_string
            if target['ports'] == "":
                issue_msg = 'No ports open for tcp or udp (probably can only be pinged). Rules that are not tcp or udp: {} -- {}'
                log_warning(issue_msg.format(json.dumps(pyjq.all('.[]|select((.IpProtocol!="tcp") and (.IpProtocol!="udp"))'.format(selection), ingress)), account))
            print(json.dumps(target, indent=4, sort_keys=True))
Exemple #3
0
def get_public_nodes(account, config, use_cache=False):
    # TODO Look for IPv6 also
    # TODO Look at more services from https://github.com/arkadiyt/aws_public_ips
    # TODO Integrate into something to more easily port scan and screenshot web services

    # Try reading from cache
    cache_file_path = "account-data/{}/public_nodes.json".format(account["name"])
    if use_cache and os.path.isfile(cache_file_path):
        with open(cache_file_path) as f:
            return json.load(f), []

    # Get the data from the `prepare` command
    outputfilter = {
        "internal_edges": False,
        "read_replicas": False,
        "inter_rds_edges": False,
        "azs": False,
        "collapse_by_tag": None,
        "collapse_asgs": True,
        "mute": True,
    }
    network = build_data_structure(account, config, outputfilter)

    public_nodes = []
    warnings = []

    # Look at all the edges for ones connected to the public Internet (0.0.0.0/0)
    for edge in pyjq.all(
        '.[].data|select(.type=="edge")|select(.source=="0.0.0.0/0")', network
    ):

        # Find the node at the other end of this edge
        target = {"arn": edge["target"], "account": account["name"]}
        target_node = pyjq.first(
            '.[].data|select(.id=="{}")'.format(target["arn"]), network, {}
        )

        # Depending on the type of node, identify what the IP or hostname is
        if target_node["type"] == "elb":
            target["type"] = "elb"
            target["hostname"] = target_node["node_data"]["DNSName"]
        elif target_node["type"] == "elbv2":
            target["type"] = "elbv2"
            target["hostname"] = target_node["node_data"]["DNSName"]
        elif target_node["type"] == "autoscaling":
            target["type"] = "autoscaling"
            target["hostname"] = target_node["node_data"].get("PublicIpAddress", "")
            if target["hostname"] == "":
                target["hostname"] = target_node["node_data"]["PublicDnsName"]
        elif target_node["type"] == "rds":
            target["type"] = "rds"
            target["hostname"] = target_node["node_data"]["Endpoint"]["Address"]
        elif target_node["type"] == "ec2":
            target["type"] = "ec2"
            dns_name = target_node["node_data"].get("PublicDnsName", "")
            target["hostname"] = target_node["node_data"].get(
                "PublicIpAddress", dns_name
            )
            target["tags"] = target_node["node_data"].get("Tags", [])
        elif target_node["type"] == "ecs":
            target["type"] = "ecs"
            target["hostname"] = ""
            for ip in target_node["node_data"]["ips"]:
                if is_public_ip(ip):
                    target["hostname"] = ip
        elif target_node["type"] == "redshift":
            target["type"] = "redshift"
            target["hostname"] = (
                target_node["node_data"].get("Endpoint", {}).get("Address", "")
            )
        else:
            # Unknown node
            raise Exception("Unknown type: {}".format(target_node["type"]))

        # Check if any protocol is allowed (indicated by IpProtocol == -1)
        ingress = pyjq.all(".[]", edge.get("node_data", {}))

        sg_group_allowing_all_protocols = pyjq.first(
            '.[]|select(.IpPermissions[]?|.IpProtocol=="-1")|.GroupId', ingress, None
        )
        public_sgs = {}
        if sg_group_allowing_all_protocols is not None:
            warnings.append(
                "All protocols allowed access to {} due to {}".format(
                    target, sg_group_allowing_all_protocols
                )
            )
            # I would need to redo this code in order to get the name of the security group
            public_sgs[sg_group_allowing_all_protocols] = {"public_ports": "0-65535"}

        # from_port and to_port mean the beginning and end of a port range
        # We only care about TCP (6) and UDP (17)
        # For more info see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html
        port_ranges = []
        for sg in ingress:
            sg_port_ranges = []
            for ip_permission in sg.get("IpPermissions", []):
                selection = 'select((.IpProtocol=="tcp") or (.IpProtocol=="udp")) | select(.IpRanges[].CidrIp=="0.0.0.0/0")'
                sg_port_ranges.extend(
                    pyjq.all("{}| [.FromPort,.ToPort]".format(selection), ip_permission)
                )
                selection = 'select(.IpProtocol=="-1") | select(.IpRanges[].CidrIp=="0.0.0.0/0")'
                sg_port_ranges.extend(
                    pyjq.all("{}| [0,65535]".format(selection), ip_permission)
                )
            public_sgs[sg["GroupId"]] = {
                "GroupId": sg["GroupId"],
                "GroupName": sg["GroupName"],
                "public_ports": port_ranges_string(regroup_ranges(sg_port_ranges)),
            }
            port_ranges.extend(sg_port_ranges)
        range_string = port_ranges_string(regroup_ranges(port_ranges))

        target["ports"] = range_string
        target["public_sgs"] = public_sgs
        if target["ports"] == "":
            issue_msg = "No ports open for tcp or udp (probably can only be pinged). Rules that are not tcp or udp: {} -- {}"
            warnings.append(
                issue_msg.format(
                    json.dumps(
                        pyjq.all(
                            '.[]|select((.IpProtocol!="tcp") and (.IpProtocol!="udp"))'.format(
                                selection
                            ),
                            ingress,
                        )
                    ),
                    account,
                )
            )
        public_nodes.append(target)

    # For the network diagram, if an ELB has availability across 3 subnets, I put one node in each subnet.
    # We don't care about that when we want to know what is public and it makes it confusing when you
    # see 3 resources with the same hostname, when you view your environment as only having one ELB.
    # This same issue exists for RDS.
    # Reduce these to single nodes.

    reduced_nodes = {}

    for node in public_nodes:
        reduced_nodes[node["hostname"]] = node

    public_nodes = []
    for _, node in reduced_nodes.items():
        public_nodes.append(node)

    account = Account(None, account)
    for region_json in get_regions(account):
        region = Region(account, region_json)
        # Look for CloudFront
        if region.name == "us-east-1":
            json_blob = query_aws(
                region.account, "cloudfront-list-distributions", region
            )

            for distribution in json_blob.get("DistributionList", {}).get("Items", []):
                if not distribution["Enabled"]:
                    continue

                target = {"arn": distribution["ARN"], "account": account.name}
                target["type"] = "cloudfront"
                target["hostname"] = distribution["DomainName"]
                target["ports"] = "80,443"

                public_nodes.append(target)

        # Look for API Gateway
        json_blob = query_aws(region.account, "apigateway-get-rest-apis", region)
        if json_blob is not None:
            for api in json_blob.get("items", []):
                target = {"arn": api["id"], "account": account.name}
                target["type"] = "apigateway"
                target["hostname"] = "{}.execute-api.{}.amazonaws.com".format(
                    api["id"], region.name
                )
                target["ports"] = "80,443"

                public_nodes.append(target)

    # Write cache file
    with open(cache_file_path, "w") as f:
        f.write(json.dumps(public_nodes, indent=4, sort_keys=True))

    return public_nodes, warnings
Exemple #4
0
def public(accounts, config):
    for account in accounts:
        # Get the data from the `prepare` command
        outputfilter = {
            'internal_edges': False,
            'read_replicas': False,
            'inter_rds_edges': False,
            'azs': False,
            'collapse_by_tag': None,
            'collapse_asgs': True,
            'mute': True
        }
        network = build_data_structure(account, config, outputfilter)

        # Look at all the edges for ones connected to the public Internet (0.0.0.0/0)
        for edge in pyjq.all(
                '.[].data|select(.type=="edge")|select(.source=="0.0.0.0/0")',
                network):

            # Find the node at the other end of this edge
            target = {'arn': edge['target'], 'account': account['name']}
            target_node = pyjq.first(
                '.[].data|select(.id=="{}")'.format(target['arn']), network,
                {})

            # Depending on the type of node, identify what the IP or hostname is
            if target_node['type'] == 'elb':
                target['type'] = 'elb'
                target['hostname'] = target_node['node_data']['DNSName']
            elif target_node['type'] == 'autoscaling':
                target['type'] = 'autoscaling'
                target['hostname'] = target_node['node_data'].get(
                    'PublicIpAddress', '')
                if target['hostname'] == '':
                    target['hostname'] = target_node['node_data'][
                        'PublicDnsName']
            elif target_node['type'] == 'rds':
                target['type'] = 'rds'
                target['hostname'] = target_node['node_data']['Endpoint'][
                    'Address']
            elif target_node['type'] == 'ec2':
                target['type'] = 'ec2'
                dns_name = target_node['node_data'].get('PublicDnsName', '')
                target['hostname'] = target_node['node_data'].get(
                    'PublicIpAddress', dns_name)
            else:
                print(
                    pyjq.first(
                        '.[].data|select(.id=="{}")|[.type, (.node_data|keys)]'
                        .format(target['arn']), network, {}))

            # Check if any protocol is allowed (indicated by IpProtocol == -1)
            ingress = pyjq.all('.[]', edge.get('node_data', {}))

            sg_group_allowing_all_protocols = pyjq.first(
                'select(.IpPermissions[]|.IpProtocol=="-1")|.GroupId', ingress,
                None)
            public_sgs = set()
            if sg_group_allowing_all_protocols is not None:
                log_warning(
                    'All protocols allowed access to {} due to {}'.format(
                        target, sg_group_allowing_all_protocols))
                range_string = '0-65535'
                public_sgs.add(sg_group_allowing_all_protocols)
            else:
                # from_port and to_port mean the beginning and end of a port range
                # We only care about TCP (6) and UDP (17)
                # For more info see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html
                port_ranges = []
                for sg in ingress:
                    for ip_permission in sg['IpPermissions']:
                        selection = 'select((.IpProtocol=="tcp") or (.IpProtocol=="udp")) | select(.IpRanges[].CidrIp=="0.0.0.0/0")'
                        port_ranges.extend(
                            pyjq.all(
                                '{}| [.FromPort,.ToPort]'.format(selection),
                                ip_permission))
                        public_sgs.add(sg['GroupId'])
                range_string = port_ranges_string(regroup_ranges(port_ranges))

            target['ports'] = range_string
            target['public_sgs'] = list(public_sgs)
            if target['ports'] == "":
                issue_msg = 'No ports open for tcp or udp (probably can only be pinged). Rules that are not tcp or udp: {} -- {}'
                log_warning(
                    issue_msg.format(
                        json.dumps(
                            pyjq.all(
                                '.[]|select((.IpProtocol!="tcp") and (.IpProtocol!="udp"))'
                                .format(selection), ingress)), account))
            print(json.dumps(target, indent=4, sort_keys=True))

        account = Account(None, account)
        for region_json in get_regions(account):
            region = Region(account, region_json)
            # Look for CloudFront
            if region.name == 'us-east-1':
                json_blob = query_aws(region.account,
                                      'cloudfront-list-distributions', region)

                for distribution in json_blob.get('DistributionList',
                                                  {}).get('Items', []):
                    if not distribution['Enabled']:
                        continue

                    target = {
                        'arn': distribution['ARN'],
                        'account': account.name
                    }
                    target['type'] = 'cloudfront'
                    target['hostname'] = distribution['DomainName']
                    target['ports'] = '80,443'

                    print(json.dumps(target, indent=4, sort_keys=True))

            # Look for API Gateway
            json_blob = query_aws(region.account, 'apigateway-get-rest-apis',
                                  region)
            for api in json_blob.get('items', []):
                target = {'arn': api['id'], 'account': account.name}
                target['type'] = 'apigateway'
                target['hostname'] = '{}.execute-api.{}.amazonaws.com'.format(
                    api['id'], region.name)
                target['ports'] = '80,443'

                print(json.dumps(target, indent=4, sort_keys=True))
Exemple #5
0
def get_public_nodes(account, config, use_cache=False):
    # TODO Look for IPv6 also
    # TODO Look at more services from https://github.com/arkadiyt/aws_public_ips
    # TODO Integrate into something to more easily port scan and screenshot web services

    # Try reading from cache
    cache_file_path = 'account-data/{}/public_nodes.json'.format(
        account['name'])
    if use_cache:
        if os.path.isfile(cache_file_path):
            with open(cache_file_path) as f:
                return json.load(f), []

    # Get the data from the `prepare` command
    outputfilter = {
        'internal_edges': False,
        'read_replicas': False,
        'inter_rds_edges': False,
        'azs': False,
        'collapse_by_tag': None,
        'collapse_asgs': True,
        'mute': True
    }
    network = build_data_structure(account, config, outputfilter)

    public_nodes = []
    warnings = []

    # Look at all the edges for ones connected to the public Internet (0.0.0.0/0)
    for edge in pyjq.all(
            '.[].data|select(.type=="edge")|select(.source=="0.0.0.0/0")',
            network):

        # Find the node at the other end of this edge
        target = {'arn': edge['target'], 'account': account['name']}
        target_node = pyjq.first(
            '.[].data|select(.id=="{}")'.format(target['arn']), network, {})

        # Depending on the type of node, identify what the IP or hostname is
        if target_node['type'] == 'elb':
            target['type'] = 'elb'
            target['hostname'] = target_node['node_data']['DNSName']
        elif target_node['type'] == 'elbv2':
            target['type'] = 'elbv2'
            target['hostname'] = target_node['node_data']['DNSName']
        elif target_node['type'] == 'autoscaling':
            target['type'] = 'autoscaling'
            target['hostname'] = target_node['node_data'].get(
                'PublicIpAddress', '')
            if target['hostname'] == '':
                target['hostname'] = target_node['node_data']['PublicDnsName']
        elif target_node['type'] == 'rds':
            target['type'] = 'rds'
            target['hostname'] = target_node['node_data']['Endpoint'][
                'Address']
        elif target_node['type'] == 'ec2':
            target['type'] = 'ec2'
            dns_name = target_node['node_data'].get('PublicDnsName', '')
            target['hostname'] = target_node['node_data'].get(
                'PublicIpAddress', dns_name)
        elif target_node['type'] == 'ecs':
            target['type'] = 'ecs'
            target['hostname'] = ''
            for ip in target_node['node_data']['ips']:
                if is_public_ip(ip):
                    target['hostname'] = ip
        elif target_node['type'] == 'redshift':
            target['type'] = 'redshift'
            target['hostname'] = target_node['node_data'].get(
                'Endpoint', {}).get('Address', '')
        else:
            # Unknown node
            raise Exception('Unknown type: {}'.format(target_node['type']))

        # Check if any protocol is allowed (indicated by IpProtocol == -1)
        ingress = pyjq.all('.[]', edge.get('node_data', {}))

        sg_group_allowing_all_protocols = pyjq.first(
            'select(.IpPermissions[]|.IpProtocol=="-1")|.GroupId', ingress,
            None)
        public_sgs = {}
        if sg_group_allowing_all_protocols is not None:
            warnings.append(
                'All protocols allowed access to {} due to {}'.format(
                    target, sg_group_allowing_all_protocols))
            range_string = '0-65535'
            # I would need to redo this code in order to get the name of the security group
            public_sgs[sg_group_allowing_all_protocols] = {
                'public_ports': '0-65535'
            }
        else:
            # from_port and to_port mean the beginning and end of a port range
            # We only care about TCP (6) and UDP (17)
            # For more info see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html
            port_ranges = []
            for sg in ingress:
                sg_port_ranges = []
                for ip_permission in sg['IpPermissions']:
                    selection = 'select((.IpProtocol=="tcp") or (.IpProtocol=="udp")) | select(.IpRanges[].CidrIp=="0.0.0.0/0")'
                    sg_port_ranges.extend(
                        pyjq.all('{}| [.FromPort,.ToPort]'.format(selection),
                                 ip_permission))
                public_sgs[sg['GroupId']] = {
                    'GroupName':
                    sg['GroupName'],
                    'public_ports':
                    port_ranges_string(regroup_ranges(sg_port_ranges))
                }
                port_ranges.extend(sg_port_ranges)
            range_string = port_ranges_string(regroup_ranges(port_ranges))

            target['ports'] = range_string
            target['public_sgs'] = public_sgs
            if target['ports'] == "":
                issue_msg = 'No ports open for tcp or udp (probably can only be pinged). Rules that are not tcp or udp: {} -- {}'
                warnings.append(
                    issue_msg.format(
                        json.dumps(
                            pyjq.all(
                                '.[]|select((.IpProtocol!="tcp") and (.IpProtocol!="udp"))'
                                .format(selection), ingress)), account))
            public_nodes.append(target)

    # For the network diagram, if an ELB has availability across 3 subnets, I put one node in each subnet.
    # We don't care about that when we want to know what is public and it makes it confusing when you
    # see 3 resources with the same hostname, when you view your environment as only having one ELB.
    # This same issue exists for RDS.
    # Reduce these to single nodes.

    reduced_nodes = {}

    for node in public_nodes:
        reduced_nodes[node['hostname']] = node

    public_nodes = []
    for _, node in reduced_nodes.items():
        public_nodes.append(node)

    account = Account(None, account)
    for region_json in get_regions(account):
        region = Region(account, region_json)
        # Look for CloudFront
        if region.name == 'us-east-1':
            json_blob = query_aws(region.account,
                                  'cloudfront-list-distributions', region)

            for distribution in json_blob.get('DistributionList',
                                              {}).get('Items', []):
                if not distribution['Enabled']:
                    continue

                target = {'arn': distribution['ARN'], 'account': account.name}
                target['type'] = 'cloudfront'
                target['hostname'] = distribution['DomainName']
                target['ports'] = '80,443'

                public_nodes.append(target)

        # Look for API Gateway
        json_blob = query_aws(region.account, 'apigateway-get-rest-apis',
                              region)
        if json_blob is not None:
            for api in json_blob.get('items', []):
                target = {'arn': api['id'], 'account': account.name}
                target['type'] = 'apigateway'
                target['hostname'] = '{}.execute-api.{}.amazonaws.com'.format(
                    api['id'], region.name)
                target['ports'] = '80,443'

                public_nodes.append(target)

    # Write cache file
    with open(cache_file_path, 'w') as f:
        f.write(json.dumps(public_nodes, indent=4, sort_keys=True))

    return public_nodes, warnings