Esempio n. 1
0
def _get_role_data(roleID, fields=None):
    """
    Get raw role data as a dictionary for a given role by ID
    Do not use for data presented to the user because this data still has dynamo empty string placeholders, use
    get_role_data() instead

    Args:
        roleID (string)

    Returns:
        dict: data for the role if it exists, else None
    """
    try:
        if fields:
            response = DYNAMO_TABLE.get_item(Key={'RoleId': roleID},
                                             AttributesToGet=fields)
        else:
            response = DYNAMO_TABLE.get_item(Key={'RoleId': roleID})
    except BotoClientError as e:
        LOGGER.error('Dynamo table error: {}'.format(e))
    else:
        if 'Item' in response:
            return response['Item']
        else:
            return None
Esempio n. 2
0
def _get_permissions_in_policy(policy_dict, warn_unknown_perms=False):
    """
    Given a set of policies for a role, return a set of all allowed permissions

    Args:
        policy_dict
        warn_unknown_perms

    Returns
        set - all permissions allowed by the policies
    """
    permissions = set()

    for policy_name, policy in policy_dict.items():
        policy = expand_policy(policy=policy, expand_deny=False)
        for statement in policy.get('Statement'):
            if statement['Effect'].lower() == 'allow':
                permissions = permissions.union(
                    get_actions_from_statement(statement))

    weird_permissions = permissions.difference(all_permissions)
    if weird_permissions and warn_unknown_perms:
        LOGGER.warn('Unknown permissions found: {}'.format(weird_permissions))

    return permissions
Esempio n. 3
0
def role_ids_for_all_accounts():
    """
    Get a list of all role IDs for all accounts by scanning the Dynamo table

    Args:
        None

    Returns:
        list: role ids in all accounts
    """
    role_ids = []

    try:
        response = DYNAMO_TABLE.scan(ProjectionExpression='RoleId')
        role_ids.extend(
            [role_dict['RoleId'] for role_dict in response['Items']])

        while 'LastEvaluatedKey' in response:
            response = DYNAMO_TABLE.scan(
                ProjectionExpression='RoleId',
                ExclusiveStartKey=response['LastEvaluatedKey'])
            role_ids.extend(
                [role_dict['RoleId'] for role_dict in response['Items']])
    except BotoClientError as e:
        LOGGER.error('Dynamo table error: {}'.format(e))

    return role_ids
Esempio n. 4
0
def update_aardvark_data(aardvark_data, roles):
    """
    Update Aardvark data for a given set of roles by looking for the ARN in the aardvark data dict.
    If the ARN is in Aardvark data update the role's aa_data attribute and Dynamo.

    Args:
        aardvark_data (dict): A dict of Aardvark data from an account
        roles (Roles): a list of all the role objects to update data for

    Returns:
        None
    """
    for role in roles:
        if role.arn in aardvark_data:
            role.aa_data = aardvark_data[role.arn]
        try:
            DYNAMO_TABLE.update_item(Key={'RoleId': role.role_id},
                                     UpdateExpression="SET AAData=:aa_data",
                                     ExpressionAttributeValues={
                                         ":aa_data":
                                         _empty_string_to_dynamo_replace(
                                             role.aa_data)
                                     })
        except BotoClientError as e:
            LOGGER.error('Dynamo table error: {}'.format(e))
Esempio n. 5
0
def find_and_mark_inactive(account_number, active_roles):
    """
    Mark roles in the account that aren't currently active inactive. Do this by getting all roles in the account and
    subtracting the active roles, any that are left are inactive and should be marked thusly.

    Args:
        account_number (string)
        active_roles (set): the currently active roles discovered in the most recent scan

    Returns:
        None
    """

    active_roles = set(active_roles)
    known_roles = set(role_ids_for_account(account_number))
    inactive_roles = known_roles - active_roles

    for roleID in inactive_roles:
        role_dict = _get_role_data(roleID, fields=['Active', 'Arn'])
        if role_dict['Active']:
            try:
                DYNAMO_TABLE.update_item(
                    Key={'RoleId': roleID},
                    UpdateExpression="SET Active = :false",
                    ExpressionAttributeValues={":false": False})
            except BotoClientError as e:
                LOGGER.error('Dynamo table error: {}'.format(e))
            else:
                LOGGER.info('Marked role ({}): {} inactive'.format(
                    roleID, role_dict['Arn']))
Esempio n. 6
0
def role_ids_for_account(account_number):
    """
    Get a list of all role IDs in a given account by querying the Dynamo secondary index 'account'

    Args:
        account_number (string)

    Returns:
        list: role ids in given account
    """
    role_ids = set()
    try:
        results = DYNAMO_TABLE.query(
            IndexName='Account',
            ProjectionExpression='RoleId',
            KeyConditionExpression='Account = :act',
            ExpressionAttributeValues={':act': account_number})
        role_ids.update(
            [return_dict['RoleId'] for return_dict in results.get('Items')])

        while 'LastEvaluatedKey' in results:
            results = DYNAMO_TABLE.query(
                IndexName='Account',
                ProjectionExpression='RoleId',
                KeyConditionExpression='Account = :act',
                ExpressionAttributeValues={':act': account_number},
                ExclusiveStartKey=results.get('LastEvaluatedKey'))
            role_ids.update([
                return_dict['RoleId'] for return_dict in results.get('Items')
            ])
    except BotoClientError as e:
        LOGGER.error('Dynamo table error: {}'.format(e))

    return role_ids
Esempio n. 7
0
def update_repoed_description(role_name, client=None):
    description = None
    try:
        description = client.get_role(RoleName=role_name)["Role"].get(
            "Description", "")
    except KeyError:
        return
    date_string = datetime.datetime.utcnow().strftime("%m/%d/%y")
    if "; Repokid repoed" in description:
        new_description = re.sub(
            r"; Repokid repoed [0-9]{2}\/[0-9]{2}\/[0-9]{2}",
            "; Repokid repoed {}".format(date_string),
            description,
        )
    else:
        new_description = description + " ; Repokid repoed {}".format(
            date_string)
    # IAM role descriptions have a max length of 1000, if our new length would be longer, skip this
    if len(new_description) < 1000:
        client.update_role_description(RoleName=role_name,
                                       Description=new_description)
    else:
        LOGGER.error(
            "Unable to set repo description ({}) for role {}, length would be too long"
            .format(new_description, role_name))
Esempio n. 8
0
def replace_policies(repoed_policies, role, account_number, conn):
    """Overwrite IAM Role inline policies with those supplied.

    Args:
        repoed_policies (dict)
        role (Role object)
        account_number (string)
        conn (dict)

    Returns:
        error (string) or None
    """
    LOGGER.info("Replacing Policies With: \n{} (role: {} account: {})".format(
        json.dumps(repoed_policies, indent=2, sort_keys=True),
        role.role_name,
        account_number,
    ))

    for policy_name, policy in repoed_policies.items():
        try:
            put_role_policy(
                RoleName=role.role_name,
                PolicyName=policy_name,
                PolicyDocument=json.dumps(policy, indent=2, sort_keys=True),
                **conn,
            )

        except botocore.exceptions.ClientError as e:
            error = "Exception calling PutRolePolicy on {role}/{policy} in account {account}\n{e}\n".format(
                role=role.role_name,
                policy=policy_name,
                account=account_number,
                e=str(e),
            )
            return error
Esempio n. 9
0
    def __init__(self, config=None):
        blocklist_json = None
        bucket_config = config.get('blocklist_bucket',
                                   config.get('blacklist_bucket', None))
        if bucket_config:
            blocklist_json = get_blocklist_from_bucket(bucket_config)

        current_account = config.get('current_account') or None
        if not current_account:
            LOGGER.error('Unable to get current account for Blocklist Filter')

        blocklisted_role_names = set()
        blocklisted_role_names.update(
            [rolename.lower() for rolename in config.get(current_account, [])])
        blocklisted_role_names.update(
            [rolename.lower() for rolename in config.get('all', [])])

        if blocklist_json:
            blocklisted_role_names.update([
                name.lower()
                for name, accounts in blocklist_json['names'].items()
                if ('all' in accounts
                    or config.get('current_account') in accounts)
            ])

        self.blocklisted_arns = set(
        ) if not blocklist_json else blocklist_json.get('arns', [])
        self.blocklisted_role_names = blocklisted_role_names
Esempio n. 10
0
def _get_role_permissions(role, warn_unknown_perms=False):
    """
    Expand the most recent version of policies from a role to produce a list of all the permissions that are allowed
    (permission is included in one or more statements that is allowed).  To perform expansion the policyuniverse
    library is used. The result is a list of all of the individual permissions that are allowed in any of the
    statements. If our resultant list contains any permissions that aren't listed in the master list of permissions
    we'll raise an exception with the set of unknown permissions found.

    Args:
        role (Role): The role object that we're getting a list of permissions for

    Returns:
        set: A set of permissions that the role has policies that allow
    """
    permissions = set()

    for policy_name, policy in role.policies[-1]['Policy'].items():
        policy = expand_policy(policy=policy, expand_deny=False)
        for statement in policy.get('Statement'):
            if statement['Effect'].lower() == 'allow':
                permissions = permissions.union(
                    get_actions_from_statement(statement))

    weird_permissions = permissions.difference(all_permissions)
    if weird_permissions and warn_unknown_perms:
        LOGGER.warn('Unknown permissions found: {}'.format(weird_permissions))

    return permissions
Esempio n. 11
0
def update_role_data(dynamo_table,
                     account_number,
                     role,
                     current_policy,
                     source='Scan',
                     add_no_repo=True):
    """
    Compare the current version of a policy for a role and what has been previously stored in Dynamo.
      - If current and new policy versions are different store the new version in Dynamo. Add any newly added
          permissions to temporary permission blacklist. Purge any old entries from permission blacklist.
      - Refresh the updated time on the role policy
      - If the role is completely new, store the first version in Dynamo
      - Updates the role with full history of policies, including current version

    Args:
        dynamo_table
        account_number
        role (Role): current role being updated
        current_policy (dict): representation of the current policy version
        source: Default 'Scan' but could be Repo, Rollback, etc

    Returns:
        None
    """

    # policy_entry: source, discovered, policy
    stored_role = get_role_data(dynamo_table,
                                role.role_id,
                                fields=['OptOut', 'Policies'])
    if not stored_role:
        role_dict = store_initial_role_data(dynamo_table, role.arn,
                                            role.create_date, role.role_id,
                                            role.role_name, account_number,
                                            current_policy)
        role.set_attributes(role_dict)
        LOGGER.info('Added new role ({}): {}'.format(role.role_id, role.arn))
    else:
        # is the policy list the same as the last we had?
        old_policy = stored_role['Policies'][-1]['Policy']
        if current_policy != old_policy:
            add_new_policy_version(dynamo_table, role, current_policy, source)
            LOGGER.info(
                '{} has different inline policies than last time, adding to role store'
                .format(role.arn))

            newly_added_permissions = find_newly_added_permissions(
                old_policy, current_policy)
        else:
            newly_added_permissions = set()

        if add_no_repo:
            update_no_repo_permissions(dynamo_table, role,
                                       newly_added_permissions)
        update_opt_out(dynamo_table, role)
        set_role_data(dynamo_table, role.role_id,
                      {'Refreshed': datetime.datetime.utcnow().isoformat()})

        role.policies = get_role_data(dynamo_table,
                                      role.role_id,
                                      fields=['Policies'])['Policies']
Esempio n. 12
0
def update_stats(roles, source='Scan'):
    """
    Create a new stats entry for each role in a set of roles and add it to Dynamo

    Args:
        roles (Roles): a list of all the role objects to update data for
        source (string): the source of the new stats data (repo, scan, etc)

    Returns:
        None
    """
    for role in roles:
        cur_stats = {
            'Date': datetime.datetime.utcnow().isoformat(),
            'DisqualifiedBy': role.disqualified_by,
            'PermissionsCount': role.total_permissions,
            'Source': source
        }

        try:
            DYNAMO_TABLE.update_item(
                Key={'RoleId': role.role_id},
                UpdateExpression=("SET #statsarray = list_append(if_not_exists"
                                  "(#statsarray, :empty_list), :stats)"),
                ExpressionAttributeNames={"#statsarray": "Stats"},
                ExpressionAttributeValues={
                    ":empty_list": [],
                    ":stats": [cur_stats]
                })

        except BotoClientError as e:
            LOGGER.error('Dynamo table error: {}'.format(e))
Esempio n. 13
0
def log_deleted_and_repoed_policies(deleted_policy_names, repoed_policies,
                                    role_name, account_number):
    """Logs data on policies that would otherwise be modified or deleted if the commit flag were set.

    Args:
        deleted_policy_names (list<string>)
        repoed_policies (list<dict>)
        role_name (string)
        account_number (string)

    Returns:
        None
    """
    for name in deleted_policy_names:
        LOGGER.info(
            "Would delete policy from {} with name {} in account {}".format(
                role_name, name, account_number))

    if repoed_policies:
        LOGGER.info(
            "Would replace policies for role {} with: \n{} in account {}".
            format(
                role_name,
                json.dumps(repoed_policies, indent=2, sort_keys=True),
                account_number,
            ))
Esempio n. 14
0
def log_before_repo_roles(input_dict):
    LOGGER.debug("Calling DURING_REPOABLE_CALCULATION hooks")
    if not all(required in input_dict
               for required in ['account_number', 'roles']):
        raise hooks.MissingHookParamaeter(
            "Did not get all required parameters for BEFORE_REPO_ROLES hook")
    return input_dict
Esempio n. 15
0
def _calculate_repo_scores(roles, minimum_age, hooks, batch=False, batch_size=100):
    """
    Get the total and repoable permissions count and set of repoable services for every role in the account.
    For each role:
      1) call _get_role_permissions
      2) call _get_repoable_permissions (count), repoable_permissions (count), and repoable_services (list) for role

    Each time we got the role permissions we built a list of any permissions that the role's policies granted access
    to but weren't in our master list of permissions AWS has.  At the end of this run we'll warn about any of these.

    Args:
        roles (Roles): The set of all roles we're analyzing
        minimum_age
        hooks

    Returns:
        None
    """
    repo_able_roles = []
    eligible_permissions_dict = {}
    for role in roles:
        total_permissions, eligible_permissions = _get_role_permissions(role)
        role.total_permissions = len(total_permissions)

        # if we don't have any access advisor data for a service than nothing is repoable
        if not role.aa_data:
            LOGGER.info("No data found in access advisor for {}".format(role.role_id))
            role.repoable_permissions = 0
            role.repoable_services = []
            continue

        # permissions are only repoable if the role isn't being disqualified by filter(s)
        if len(role.disqualified_by) == 0:
            repo_able_roles.append(role)
            eligible_permissions_dict[role.arn] = eligible_permissions
        else:
            role.repoable_permissions = 0
            role.repoable_services = []

    repoable_permissions_dict = {}
    if batch:
        repoable_permissions_dict = _get_repoable_permissions_batch(
            repo_able_roles, eligible_permissions_dict, minimum_age, hooks, batch_size
        )
    else:
        for role in repo_able_roles:
            repoable_permissions_dict[role.arn] = _get_repoable_permissions(
                role.account,
                role.role_name,
                eligible_permissions_dict[role.arn],
                role.aa_data,
                role.no_repo_permissions,
                minimum_age,
                hooks,
            )

    for role in repo_able_roles:
        eligible_permissions = eligible_permissions_dict[role.arn]
        repoable_permissions = repoable_permissions_dict[role.arn]
        _update_repoable_services(role, repoable_permissions, eligible_permissions)
Esempio n. 16
0
    def __init__(self, config=None):
        blocklist_json = None
        bucket_config = config.get(
            "blocklist_bucket", config.get("blacklist_bucket", None)
        )
        if bucket_config:
            blocklist_json = get_blocklist_from_bucket(bucket_config)

        current_account = config.get("current_account") or None
        if not current_account:
            LOGGER.error("Unable to get current account for Blocklist Filter")

        blocklisted_role_names = set()
        blocklisted_role_names.update(
            [rolename.lower() for rolename in config.get(current_account, [])]
        )
        blocklisted_role_names.update(
            [rolename.lower() for rolename in config.get("all", [])]
        )

        if blocklist_json:
            blocklisted_role_names.update(
                [
                    name.lower()
                    for name, accounts in blocklist_json["names"].items()
                    if ("all" in accounts or config.get("current_account") in accounts)
                ]
            )

        self.blocklisted_arns = (
            set() if not blocklist_json else blocklist_json.get("arns", [])
        )
        self.blocklisted_role_names = blocklisted_role_names
Esempio n. 17
0
def _get_permissions_in_policy(policy_dict, warn_unknown_perms=False):
    """
    Given a set of policies for a role, return a set of all allowed permissions

    Args:
        policy_dict
        warn_unknown_perms

    Returns
        tuple
        set - all permissions allowed by the policies
        set - all permisisons allowed by the policies not marked with STATEMENT_SKIP_SID
    """
    total_permissions = set()
    eligible_permissions = set()

    for policy_name, policy in list(policy_dict.items()):
        policy = expand_policy(policy=policy, expand_deny=False)
        for statement in policy.get("Statement"):
            if statement["Effect"].lower() == "allow":
                total_permissions = total_permissions.union(
                    get_actions_from_statement(statement))
                if not ("Sid" in statement
                        and statement["Sid"].startswith(STATEMENT_SKIP_SID)):
                    # No Sid
                    # Sid exists, but doesn't start with STATEMENT_SKIP_SID
                    eligible_permissions = eligible_permissions.union(
                        get_actions_from_statement(statement))

    weird_permissions = total_permissions.difference(all_permissions)
    if weird_permissions and warn_unknown_perms:
        LOGGER.warn("Unknown permissions found: {}".format(weird_permissions))

    return total_permissions, eligible_permissions
Esempio n. 18
0
def repo_stats(output_file, dynamo_table, account_number=None):
    """
    Create a csv file with stats about roles, total permissions, and applicable filters over time

    Args:
        output_file (string): the name of the csv file to write
        account_number (string): if specified only display roles from selected account, otherwise display all

    Returns:
        None
    """
    roleIDs = (role_ids_for_account(dynamo_table, account_number) if account_number else
               role_ids_for_all_accounts(dynamo_table))
    headers = ['RoleId', 'Role Name', 'Account', 'Active', 'Date', 'Source', 'Permissions Count',
               'Repoable Permissions Count', 'Disqualified By']
    rows = []

    for roleID in roleIDs:
        role_data = get_role_data(dynamo_table, roleID, fields=['RoleId', 'RoleName', 'Account', 'Active', 'Stats'])
        for stats_entry in role_data.get('Stats', []):
            rows.append([role_data['RoleId'], role_data['RoleName'], role_data['Account'], role_data['Active'],
                         stats_entry['Date'], stats_entry['Source'], stats_entry['PermissionsCount'],
                         stats_entry.get('RepoablePermissionsCount'), stats_entry.get('DisqualifiedBy', [])])

    try:
        with open(output_file, 'wb') as csvfile:
            csv_writer = csv.writer(csvfile)
            csv_writer.writerow(headers)
            for row in rows:
                csv_writer.writerow(row)
    except IOError as e:
        LOGGER.error('Unable to write file {}: {}'.format(output_file, e))
    else:
        LOGGER.info('Successfully wrote stats to {}'.format(output_file))
Esempio n. 19
0
def schedule_repo(account_number, dynamo_table, config, hooks):
    """
    Schedule a repo for a given account.  Schedule repo for a time in the future (default 7 days) for any roles in
    the account with repoable permissions.
    """
    scheduled_roles = []

    roles = Roles([Role(get_role_data(dynamo_table, roleID))
                  for roleID in tqdm(role_ids_for_account(dynamo_table, account_number))])

    scheduled_time = int(time.time()) + (86400 * config.get('repo_schedule_period_days', 7))
    for role in roles:
        if role.repoable_permissions > 0 and not role.repo_scheduled:
            role.repo_scheduled = scheduled_time
            # freeze the scheduled perms to whatever is repoable right now
            set_role_data(dynamo_table, role.role_id,
                          {'RepoScheduled': scheduled_time, 'ScheduledPerms': role.repoable_services})

            scheduled_roles.append(role)

    LOGGER.info("Scheduled repo for {} days from now for account {} and these roles:\n\t{}".format(
                config.get('repo_schedule_period_days', 7),
                account_number,
                ', '.join([r.role_name for r in scheduled_roles])))

    repokid.hooks.call_hooks(hooks, 'AFTER_SCHEDULE_REPO', {'roles': scheduled_roles})
Esempio n. 20
0
def cancel_scheduled_repo(account_number, dynamo_table, role_name=None, is_all=None):
    """
    Cancel scheduled repo for a role in an account
    """
    if not is_all and not role_name:
        LOGGER.error('Either a specific role to cancel or all must be provided')
        return

    if is_all:
        roles = Roles([Role(get_role_data(dynamo_table, roleID))
                      for roleID in role_ids_for_account(dynamo_table, account_number)])

        # filter to show only roles that are scheduled
        roles = [role for role in roles if (role.repo_scheduled)]

        for role in roles:
            set_role_data(dynamo_table, role.role_id, {'RepoScheduled': 0, 'ScheduledPerms': []})

        LOGGER.info('Canceled scheduled repo for roles: {}'.format(', '.join([role.role_name for role in roles])))
        return

    role_id = find_role_in_cache(dynamo_table, account_number, role_name)
    if not role_id:
        LOGGER.warn('Could not find role with name {} in account {}'.format(role_name, account_number))
        return

    role = Role(get_role_data(dynamo_table, role_id))

    if not role.repo_scheduled:
        LOGGER.warn('Repo was not scheduled for role {} in account {}'.format(role.role_name, account_number))
        return

    set_role_data(dynamo_table, role.role_id, {'RepoScheduled': 0, 'ScheduledPerms': []})
    LOGGER.info('Successfully cancelled scheduled repo for role {} in account {}'.format(role.role_name,
                role.account))
Esempio n. 21
0
def log_during_repoable_calculation_hooks(input_dict):
    LOGGER.debug("Calling DURING_REPOABLE_CALCULATION hooks")
    if not all(required in input_dict for required in [
            'account_number', 'role_name', 'potentially_repoable_permissions',
            'minimum_age'
    ]):
        raise hooks.MissingHookParamaeter(
            "Did not get all required parameters for DURING_REPOABLE_CALCULATION hook"
        )
    return input_dict
Esempio n. 22
0
    def __init__(self, config=None):
        current_account = config.get("current_account") or None
        if not current_account:
            LOGGER.error("Unable to get current account for Exclusive Filter")

        exclusive_role_globs = set()
        exclusive_role_globs.update([
            role_glob.lower() for role_glob in config.get(current_account, [])
        ])
        exclusive_role_globs.update(
            [role_glob.lower() for role_glob in config.get("all", [])])

        self.exclusive_role_globs = exclusive_role_globs
Esempio n. 23
0
def _calculate_repo_scores(roles):
    """
    Get the total and repoable permissions count and set of repoable services for every role in the account.
    For each role:
      1) call _get_role_permissions
      2) call _get_repoable_permissions (count), repoable_permissions (count), and repoable_services (list) for role

    Each time we got the role permissions we built a list of any permissions that the role's policies granted access
    to but weren't in our master list of permissions AWS has.  At the end of this run we'll warn about any of these.

    Args:
        roles (Roles): The set of all roles we're analyzing

    Returns:
        None
    """
    for role in roles:
        permissions = _get_role_permissions(role)
        role.total_permissions = len(permissions)

        # if we don't have any access advisor data for a service than nothing is repoable
        if not role.aa_data:
            LOGGER.info('No data found in access advisor for {}'.format(
                role.role_id))
            role.repoable_permissions = 0
            role.repoable_services = []
            continue

        # permissions are only repoable if the role isn't being disqualified by filter(s)
        if len(role.disqualified_by) == 0:
            repoable_permissions = _get_repoable_permissions(
                permissions, role.aa_data, role.no_repo_permissions)
            repoable_services = set([
                permission.split(':')[0] for permission in repoable_permissions
            ])
            repoable_services = sorted(repoable_services)
            role.repoable_permissions = len(repoable_permissions)
            role.repoable_services = repoable_services
        else:
            role.repoable_permissions = 0
            role.repoable_services = []

    if WEIRD:
        all_services = set(
            [permission.split(':')[0] for permission in all_permissions])
        # print('Not sure about these permissions:\n{}'.format(json.dumps(list(WEIRD), indent=2, sort_keys=True)))
        weird_services = set(
            [permission.split(':')[0] for permission in WEIRD])
        weird_services = weird_services.difference(all_services)
        LOGGER.warn('Not sure about these services:\n{}'.format(
            json.dumps(list(weird_services), indent=2, sort_keys=True)))
Esempio n. 24
0
def get_aardvark_data(aardvark_api_location, account_number=None, arn=None):
    """
    Make a request to the Aardvark server to get all data about a given account or ARN.
    We'll request in groups of PAGE_SIZE and check the current count to see if we're done. Keep requesting as long as
    the total count (reported by the API) is greater than the number of pages we've received times the page size.  As
    we go, keeping building the dict and return it when done.

    Args:
        aardvark_api_location
        account_number (string): Used to form the phrase query for Aardvark so we only get data for the account we want
        arn (string)

    Returns:
        dict: Aardvark data is a dict with the role ARN as the key and a list of services as value
    """
    response_data = {}

    PAGE_SIZE = 1000
    page_num = 1

    if account_number:
        payload = {"phrase": "{}".format(account_number)}
    elif arn:
        payload = {"arn": [arn]}
    else:
        return
    while True:
        params = {"count": PAGE_SIZE, "page": page_num}
        try:
            r_aardvark = requests.post(
                aardvark_api_location, params=params, json=payload
            )
        except requests.exceptions.RequestException as e:
            LOGGER.error("Unable to get Aardvark data: {}".format(e))
            sys.exit(1)
        else:
            if r_aardvark.status_code != 200:
                LOGGER.error("Unable to get Aardvark data")
                sys.exit(1)

            response_data.update(r_aardvark.json())
            # don't want these in our Aardvark data
            response_data.pop("count")
            response_data.pop("page")
            response_data.pop("total")
            if PAGE_SIZE * page_num < r_aardvark.json().get("total"):
                page_num += 1
            else:
                break
    return response_data
Esempio n. 25
0
def find_roles_with_permission(permission, dynamo_table):
    """
    Search roles in all accounts for a policy with a given permission, log the ARN of each role with this permission

    Args:
        permission (string): The name of the permission to find

    Returns:
        None
    """
    for roleID in role_ids_for_all_accounts(dynamo_table):
        role = Role(get_role_data(dynamo_table, roleID, fields=['Policies', 'RoleName', 'Arn', 'Active']))
        permissions = roledata._get_role_permissions(role)
        if permission.lower() in permissions and role.active:
            LOGGER.info('ARN {arn} has {permission}'.format(arn=role.arn, permission=permission))
Esempio n. 26
0
 def load_plugin(self, module, config=None):
     """Import a module by path, instantiate it with plugin specific config and add to the list of active plugins"""
     cls = None
     try:
         cls = import_string(module)
     except ImportError as e:
         LOGGER.warn("Unable to find plugin {}, exception: {}".format(module, e))
     else:
         plugin = None
         try:
             plugin = cls(config=config)
         except KeyError:
             plugin = cls()
         LOGGER.info('Loaded plugin {}'.format(module))
         self.filter_plugins.append(plugin)
Esempio n. 27
0
def _calculate_repo_scores(roles, minimum_age, hooks):
    """
    Get the total and repoable permissions count and set of repoable services for every role in the account.
    For each role:
      1) call _get_role_permissions
      2) call _get_repoable_permissions (count), repoable_permissions (count), and repoable_services (list) for role

    Each time we got the role permissions we built a list of any permissions that the role's policies granted access
    to but weren't in our master list of permissions AWS has.  At the end of this run we'll warn about any of these.

    Args:
        roles (Roles): The set of all roles we're analyzing
        minimum_age
        hooks

    Returns:
        None
    """
    for role in roles:
        permissions = _get_role_permissions(role)

        role.total_permissions = len(permissions)

        # if we don't have any access advisor data for a service than nothing is repoable
        if not role.aa_data:
            LOGGER.info('No data found in access advisor for {}'.format(
                role.role_id))
            role.repoable_permissions = 0
            role.repoable_services = []
            continue

        # permissions are only repoable if the role isn't being disqualified by filter(s)
        if len(role.disqualified_by) == 0:
            repoable_permissions = _get_repoable_permissions(
                role.account, role.role_name, permissions, role.aa_data,
                role.no_repo_permissions, minimum_age, hooks)
            (repoable_permissions_set, repoable_services_set
             ) = _convert_repoable_perms_to_perms_and_services(
                 permissions, repoable_permissions)

            role.repoable_permissions = len(repoable_permissions)

            # we're going to store both repoable permissions and repoable services in the field "RepoableServices"
            role.repoable_services = repoable_services_set + repoable_permissions_set
        else:
            role.repoable_permissions = 0
            role.repoable_services = []
Esempio n. 28
0
    def apply(self, input_list):
        now = datetime.datetime.now(tzlocal())
        try:
            days_delta = self.config['minimum_age']
        except KeyError:
            LOGGER.info('Minimum age not set in config, using default 90 days')
            days_delta = 90

        ago = datetime.timedelta(days=days_delta)

        too_young = []
        for role in input_list:
            if role.create_date > now - ago:
                LOGGER.info('Role {name} created too recently to cleanup. ({date})'.format(
                            name=role.role_name, date=role.create_date))
                too_young.append(role)
        return too_young
Esempio n. 29
0
def log_during_repoable_calculation_batch_hooks(input_dict):
    LOGGER.debug("Calling DURING_REPOABLE_CALCULATION_BATCH hooks")

    if not all(required in input_dict for required in [
            "role_batch",
            "potentially_repoable_permissions",
            "minimum_age",
    ]):
        raise hooks.MissingHookParamaeter(
            "Did not get all required parameters for DURING_REPOABLE_CALCULATION_BATCH hook"
        )
    for role in input_dict["role_batch"]:
        if not isinstance(role, Role):
            raise hooks.MissingHookParamaeter(
                "Role_batch needs to be a series of Role objects in DURING_REPOABLE_CALCULATION_BATCH hook"
            )
    return input_dict
Esempio n. 30
0
def _update_repoed_description(role_name, client=None):
    description = None
    try:
        description = client.get_role(RoleName=role_name)['Role'].get('Description', '')
    except KeyError:
        return
    date_string = datetime.datetime.utcnow().strftime('%m/%d/%y')
    if '; Repokid repoed' in description:
        new_description = re.sub(r'; Repokid repoed [0-9]{2}\/[0-9]{2}\/[0-9]{2}', '; Repokid repoed {}'.format(
                                 date_string), description)
    else:
        new_description = description + ' ; Repokid repoed {}'.format(date_string)
    # IAM role descriptions have a max length of 1000, if our new length would be longer, skip this
    if len(new_description) < 1000:
        client.update_role_description(RoleName=role_name, Description=new_description)
    else:
        LOGGER.error('Unable to set repo description ({}) for role {}, length would be too long'.format(
            new_description, role_name))