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
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
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
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))
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']))
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
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))
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
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
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
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']
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))
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, ))
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
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)
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
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
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))
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})
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))
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
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
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)))
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
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))
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)
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 = []
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
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
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))