Пример #1
0
def lambda_handler(event, context):

    print(event)
    payload = json.loads(event['body'])
    print(payload)

    bucketName = payload['bucketname']
    s3ObjectName = payload['s3objectname']
    s3ObjectName2 = payload['s3objectname2']

    parser = argparse.ArgumentParser()
    parser.add_argument('--profile', default='default')
    parser.add_argument('--format', default='text', choices=['text', 'json'])

    parsed_args = parser.parse_args()
    session = botocore_tools.get_session(parsed_args.profile)
    graph_obj = graph_actions.create_new_graph(session, checker_map.keys())

    #graph report section
    filePath = LOCAL_STORAGE_PATH + s3ObjectName
    graph_writer.handle_request(graph_obj, filePath, 'svg')
    print(filePath)
    s3util.upload_to_s3(filePath, bucketName, s3ObjectName)

    #analysis report section
    filePath2 = LOCAL_STORAGE_PATH + s3ObjectName2
    reportobj = find_risks.gen_report(graph_obj)
    reportdict = reportobj.as_dictionary()
    with open(filePath2, 'w') as outfile:
        json.dump(reportdict, outfile)
    print(filePath2)
    s3util.upload_to_s3(filePath2, bucketName, s3ObjectName2)
Пример #2
0
def process_arguments(parsed_args: Namespace):
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    if parsed_args.account is None:
        session = botocore_tools.get_session(parsed_args.profile)
    else:
        session = None
    graph = graph_actions.get_existing_graph(session, parsed_args.account)
    logger.debug('Querying against graph {}'.format(
        graph.metadata['account_id']))

    # process condition args to generate input dict
    conditions = {}
    if parsed_args.condition is not None:
        for arg in parsed_args.condition:
            # split on equals-sign (=), assume first instance separates the key and value
            components = arg.split('=')
            if len(components) < 2:
                print('Format for condition args not matched: <key>=<value>')
                return 64
            key = components[0]
            value = '='.join(components[1:])
            conditions.update({key: value})

    if parsed_args.with_resource_policy:
        resource_policy = query_utils.pull_cached_resource_policy_by_arn(
            graph.policies, parsed_args.resource)
    elif parsed_args.resource_policy_text:
        resource_policy = json.loads(parsed_args.resource_policy_text)
    else:
        resource_policy = None

    if parsed_args.scps:
        if 'org-id' in graph.metadata and 'org-path' in graph.metadata:
            org_tree_path = os.path.join(get_storage_root(),
                                         graph.metadata['org-id'])
            org_tree = OrganizationTree.create_from_dir(org_tree_path)
            scps = query_orgs.produce_scp_list(graph, org_tree)
        else:
            raise ValueError(
                'Graph for account {} does not have an associated OrganizationTree mapped (need to run '
                '`pmapper orgs create/update` to get that.')
    else:
        scps = None

    query_actions.argquery(graph, parsed_args.principal, parsed_args.action,
                           parsed_args.resource, conditions,
                           parsed_args.preset, parsed_args.skip_admin,
                           resource_policy, parsed_args.resource_owner,
                           parsed_args.include_unauthorized,
                           parsed_args.session_policy, scps)

    return 0
Пример #3
0
def process_arguments(parsed_args: Namespace) -> int:
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    if parsed_args.account is None:
        session = botocore_tools.get_session(parsed_args.profile)
    else:
        session = None
    graph = graph_actions.get_existing_graph(session, parsed_args.account)

    find_risks.gen_findings_and_print(graph, parsed_args.output_type)

    return 0
Пример #4
0
def process_arguments(parsed_args: Namespace):
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    if parsed_args.account is None:
        session = botocore_tools.get_session(parsed_args.profile)
    else:
        session = None

    graph = graph_actions.get_existing_graph(session, parsed_args.account)
    logger.debug('Querying against graph {}'.format(graph.metadata['account_id']))

    if parsed_args.with_resource_policy:
        resource_policy = query_utils.pull_cached_resource_policy_by_arn(
            graph,
            arn=None,
            query=parsed_args.query
        )
    elif parsed_args.resource_policy_text:
        resource_policy = json.loads(parsed_args.resource_policy_text)
    else:
        resource_policy = None

    resource_owner = parsed_args.resource_owner
    if resource_policy is not None:
        if resource_owner is None:
            if arns.get_service(resource_policy.arn) == 's3':
                raise ValueError('Must supply resource owner (--resource-owner) when including S3 bucket policies '
                                 'in a query')
            else:
                resource_owner = arns.get_account_id(resource_policy.arn)
        if isinstance(resource_policy, Policy):
            resource_policy = resource_policy.policy_doc

    if parsed_args.scps:
        if 'org-id' in graph.metadata and 'org-path' in graph.metadata:
            org_tree_path = os.path.join(get_storage_root(), graph.metadata['org-id'])
            org_tree = OrganizationTree.create_from_dir(org_tree_path)
            scps = query_orgs.produce_scp_list(graph, org_tree)
        else:
            raise ValueError('Graph for account {} does not have an associated OrganizationTree mapped (need to run '
                             '`pmapper orgs create/update` to get that.')
    else:
        scps = None

    query_actions.query_response(
        graph, parsed_args.query, parsed_args.skip_admin, resource_policy, resource_owner,
        parsed_args.include_unauthorized, parsed_args.session_policy, scps
    )

    return 0
Пример #5
0
def process_arguments(parsed_args: Namespace):
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    if parsed_args.account is None:
        session = botocore_tools.get_session(parsed_args.profile)
    else:
        session = None
    graph = graph_actions.get_existing_graph(session, parsed_args.account)

    repl_obj = repl.PMapperREPL(graph)
    repl_obj.begin_repl()

    return 0
Пример #6
0
def main():
    """Body of the script."""
    # Handle input args --profile and --format
    parser = argparse.ArgumentParser()
    parser.add_argument('--profile')
    parser.add_argument('--format', default='text', choices=['text', 'json'])
    parsed_args = parser.parse_args()

    # Generate the graph (such as with `pmapper graph --create`)
    session = botocore_tools.get_session(parsed_args.profile)
    # graph_obj = gathering.create_graph(session, checker_map.keys())  # gets a Graph object without printing info
    graph_obj = graph_actions.create_new_graph(session, checker_map.keys())

    # Print out identified findings (such as with `pmapper analysis`)
    find_risks.gen_findings_and_print(graph_obj, parsed_args.format)
Пример #7
0
def lambda_handler(event, context):
    parser = argparse.ArgumentParser()
    parser.add_argument('--profile', default='default')
    parser.add_argument('--format', default='text', choices=['text', 'json'])

    #parsed_args = parser.parse_args(["None"])
    parsed_args = parser.parse_args()
    session = botocore_tools.get_session(parsed_args.profile)
    graph_obj = graph_actions.create_new_graph(session, checker_map.keys())

    dateNow = datetime.now()
    unique_outputFile = "output_" + dateNow.strftime("%H-%M-%S-%f")
    s3ObjectName = unique_outputFile + '.png'

    create_signed_URL(s3ObjectName)

    filePath = LOCAL_STORAGE_PATH + s3ObjectName
    graph_writer.handle_request(graph_obj, filePath, 'png')
    uploaded = s3util.upload_to_s3(filePath, BUCKET_NAME, s3ObjectName)
    return uploaded
Пример #8
0
def process_arguments(parsed_args: Namespace):
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    if parsed_args.account is None:
        session = botocore_tools.get_session(parsed_args.profile)
    else:
        session = None
    graph = graph_actions.get_existing_graph(session, parsed_args.account)

    if parsed_args.only_privesc:
        filepath = './{}-privesc-risks.{}'.format(graph.metadata['account_id'], parsed_args.filetype)
        graph_writer.draw_privesc_paths(graph, filepath, parsed_args.filetype)
    else:
        # create file
        filepath = './{}.{}'.format(graph.metadata['account_id'], parsed_args.filetype)
        graph_writer.handle_request(graph, filepath, parsed_args.filetype)

    print('Created file {}'.format(filepath))

    return 0
Пример #9
0
def _grab_session(parsed_args) -> Optional[botocore.session.Session]:
    if parsed_args.account is None:
        return botocore_tools.get_session(parsed_args.profile)
    else:
        return None
Пример #10
0
def process_arguments(parsed_args: Namespace):
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    # new args for handling AWS Organizations
    if parsed_args.picked_orgs_cmd == 'create':
        logger.debug('Called create subcommand for organizations')

        # filter the args first
        if parsed_args.account is not None:
            print(
                'Cannot specify offline-mode param `--account` when calling `pmapper graph org_create`. If you have '
                'credentials for a specific account to graph, you can use those credentials similar to how the '
                'AWS CLI works (environment variables, profiles, EC2 instance metadata). In the case of using '
                'a profile, use the `--profile [PROFILE]` argument before specifying the `graph` subcommand.'
            )
            return 64

        # get the botocore session and go to work creating the OrganizationTree obj
        session = botocore_tools.get_session(parsed_args.profile)
        org_tree = get_organizations_data(session)
        logger.info('Generated initial organization data for {}'.format(
            org_tree.org_id))

        # create the account -> OU path map and apply to all accounts (same as org_update operation)
        account_ou_map = _map_account_ou_paths(org_tree)
        logger.debug('account_ou_map: {}'.format(account_ou_map))
        _update_accounts_with_ou_path_map(org_tree.org_id, account_ou_map,
                                          get_storage_root())
        logger.info(
            'Updated currently stored Graphs with applicable AWS Organizations data'
        )

        # create and cache a list of edges between all the accounts we have data for
        edge_list = []
        graph_objs = []
        for account in org_tree.accounts:
            try:
                potential_path = os.path.join(get_storage_root(), account)
                logger.debug(
                    'Trying to load a Graph from {}'.format(potential_path))
                graph_obj = Graph.create_graph_from_local_disk(potential_path)
                graph_objs.append(graph_obj)
            except Exception as ex:
                logger.warning(
                    'Unable to load a Graph object for account {}, possibly because it is not mapped yet. '
                    'Please map all accounts and then update the Organization Tree '
                    '(`pmapper graph org_update`).'.format(account))
                logger.debug(str(ex))

        for graph_obj_a in graph_objs:
            for graph_obj_b in graph_objs:
                if graph_obj_a == graph_obj_b:
                    continue
                graph_a_scps = produce_scp_list(graph_obj_a, org_tree)
                graph_b_scps = produce_scp_list(graph_obj_b, org_tree)
                edge_list.extend(
                    get_edges_between_graphs(graph_obj_a, graph_obj_b,
                                             graph_a_scps, graph_b_scps))

        org_tree.edge_list = edge_list
        logger.info('Compiled cross-account edges')

        org_tree.save_organization_to_disk(
            os.path.join(get_storage_root(), org_tree.org_id))
        logger.info('Stored organization data to disk')

    elif parsed_args.picked_orgs_cmd == 'update':
        # pull the existing data from disk
        org_filepath = os.path.join(get_storage_root(), parsed_args.org)
        org_tree = OrganizationTree.create_from_dir(org_filepath)

        # create the account -> OU path map and apply to all accounts
        account_ou_map = _map_account_ou_paths(org_tree)
        logger.debug('account_ou_map: {}'.format(account_ou_map))
        _update_accounts_with_ou_path_map(org_tree.org_id, account_ou_map,
                                          get_storage_root())
        logger.info(
            'Updated currently stored Graphs with applicable AWS Organizations data'
        )

        # create and cache a list of edges between all the accounts we have data for
        edge_list = []
        graph_objs = []
        for account in org_tree.accounts:
            try:
                potential_path = os.path.join(get_storage_root(), account)
                logger.debug(
                    'Trying to load a Graph from {}'.format(potential_path))
                graph_obj = Graph.create_graph_from_local_disk(potential_path)
                graph_objs.append(graph_obj)
            except Exception as ex:
                logger.warning(
                    'Unable to load a Graph object for account {}, possibly because it is not mapped yet. '
                    'Please map all accounts and then update the Organization Tree '
                    '(`pmapper graph org_update`).'.format(account))
                logger.debug(str(ex))

        for graph_obj_a in graph_objs:
            for graph_obj_b in graph_objs:
                if graph_obj_a == graph_obj_b:
                    continue
                graph_a_scps = produce_scp_list(graph_obj_a, org_tree)
                graph_b_scps = produce_scp_list(graph_obj_b, org_tree)
                edge_list.extend(
                    get_edges_between_graphs(graph_obj_a, graph_obj_b,
                                             graph_a_scps, graph_b_scps))

        org_tree.edge_list = edge_list
        logger.info('Compiled cross-account edges')

        org_tree.save_organization_to_disk(
            os.path.join(get_storage_root(), org_tree.org_id))
        logger.info('Stored organization data to disk')

    elif parsed_args.picked_orgs_cmd == 'display':
        # pull the existing data from disk
        org_filepath = os.path.join(get_storage_root(), parsed_args.org)
        org_tree = OrganizationTree.create_from_dir(org_filepath)

        def _print_account(org_account: OrganizationAccount, indent_level: int,
                           inherited_scps: List[Policy]):
            print('{} {}:'.format(' ' * indent_level, org_account.account_id))
            print('{}  Directly Attached SCPs: {}'.format(
                ' ' * indent_level, [x.name for x in org_account.scps]))
            print('{}  Inherited SCPs:         {}'.format(
                ' ' * indent_level, [x.name for x in inherited_scps]))

        def _walk_and_print_ou(org_node: OrganizationNode, indent_level: int,
                               inherited_scps: List[Policy]):
            print('{}"{}" ({}):'.format(' ' * indent_level, org_node.ou_name,
                                        org_node.ou_id))
            print('{}  Accounts:'.format(' ' * indent_level))
            for o_account in org_node.accounts:
                _print_account(o_account, indent_level + 2, inherited_scps)
            print('{}  Directly Attached SCPs: {}'.format(
                ' ' * indent_level, [x.name for x in org_node.scps]))
            print('{}  Inherited SCPs:         {}'.format(
                ' ' * indent_level, [x.name for x in inherited_scps]))
            print('{}  Child OUs:'.format(' ' * indent_level))
            for child_node in org_node.child_nodes:
                new_inherited_scps = inherited_scps.copy()
                new_inherited_scps.extend(
                    [x for x in org_node.scps if x not in inherited_scps])
                _walk_and_print_ou(child_node, indent_level + 4,
                                   new_inherited_scps)

        print('Organization {}:'.format(org_tree.org_id))
        for root_ou in org_tree.root_ous:
            _walk_and_print_ou(root_ou, 0, [])

    elif parsed_args.picked_orgs_cmd == 'list':
        print("Organization IDs:")
        print("---")
        storage_root = Path(get_storage_root())
        account_id_pattern = re.compile(r'o-\w+')
        for direct in storage_root.iterdir():
            if account_id_pattern.search(str(direct)) is not None:
                metadata_file = direct.joinpath(Path('metadata.json'))
                with open(str(metadata_file)) as fd:
                    version = json.load(fd)['pmapper_version']
                print("{} (PMapper Version {})".format(direct.name, version))

    return 0
Пример #11
0
def process_arguments(parsed_args: Namespace):
    """Given a namespace object generated from parsing args, perform the appropriate tasks. Returns an int
    matching expectations set by /usr/include/sysexits.h for command-line utilities."""

    if parsed_args.picked_graph_cmd == 'create':
        logger.debug('Called create subcommand of graph')

        # filter the args first
        if parsed_args.account is not None:
            print(
                'Cannot specify offline-mode param `--account` when calling `pmapper graph create`. If you have '
                'credentials for a specific account to graph, you can use those credentials similar to how the '
                'AWS CLI works (environment variables, profiles, EC2 instance metadata). In the case of using '
                'a profile, use the `--profile [PROFILE]` argument before specifying the `graph` subcommand.'
            )
            return 64

        service_list_base = list(checker_map.keys())
        if parsed_args.include_services is not None:
            service_list = [
                x for x in service_list_base
                if x in parsed_args.include_services
            ]
        elif parsed_args.exclude_services is not None:
            service_list = [
                x for x in service_list_base
                if x not in parsed_args.exclude_services
            ]
        else:
            service_list = service_list_base
        logger.debug(
            'Service list after processing args: {}'.format(service_list))

        # need to know account ID to search potential SCPs
        if parsed_args.localstack_endpoint is not None:
            session = botocore_tools.get_session(
                parsed_args.profile,
                {'endpoint_url': parsed_args.localstack_endpoint})
        else:
            session = botocore_tools.get_session(parsed_args.profile)

        scps = None
        if not parsed_args.ignore_orgs:
            if parsed_args.localstack_endpoint is not None:
                stsclient = session.create_client(
                    'sts', endpoint_url=parsed_args.localstack_endpoint)
            else:
                stsclient = session.create_client('sts')
            caller_identity = stsclient.get_caller_identity()
            caller_account = caller_identity['Account']
            logger.debug("Caller Identity: {}".format(caller_identity))

            org_tree_search_dir = Path(get_storage_root())
            org_id_pattern = re.compile(r'/o-\w+')
            for subdir in org_tree_search_dir.iterdir():
                if org_id_pattern.search(str(subdir)) is not None:
                    logger.debug(
                        'Checking {} to see if account {} is a member'.format(
                            str(subdir), caller_account))
                    org_tree = OrganizationTree.create_from_dir(str(subdir))
                    if caller_account in org_tree.accounts:
                        logger.info(
                            'Account {} is a member of Organization {}'.format(
                                caller_account, org_tree.org_id))
                        if caller_account == org_tree.management_account_id:
                            logger.info(
                                'Account {} is the management account, SCPs do not apply'
                                .format(caller_account))
                        else:
                            logger.info(
                                'Identifying and applying SCPs for the graphing process'
                            )
                            scps = query_orgs.produce_scp_list_by_account_id(
                                caller_account, org_tree)
                        break

        if parsed_args.localstack_endpoint is not None:
            full_service_list = ('autoscaling', 'cloudformation', 'codebuild',
                                 'ec2', 'iam', 'kms', 'lambda', 'sagemaker',
                                 's3', 'ssm', 'secretsmanager', 'sns', 'sts',
                                 'sqs')

            client_args_map = {
                x: {
                    'endpoint_url': parsed_args.localstack_endpoint
                }
                for x in full_service_list
            }
        else:
            client_args_map = None

        graph = graph_actions.create_new_graph(session, service_list,
                                               parsed_args.include_regions,
                                               parsed_args.exclude_regions,
                                               scps, client_args_map)
        graph_actions.print_graph_data(graph)
        graph.store_graph_as_json(
            os.path.join(get_storage_root(), graph.metadata['account_id']))

    elif parsed_args.picked_graph_cmd == 'display':
        if parsed_args.account is None:
            session = botocore_tools.get_session(parsed_args.profile)
        else:
            session = None

        graph = graph_actions.get_existing_graph(session, parsed_args.account)
        graph_actions.print_graph_data(graph)

    elif parsed_args.picked_graph_cmd == 'list':
        print("Account IDs:")
        print("---")
        storage_root = Path(get_storage_root())
        account_id_pattern = re.compile(r'\d{12}')
        for direct in storage_root.iterdir():
            if account_id_pattern.search(str(direct)) is not None:
                metadata_file = direct.joinpath(Path('metadata.json'))
                with open(str(metadata_file)) as fd:
                    account_metadata = json.load(fd)
                version = account_metadata['pmapper_version']
                print("{} (PMapper Version {})".format(direct.name, version))

    return 0