Example #1
0
def get_unfilled_groups(iamclient, nodes: List[Node]) -> List[Group]:
    """Using an IAM.Client object, returns a list of Group objects. Adds to each passed Node's group_memberships
    property.

    Does not set Policy objects. Those have to be filled in later.

    Writes high-level progress information to parameter output
    """
    result = []

    # paginate through groups and build result
    logger.info("Obtaining IAM groups in the account.")
    group_paginator = iamclient.get_paginator('list_groups')
    for page in group_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_groups page: {}'.format(page))
        for group in page['Groups']:
            result.append(Group(arn=group['Arn'], attached_policies=[]))

    # loop through group memberships
    logger.info("Connecting IAM users to their groups.")
    for node in nodes:
        if not arns.get_resource(node.arn).startswith('user/'):
            continue  # skip when not an IAM user
        logger.debug('finding groups for user {}'.format(node.arn))
        user_name = arns.get_resource(node.arn)[5:]
        if '/' in user_name:
            user_name = user_name.split('/')[-1]
            logger.debug('removed path from username {}'.format(user_name))
        group_list = iamclient.list_groups_for_user(UserName=user_name)
        for group in group_list['Groups']:
            for group_obj in result:
                if group['Arn'] == group_obj.arn:
                    node.group_memberships.append(group_obj)

    return result
Example #2
0
def pull_resource_policy_by_arn(session: botocore.session.Session, arn: Optional[str], query: str = None) -> dict:
    """helper function for pulling the resource policy for a resource at the denoted ARN.

    raises ValueError if it cannot be retrieved, or a botocore ClientError if another issue arises
    """
    if query is not None:
        if arn is not None:
            raise ValueError('Must specify either arn or query, not both.')
        pattern = re.compile(r'.*(arn:[^:]*:[^:]*:[^:]*:[^:]*:\S+).*')
        matches = pattern.match(query)
        if matches is None:
            raise ValueError('Resource policy retrieval error: could not extract resource ARN from query')
        arn = matches.group(1)
        if '?' in arn or '*' in arn:
            raise ValueError('Resource component from query must not have wildcard (? or *) when evaluating '
                             'resource policies.')

    service = arns.get_service(arn)
    if service == 'iam':
        # arn:aws:iam::<account_id>:role/<role_name>
        client = session.create_client('iam')
        role_name = arns.get_resource(arn).split('/')[-1]
        logger.debug('Calling IAM API to retrieve AssumeRolePolicyDocument of {}'.format(role_name))
        trust_doc = client.get_role(RoleName=role_name)['Role']['AssumeRolePolicyDocument']
        return trust_doc
    elif service == 's3':
        # arn:aws:s3:::<bucket>/<path_to_object_with_potential_colons>
        client = session.create_client('s3')
        bucket_name = arns.get_resource(arn).split('arn:aws:s3:::')[-1].split('/')[0]
        logger.debug('Calling S3 API to retrieve bucket policy of {}'.format(bucket_name))
        bucket_policy = json.loads(client.get_bucket_policy(Bucket=bucket_name)['Policy'])
        return bucket_policy
    elif service == 'sns':
        region = arns.get_region(arn)
        client = session.create_client('sns', region_name=region)
        logger.debug('Calling SNS API to retrieve topic policy of {}'.format(arn))
        policy_str = client.get_topic_attributes(TopicArn=arn)['Attributes']['Policy']
        return json.loads(policy_str)
    elif service == 'sqs':
        region = arns.get_region(arn)
        client = session.create_client('sqs', region_name=region)
        logger.debug('Calling SQS API to retrieve queue policy of {}'.format(arn))
        queue_url = 'https://sqs.{}.amazonaws.com/{}/{}'.format(
            arns.get_region(arn),
            arns.get_account_id(arn),
            arns.get_resource(arn)
        )  # TODO: future proof queue URL creation? this still work with FIFO queues?
        policy_str = client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=['Policy'])['Policy']
        return json.loads(policy_str)
    elif service == 'kms':
        region = arns.get_region(arn)
        client = session.create_client('kms', region_name=region)
        logger.debug('Calling KMS API to retrieve key policy of {}'.format(arn))
        key_policy = json.loads(client.get_key_policy(KeyId=arn, PolicyName='default')['Policy'])
        return key_policy
Example #3
0
def pull_cached_resource_policy_by_arn(graph: Graph, arn: Optional[str], query: str = None) -> Union[Policy, dict]:
    """Function that pulls a resource policy that's cached on-disk from the given Graph object.

    Returns either a Policy object or a dictionary representing the resource policy. Caller is responsible
    for checking before sending it along to other components.

    Raises ValueError if it is not able to be retrieved.
    """
    if query is not None:
        if arn is not None:
            raise ValueError('Must specify either arn or query, not both.')
        pattern = re.compile(r'.*(arn:[^:]*:[^:]*:[^:]*:[^:]*:\S+).*')
        matches = pattern.match(query)
        if matches is None:
            raise ValueError('Resource policy retrieval error: could not extract resource ARN from query')
        arn = matches.group(1)
    if '?' in arn or '*' in arn:
        raise ValueError('Resource component from query must not have wildcard (? or *) when evaluating '
                         'resource policies.')

    logger.debug('Looking for cached policy for {}'.format(arn))

    # manipulate the ARN as needed
    service = arns.get_service(arn)
    if service == 's3':
        # we only need the ARN of the bucket
        search_arn = 'arn:{}:s3:::{}'.format(arns.get_partition(arn), arns.get_resource(arn).split('/')[0])
    elif service == 'iam':
        # special case: trust policies
        role_name = arns.get_resource(arn).split('/')[-1]  # get the last part of :role/path/to/role_name
        role_node = graph.get_node_by_searchable_name('role/{}'.format(role_name))
        return role_node.trust_policy
    elif service == 'sns':
        search_arn = arn
    elif service == 'sqs':
        search_arn = arn
    elif service == 'kms':
        search_arn = arn
    elif service == 'secretsmanager':
        search_arn = arn
    else:
        raise NotImplementedError('Service policies for {} are not (currently) cached.'.format(service))

    for policy in graph.policies:
        if search_arn == policy.arn:
            return policy

    raise ValueError('Unable to locate a cached policy for resource {}'.format(arn))
Example #4
0
def _get_admin_reason(node: Node) -> List[str]:
    """Return a list of reasons why this given node is an admin."""

    result = []
    logger.debug("Checking if {} is an admin".format(node.searchable_name()))
    node_type = arns.get_resource(node.arn).split('/')[0]

    # check if node can modify its own inline policies
    if node_type == 'user':
        action = 'iam:PutUserPolicy'
    else:  # node_type == 'role'
        action = 'iam:PutRolePolicy'
    if query_interface.local_check_authorization_handling_mfa(node, action, node.arn, {})[0]:
        result.append('Can call {} to add/update their own inline policies'.format(action))

    # check if node can attach the AdministratorAccess policy to itself
    if node_type == 'user':
        action = 'iam:AttachUserPolicy'
    else:
        action = 'iam:AttachRolePolicy'
    condition_keys = {'iam:PolicyARN': 'arn:aws:iam::aws:policy/AdministratorAccess'}
    if query_interface.local_check_authorization_handling_mfa(node, action, node.arn, condition_keys)[0]:
        result.append('Can call {} to attach the AdministratorAccess policy to itself'.format(action))

    # check if node can create a role and attach the AdministratorAccess policy or an inline policy
    if query_interface.local_check_authorization_handling_mfa(node, 'iam:CreateRole', '*', {})[0]:
        if query_interface.local_check_authorization_handling_mfa(node, 'iam:AttachRolePolicy', '*',
                                                                  condition_keys)[0]:
            result.append('Can create an IAM Role (iam:CreateRole) and attach the AdministratorAccess policy to it (iam:AttachRolePolicy)'.format(action))
        if query_interface.local_check_authorization_handling_mfa(node, 'iam:PutRolePolicy', '*', condition_keys)[0]:
            result.append('Can create an IAM Role (iam:CreateRole) and create an inline policy for it (iam:PutRolePolicy)'.format(action))

    # check if node can update an attached customer-managed policy (assumes SetAsDefault is set to True)
    for attached_policy in node.attached_policies:
        if attached_policy.arn != node.arn and ':aws:policy/' not in attached_policy.arn:
            if query_interface.local_check_authorization_handling_mfa(node, 'iam:CreatePolicyVersion',
                                                                      attached_policy.arn, {})[0]:
                result.append('Can modify the attached managed policy {} (iam:CreatePolicyVersion)'.format(attached_policy.arn))
                break  # reduce output

    # check if node is a user, and if it can attach or modify any of its groups's policies
    if node_type == 'user':
        for group in node.group_memberships:
            group_name = group.arn.split('/')[-1]

            if query_interface.local_check_authorization_handling_mfa(node, 'iam:PutGroupPolicy', group.arn, {})[0]:
                result.append('Can add/update an inline policy for the group {} (iam:PutGroupPolicy)'.format(group_name))

            if query_interface.local_check_authorization_handling_mfa(node, 'iam:AttachGroupPolicy', group.arn,
                                                                      condition_keys)[0]:
                result.append('Can attach the AdministratorAccess policy to the group {} (iam:AttachGroupPolicy)'.format(group_name))

            for attached_policy in group.attached_policies:
                if attached_policy.arn != group.arn and ':aws:policy/' not in attached_policy.arn:
                    if query_interface.local_check_authorization_handling_mfa(node, 'iam:CreatePolicyVersion',
                                                                              attached_policy.arn, {})[0]:
                        result.append('Can update the managed policy {} that is attached to the group {} (iam:CreatePolicyVersion)'.format(attached_policy.arn, group_name))
                        break  # reduce output

    return result
Example #5
0
 def searchable_name(self):
     """Creates and caches the searchable name of this node. First it splits the user/.../name into its
     parts divided by slashes, then returns the first and last element. The last element is supposed to be unique
     within users and roles (RoleName/--role-name or UserName/--user-name parameter when using the API/CLI).
     """
     if 'searchable_name' not in self.cache:
         components = arns.get_resource(self.arn).split('/')
         self.cache['searchable_name'] = "{}/{}".format(components[0], components[-1])
     return self.cache['searchable_name']
Example #6
0
    def __init__(self, arn: str, attached_policies: Optional[List[Policy]]):
        """Constructor"""
        if arn is None or not arns.get_resource(arn).startswith('group/'):
            raise ValueError('Group objects must be constructed with a valid ARN for a group')
        self.arn = arn

        if attached_policies is None:
            self.attached_policies = []
        else:
            self.attached_policies = attached_policies
Example #7
0
    def __init__(self, arn: str, id_value: str, attached_policies: Optional[List[Policy]],
                 group_memberships: Optional[List[Group]], trust_policy: Optional[dict],
                 instance_profile: Optional[str], num_access_keys: int, active_password: bool, is_admin: bool):
        """Constructor. Expects an ARN and ID value. Validates parameters based on the type of Node (User/Role),
        and rejects contradictory arguments like an IAM User with a trust policy.
        """

        resource_value = arns.get_resource(arn)
        if arn is None or not (resource_value.startswith('user/') or resource_value.startswith('role/')):
            raise ValueError('The parameter arn must be a valid ARN for an IAM user or role.')
        self.arn = arn

        if id_value is None or len(id_value) == 0:
            raise ValueError('The parameter id_value must be a non-empty string.')
        self.id_value = id_value

        if attached_policies is None:
            self.attached_policies = []
        else:
            self.attached_policies = attached_policies

        if group_memberships is None:
            self.group_memberships = []
        else:
            self.group_memberships = group_memberships

        if resource_value.startswith('user/') and trust_policy is not None:
            raise ValueError('IAM users do not have trust policies, pass None for the parameter trust_policy.')
        if resource_value.startswith('role/') and (trust_policy is None or not isinstance(trust_policy, dict)):
            raise ValueError('IAM roles have trust policies, which must be passed as a dictionary in trust_policy')
        self.trust_policy = trust_policy  # None denotes no trust policy (not a role), {} denotes empty trust policy

        if resource_value.startswith('user/') and instance_profile is not None:
            raise ValueError('IAM users do not have instance profiles. Pass None for the parameter instance_profile.')
        self.instance_profile = instance_profile

        self.active_password = active_password

        if num_access_keys is None:
            self.access_keys = []
        else:
            self.access_keys = num_access_keys

        self.is_admin = is_admin

        self.cache = {}
Example #8
0
def pull_cached_resource_policy_by_arn(policies: List[Policy], arn: Optional[str], query: str = None) -> Policy:
    """Function that pulls a resource policy that's cached on-disk.

    Raises ValueError if it is not able to be retrieved.
    Returns the dict, not the Policy object.
    """
    if query is not None:
        if arn is not None:
            raise ValueError('Must specify either arn or query, not both.')
        pattern = re.compile(r'.*(arn:[^:]*:[^:]*:[^:]*:[^:]*:\S+).*')
        matches = pattern.match(query)
        if matches is None:
            raise ValueError('Resource policy retrieval error: could not extract resource ARN from query')
        arn = matches.group(1)
    if '?' in arn or '*' in arn:
        raise ValueError('Resource component from query must not have wildcard (? or *) when evaluating '
                         'resource policies.')

    # manipulate the ARN as needed
    service = arns.get_service(arn)
    if service == 's3':
        # we only need the ARN of the bucket
        search_arn = 'arn:{}:s3:::{}'.format(arns.get_partition(arn), arns.get_resource(arn).split('/')[0])
    elif service == 'iam':
        search_arn = arn
    elif service == 'sns':
        search_arn = arn
    elif service == 'sqs':
        search_arn = arn
    elif service == 'kms':
        search_arn = arn
    elif service == 'secretsmanager':
        search_arn = arn
    else:
        raise NotImplementedError('Service policies for {} are not (currently) cached.'.format(service))

    for policy in policies:
        if search_arn == policy.arn:
            return policy

    raise ValueError('Unable to locate a cached policy for resource {}'.format(arn))
Example #9
0
 def describe_edge(self) -> str:
     """Returns a human-readable string explaining the edge"""
     return "{} {} {}".format(arns.get_resource(self.source.arn),
                              self.reason,
                              arns.get_resource(self.destination.arn))
Example #10
0
def get_unfilled_nodes(iamclient,
                       output: io.StringIO = os.devnull,
                       debug=False) -> List[Node]:
    """Using an IAM.Client object, return a list of Node object for each IAM user and role in an account.

    Does not set Group or Policy objects. Those have to be filled in later.

    Writes high-level information on progress to the output file
    """
    result = []
    # Get users, paginating results, still need to handle policies + group memberships + is_admin
    output.write("Obtaining IAM users in account\n")
    user_paginator = iamclient.get_paginator('list_users')
    for page in user_paginator.paginate(PaginationConfig={'PageSize': 25}):
        dprint(debug, 'list_users page: {}'.format(page))
        for user in page['Users']:
            result.append(
                Node(arn=user['Arn'],
                     id_value=user['UserId'],
                     attached_policies=[],
                     group_memberships=[],
                     trust_policy=None,
                     instance_profile=None,
                     num_access_keys=0,
                     active_password='******' in user,
                     is_admin=False))
            dprint(debug, 'Adding Node for user ' + user['Arn'])

    # Get roles, paginating results, still need to handle policies + is_admin
    output.write("Obtaining IAM roles in account\n")
    role_paginator = iamclient.get_paginator('list_roles')
    for page in role_paginator.paginate(PaginationConfig={'PageSize': 25}):
        dprint(debug, 'list_roles page: {}'.format(page))
        for role in page['Roles']:
            result.append(
                Node(arn=role['Arn'],
                     id_value=role['RoleId'],
                     attached_policies=[],
                     group_memberships=[],
                     trust_policy=role['AssumeRolePolicyDocument'],
                     instance_profile=None,
                     num_access_keys=0,
                     active_password=False,
                     is_admin=False))

    # Get instance profiles, paginating results, and attach to roles as appropriate
    output.write("Obtaining EC2 instance profiles in account\n")
    ip_paginator = iamclient.get_paginator('list_instance_profiles')
    for page in ip_paginator.paginate(PaginationConfig={'PageSize': 25}):
        dprint(debug, 'list_instance_profiles page: {}'.format(page))
        for iprofile in page['InstanceProfiles']:
            iprofile_arn = iprofile['Arn']
            role_arns = []
            for role in iprofile['Roles']:
                role_arns.append(role['Arn'])
            for node in result:
                if ':role/' in node.arn and node.arn in role_arns:
                    node.instance_profile = iprofile_arn

    # Handle access keys
    output.write("Obtaining Access Keys data for IAM users\n")
    for node in result:
        if arns.get_resource(node.arn).startswith('user/'):
            # Grab access-key count and update node
            user_name = arns.get_resource(node.arn)[5:]
            if '/' in user_name:
                user_name = user_name.split('/')[-1]
                dprint(debug,
                       'removed path from username {}'.format(user_name))
            access_keys_data = iamclient.list_access_keys(UserName=user_name)
            num_access_keys = 0
            for access_key in access_keys_data['AccessKeyMetadata']:
                if access_key['Status'] == 'Active':
                    num_access_keys += 1
            node.access_keys = num_access_keys
            dprint(
                debug, 'Access Key Count for {}: {} {}'.format(
                    user_name, len(access_keys_data['AccessKeyMetadata']),
                    num_access_keys))

    return result
Example #11
0
def update_admin_status(nodes: List[Node],
                        output: io.StringIO = os.devnull,
                        debug: bool = False) -> None:
    """Given a list of nodes, goes through and updates each node's is_admin data."""
    for node in nodes:
        output.write("checking if {} is an admin\n".format(
            node.searchable_name()))
        node_type = arns.get_resource(node.arn).split('/')[0]

        # check if node can modify its own inline policies
        if node_type == 'user':
            action = 'iam:PutUserPolicy'
        else:  # node_type == 'role'
            action = 'iam:PutRolePolicy'
        if query_interface.local_check_authorization_handling_mfa(
                node, action, node.arn, {}, debug)[0]:
            node.is_admin = True
            continue

        # check if node can attach the AdministratorAccess policy to itself
        if node_type == 'user':
            action = 'iam:AttachUserPolicy'
        else:
            action = 'iam:AttachRolePolicy'
        condition_keys = {
            'iam:PolicyARN': 'arn:aws:iam::aws:policy/AdministratorAccess'
        }
        if query_interface.local_check_authorization_handling_mfa(
                node, action, node.arn, condition_keys, debug)[0]:
            node.is_admin = True
            continue

        # check if node can create a role and attach the AdministratorAccess policy or an inline policy
        if query_interface.local_check_authorization_handling_mfa(
                node, 'iam:CreateRole', '*', {}, debug)[0]:
            if query_interface.local_check_authorization_handling_mfa(
                    node, 'iam:AttachRolePolicy', '*', condition_keys,
                    debug)[0]:
                node.is_admin = True
                continue
            if query_interface.local_check_authorization_handling_mfa(
                    node, 'iam:PutRolePolicy', '*', condition_keys, debug)[0]:
                node.is_admin = True
                continue

        # check if node can update an attached customer-managed policy (assumes SetAsDefault is set to True)
        for attached_policy in node.attached_policies:
            if attached_policy.arn != node.arn:
                if query_interface.local_check_authorization_handling_mfa(
                        node, 'iam:CreatePolicyVersion', attached_policy.arn,
                    {}, debug)[0]:
                    node.is_admin = True
                    continue

        # check if node is a user, and if it can attach or modify any of its groups's policies
        if node_type == 'user':
            for group in node.group_memberships:
                if query_interface.local_check_authorization_handling_mfa(
                        node, 'iam:PutGroupPolicy', group.arn, {}, debug)[0]:
                    node.is_admin = True
                    break  # break the loop through groups
                if query_interface.local_check_authorization_handling_mfa(
                        node, 'iam:AttachGroupPolicy', group.arn,
                        condition_keys, debug)[0]:
                    node.is_admin = True
                    break  # as above
                for attached_policy in group.attached_policies:
                    if attached_policy.arn != group.arn:
                        if query_interface.local_check_authorization_handling_mfa(
                                node, 'iam:CreatePolicyVersion',
                                attached_policy.arn, {}, debug)[0]:
                            node.is_admin = True
                            break  # break the loop through policies
                if node.is_admin:
                    break  # break the loop through groups
            if node.is_admin:
                continue  # if we add more checks later, this optimizes them out when appropriate
Example #12
0
def get_policies_and_fill_out(iamclient,
                              nodes: List[Node],
                              groups: List[Group],
                              output: io.StringIO = os.devnull,
                              debug=False) -> List[Policy]:
    """Using an IAM.Client object, return a list of Policy objects. Adds references to each passed Node and
    Group object where applicable.

    Writes high-level progress information to parameter output
    """
    result = []

    # navigate through nodes and add policy objects if they do not already exist in result
    output.write("Obtaining policies used by all IAM users and roles\n")
    for node in nodes:
        node_name_components = arns.get_resource(node.arn).split('/')
        node_type, node_name = node_name_components[0], node_name_components[
            -1]
        dprint(debug, 'Grabbing inline policies for {}'.format(node.arn))
        # get inline policies
        if node_type == 'user':
            inline_policy_arns = iamclient.list_user_policies(
                UserName=node_name)
            # get each inline policy, append it to node's policies and result list
            for policy_name in inline_policy_arns['PolicyNames']:
                dprint(debug,
                       '   Grabbing inline policy: {}'.format(policy_name))
                inline_policy = iamclient.get_user_policy(
                    UserName=node_name, PolicyName=policy_name)
                policy_object = Policy(
                    arn=node.arn,
                    name=policy_name,
                    policy_doc=inline_policy['PolicyDocument'])
                node.attached_policies.append(policy_object)
                result.append(policy_object)
        elif node_type == 'role':
            inline_policy_arns = iamclient.list_role_policies(
                RoleName=node_name)
            # get each inline policy, append it to the node's policies and result list
            # in hindsight, it's possible this could be folded with the above code, assuming the API doesn't change
            for policy_name in inline_policy_arns['PolicyNames']:
                dprint(debug,
                       '   Grabbing inline policy: {}'.format(policy_name))
                inline_policy = iamclient.get_role_policy(
                    RoleName=node_name, PolicyName=policy_name)
                policy_object = Policy(
                    arn=node.arn,
                    name=policy_name,
                    policy_doc=inline_policy['PolicyDocument'])
                node.attached_policies.append(policy_object)
                result.append(policy_object)

        # get attached policies for users and roles
        if node_type == 'user':
            attached_policies = iamclient.list_attached_user_policies(
                UserName=node_name)
        else:  # node_type == 'role':
            attached_policies = iamclient.list_attached_role_policies(
                RoleName=node_name)
        for attached_policy in attached_policies['AttachedPolicies']:
            policy_arn = attached_policy['PolicyArn']
            dprint(debug, '   Grabbing managed policy: {}'.format(policy_arn))
            # reduce API calls, search existing policies for matching arns
            policy_object = _get_policy_by_arn(policy_arn, result)
            if policy_object is None:
                # Gotta retrieve the policy's current default version
                dprint(debug, '      Policy cache miss, calling API')
                policy_response = iamclient.get_policy(PolicyArn=policy_arn)
                dprint(
                    debug, '      Policy version: {}'.format(
                        policy_response['Policy']['DefaultVersionId']))
                policy_version_response = iamclient.get_policy_version(
                    PolicyArn=policy_arn,
                    VersionId=policy_response['Policy']['DefaultVersionId'])
                policy_object = Policy(
                    arn=policy_arn,
                    name=policy_response['Policy']['PolicyName'],
                    policy_doc=policy_version_response['PolicyVersion']
                    ['Document'])
                result.append(policy_object)
            node.attached_policies.append(policy_object)

    output.write("Obtaining policies used by IAM groups\n")
    for group in groups:
        group_name = arns.get_resource(group.arn).split(
            '/', 1)[-1]  # split by slashes and take the final item
        dprint(debug, 'Getting policies for: {}'.format(group.arn))
        # get inline policies
        inline_policies = iamclient.list_group_policies(GroupName=group_name)
        for policy_name in inline_policies['PolicyNames']:
            dprint(debug, '   Grabbing inline policy: {}'.format(policy_name))
            inline_policy = iamclient.get_group_policy(GroupName=group_name,
                                                       PolicyName=policy_name)
            policy_object = Policy(arn=group.arn,
                                   name=policy_name,
                                   policy_doc=inline_policy['PolicyDocument'])
            group.attached_policies.append(policy_object)
            result.append(policy_object)

        # get attached policies
        attached_policies = iamclient.list_attached_group_policies(
            GroupName=group_name)
        for attached_policy in attached_policies['AttachedPolicies']:
            policy_arn = attached_policy['PolicyArn']
            dprint(debug, '   Grabbing managed policy: {}'.format(policy_arn))
            # check cached policies first
            policy_object = _get_policy_by_arn(policy_arn, result)
            if policy_object is None:
                dprint(debug, '      Policy cache miss, calling API')
                policy_response = iamclient.get_policy(PolicyArn=policy_arn)
                dprint(
                    debug, '      Policy version: {}'.format(
                        policy_response['Policy']['DefaultVersionId']))
                policy_version_response = iamclient.get_policy_version(
                    PolicyArn=policy_arn,
                    VersionId=policy_response['Policy']['DefaultVersionId'])
                policy_object = Policy(
                    arn=policy_arn,
                    name=policy_response['Policy']['PolicyName'],
                    policy_doc=policy_version_response['PolicyVersion']
                    ['Document'])
                result.append(policy_object)
            group.attached_policies.append(policy_object)

    return result
Example #13
0
def get_nodes_groups_and_policies(iamclient) -> dict:
    """Using an IAM.Client object, return a dictionary containing nodes, groups, and policies to be
    added to a Graph object. Admin status for the nodes are not updated.

    Writes high-level information on progress to the output stream.
    """
    logger.info('Obtaining IAM Users/Roles/Groups/Policies in the account.')
    result_paginator = iamclient.get_paginator(
        'get_account_authorization_details')
    user_results = []
    group_results = []
    role_results = []
    policy_results = []
    for page in result_paginator.paginate():
        user_results += page['UserDetailList']
        group_results += page['GroupDetailList']
        role_results += page['RoleDetailList']
        policy_results += page['Policies']

    logger.info(
        'Sorting users, roles, groups, policies, and their relationships.')

    # this is the result we return: dictionary with nodes/groups/users all filled out
    result = {'nodes': [], 'groups': [], 'policies': []}

    for p in policy_results:
        # go through each policy and update policy_results
        doc = [
            x['Document'] for x in p['PolicyVersionList']
            if x['IsDefaultVersion']
        ][0]
        result['policies'].append(Policy(p['Arn'], p['PolicyName'], doc))

    for g in group_results:
        # go through all inline policies and update policy_results
        group_policies = []
        if 'GroupPolicyList' in g:  # have to key-check these
            for p in g['GroupPolicyList']:
                group_policies.append(
                    Policy(
                        g['Arn'],  # inline policies get the same Arn as their principal
                        p['PolicyName'],
                        p['PolicyDocument']))
            result[
                'policies'] += group_policies  # this is just adding the inline policies for the group

        for p in g['AttachedManagedPolicies']:
            group_policies.append(
                _get_policy_by_arn_or_raise(p['PolicyArn'],
                                            result['policies']))

        result['groups'].append(Group(g['Arn'], group_policies))

    for u in user_results:
        # go through all inline policies and update policy_results
        user_policies = []
        if 'UserPolicyList' in u:  # have to key-check these
            for p in u['UserPolicyList']:
                user_policies.append(
                    Policy(
                        u['Arn'],  # inline policies inherit the Arn of their principal for the purposes of tracking
                        p['PolicyName'],
                        p['PolicyDocument']))
            result['policies'] += user_policies

        for p in u['AttachedManagedPolicies']:
            user_policies.append(
                _get_policy_by_arn_or_raise(p['PolicyArn'],
                                            result['policies']))

        if 'PermissionsBoundary' in u:
            boundary_policy = _get_policy_by_arn_or_raise(
                u['PermissionsBoundary']['PermissionsBoundaryArn'],
                result['policies'])
        else:
            boundary_policy = None

        group_list = []
        for group_name in u['GroupList']:
            for group in result['groups']:
                if arns.get_resource(group.arn).split('/')[-1] == group_name:
                    group_list.append(group)
                    break

        _tags = {}
        if 'Tags' in u:
            for tag in u['Tags']:
                _tags[tag['Key']] = tag['Value']

        # still need to figure out access keys
        result['nodes'].append(
            Node(u['Arn'], u['UserId'], user_policies, group_list, None, None,
                 0, 'PasswordLastUsed' in u, False, boundary_policy, False,
                 _tags))

    for r in role_results:
        # go through all inline policies and update policy_results
        role_policies = []
        for p in r['RolePolicyList']:
            role_policies.append(
                Policy(
                    r['Arn'],  # inline policies inherit the Arn of their principal for the purposes of tracking
                    p['PolicyName'],
                    p['PolicyDocument']))
        result['policies'] += role_policies

        for p in r['AttachedManagedPolicies']:
            role_policies.append(
                _get_policy_by_arn_or_raise(p['PolicyArn'],
                                            result['policies']))

        _tags = {}
        if 'Tags' in r:
            for tag in r['Tags']:
                _tags[tag['Key']] = tag['Value']

        result['nodes'].append(
            Node(r['Arn'], r['RoleId'], role_policies, None,
                 r['AssumeRolePolicyDocument'],
                 [x['Arn'] for x in r['InstanceProfileList']], 0, False, False,
                 None, False, _tags))

    logger.info("Obtaining Access Keys data for IAM users")
    for node in result['nodes']:
        if arns.get_resource(node.arn).startswith('user/'):
            # Grab access-key count and update node
            user_name = arns.get_resource(node.arn)[5:]
            if '/' in user_name:
                user_name = user_name.split('/')[-1]
            access_keys_data = iamclient.list_access_keys(UserName=user_name)
            node.access_keys = len(access_keys_data['AccessKeyMetadata'])
            # logger.debug('Access Key Count for {}: {}'.format(user_name, len(access_keys_data['AccessKeyMetadata'])))

    logger.info('Gathering MFA virtual device information')
    mfa_paginator = iamclient.get_paginator('list_virtual_mfa_devices')
    for page in mfa_paginator.paginate(AssignmentStatus='Assigned'):
        for device in page['VirtualMFADevices']:
            user_arn = device['User']['Arn']
            logger.debug('Found virtual MFA device for {}'.format(user_arn))
            for node in result['nodes']:
                if node.arn == user_arn:
                    node.has_mfa = True
                    break

    logger.info('Gathering MFA physical device information')
    for node in result['nodes']:
        node_resource_name = arns.get_resource(node.arn)
        if node_resource_name.startswith('user/'):
            user_name = node_resource_name.split('/')[-1]
            mfa_devices_response = iamclient.list_mfa_devices(
                UserName=user_name)
            if len(mfa_devices_response['MFADevices']) > 0:
                node.has_mfa = True

    return result
Example #14
0
def get_unfilled_nodes(iamclient) -> List[Node]:
    """Using an IAM.Client object, return a list of Node object for each IAM user and role in an account.

    Does not set Group or Policy objects, does not set permissions boundary attr. Those have to be filled in later.

    Writes high-level information on progress to the output file
    """
    result = []
    # Get users, paginating results, still need to handle policies + group memberships + is_admin
    logger.info("Obtaining IAM users in account")
    user_paginator = iamclient.get_paginator('list_users')
    for page in user_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_users page: {}'.format(page))
        for user in page['Users']:
            # grab permission boundary ARN if applicable
            # TODO: iam:ListUsers does not return boundary information. may need to wait for a fix.
            if 'PermissionsBoundary' in user:
                _pb = user['PermissionsBoundary']['PermissionsBoundaryArn']
            else:
                _pb = None
            result.append(
                Node(
                    arn=user['Arn'],
                    id_value=user['UserId'],
                    attached_policies=[],
                    group_memberships=[],
                    trust_policy=None,
                    instance_profile=None,
                    num_access_keys=0,
                    active_password='******' in user,
                    is_admin=False,
                    permissions_boundary=_pb,
                    has_mfa=False,
                    tags=None  # TODO: fix tags for old user-gathering method
                ))
            logger.debug('Adding Node for user ' + user['Arn'])

    # Get roles, paginating results, still need to handle policies + is_admin
    logger.info("Obtaining IAM roles in account")
    role_paginator = iamclient.get_paginator('list_roles')
    for page in role_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_roles page: {}'.format(page))
        for role in page['Roles']:
            # grab permission boundary ARN if applicable
            if 'PermissionsBoundary' in role:
                _pb = role['PermissionsBoundary']['PermissionsBoundaryArn']
            else:
                _pb = None
            result.append(
                Node(
                    arn=role['Arn'],
                    id_value=role['RoleId'],
                    attached_policies=[],
                    group_memberships=[],
                    trust_policy=role['AssumeRolePolicyDocument'],
                    instance_profile=None,
                    num_access_keys=0,
                    active_password=False,
                    is_admin=False,
                    permissions_boundary=_pb,
                    has_mfa=False,
                    tags=None  # TODO: fix tags for old role-gathering method
                ))

    # Get instance profiles, paginating results, and attach to roles as appropriate
    logger.info("Obtaining EC2 instance profiles in account")
    ip_paginator = iamclient.get_paginator('list_instance_profiles')
    for page in ip_paginator.paginate(PaginationConfig={'PageSize': 25}):
        logger.debug('list_instance_profiles page: {}'.format(page))
        for iprofile in page['InstanceProfiles']:
            iprofile_arn = iprofile['Arn']
            role_arns = []
            for role in iprofile['Roles']:
                role_arns.append(role['Arn'])
            for node in result:
                if ':role/' in node.arn and node.arn in role_arns:
                    node.instance_profile = iprofile_arn

    # Handle access keys
    logger.info("Obtaining Access Keys data for IAM users")
    for node in result:
        if arns.get_resource(node.arn).startswith('user/'):
            # Grab access-key count and update node
            user_name = arns.get_resource(node.arn)[5:]
            if '/' in user_name:
                user_name = user_name.split('/')[-1]
                logger.debug('removed path from username {}'.format(user_name))
            access_keys_data = iamclient.list_access_keys(UserName=user_name)
            node.access_keys = len(access_keys_data['AccessKeyMetadata'])
            logger.debug('Access Key Count for {}: {}'.format(
                user_name, len(access_keys_data['AccessKeyMetadata'])))

    return result