def find_and_mark_inactive(account_number: str, active_roles: RoleList) -> None: """ 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 """ # TODO: clean up and simplify this logic. We're getting a RoleList, converting to a set, # and subtracting it from a set of known roles. This is strange and confusing. active_roles_set = set(active_roles) known_roles = set(get_all_role_ids_for_account(account_number)) inactive_role_ids = known_roles - active_roles_set inactive_roles = RoleList.from_ids(inactive_role_ids, fields=["Active", "Arn"]) for role in inactive_roles: if role.active: role.mark_inactive()
def _cancel_scheduled_repo(account_number: str, role_name: str = "", is_all: bool = False) -> 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: role_ids = get_all_role_ids_for_account(account_number) roles = RoleList.from_ids(role_ids) # filter to show only roles that are scheduled roles = roles.get_scheduled() for role in roles: role.repo_scheduled = 0 role.scheduled_perms = [] try: role.store(["repo_scheduled", "scheduled_perms"]) except RoleStoreError: LOGGER.exception("failed to store role", exc_info=True) LOGGER.info("Canceled scheduled repo for roles: {}".format(", ".join( [role.role_name for role in roles]))) return role_id = find_role_in_cache(role_name, account_number) if not role_id: LOGGER.warning( f"Could not find role with name {role_name} in account {account_number}" ) return role = Role(role_id=role_id) role.fetch() if not role.repo_scheduled: LOGGER.warning( "Repo was not scheduled for role {} in account {}".format( role.role_name, account_number)) return role.repo_scheduled = 0 role.scheduled_perms = [] try: role.store(["repo_scheduled", "scheduled_perms"]) except RoleStoreError: LOGGER.exception("failed to store role", exc_info=True) raise LOGGER.info( "Successfully cancelled scheduled repo for role {} in account {}". format(role.role_name, role.account))
def _schedule_repo( account_number: str, config: RepokidConfig, hooks: RepokidHooks, ) -> None: """ 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 = [] role_ids = get_all_role_ids_for_account(account_number) roles = RoleList.from_ids(role_ids) roles.fetch_all(fetch_aa_data=True) scheduled_time = int( time.time()) + (86400 * config.get("repo_schedule_period_days", 7)) for role in roles: if not role.aa_data: LOGGER.warning("Not scheduling %s; missing Access Advisor data", role.arn) continue if not role.repoable_permissions > 0: LOGGER.debug("Not scheduling %s; no repoable permissions", role.arn) continue if role.repo_scheduled: LOGGER.debug( "Not scheduling %s; already scheduled for %s", role.arn, role.repo_scheduled, ) continue role.repo_scheduled = scheduled_time # freeze the scheduled perms to whatever is repoable right now role.repo_scheduled = scheduled_time role.scheduled_perms = role.repoable_services try: role.store(["repo_scheduled", "scheduled_perms"]) except RoleStoreError: logging.exception("failed to store role", exc_info=True) 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 _repo_stats(output_file: str, account_number: str = "") -> 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 """ role_ids = (get_all_role_ids_for_account(account_number) if account_number else role_ids_for_all_accounts()) headers = [ "RoleId", "Role Name", "Account", "Active", "Date", "Source", "Permissions Count", "Repoable Permissions Count", "Disqualified By", ] rows = [] roles = RoleList.from_ids( role_ids, fields=["RoleId", "RoleName", "Account", "Active", "Stats"]) for role in roles: for stats_entry in role.stats: rows.append([ role.role_id, role.role_name, role.account, role.active, stats_entry["Date"], stats_entry["Source"], stats_entry["PermissionsCount"], stats_entry.get("RepoablePermissionsCount", 0), stats_entry.get("DisqualifiedBy", []), ]) try: with open(output_file, "w") 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), exc_info=True) else: LOGGER.info("Successfully wrote stats to {}".format(output_file))
def _display_roles(account_number: str, inactive: bool = False) -> None: """ Display a table with data about all roles in an account and write a csv file with the data. Args: account_number (string) inactive (bool): show roles that have historically (but not currently) existed in the account if True Returns: None """ headers = [ "Name", "Refreshed", "Disqualified By", "Can be repoed", "Permissions", "Repoable", "Repoed", "Services", ] rows: List[List[Any]] = [] role_ids = get_all_role_ids_for_account(account_number) roles = RoleList.from_ids(role_ids) if not inactive: roles = roles.get_active() for role in roles: rows.append( [ role.role_name, role.refreshed, role.disqualified_by, len(role.disqualified_by) == 0, role.total_permissions, role.repoable_permissions, role.repoed, role.repoable_services, ] ) rows = sorted(rows, key=lambda x: (x[5], x[0], x[4])) rows.insert(0, headers) # print tabulate(rows, headers=headers) t.view(rows) with open("table.csv", "w") as csvfile: csv_writer = csv.writer(csvfile) csv_writer.writerow(headers) for row in rows: csv_writer.writerow(row)
def find_and_mark_inactive(account_number: str, active_roles: RoleList) -> None: """ 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 """ known_roles = set(get_all_role_ids_for_account(account_number)) inactive_roles = { role for role in active_roles if role.role_id not in known_roles } for role in inactive_roles: if role.active: role.mark_inactive()
def _show_scheduled_roles(account_number: str) -> None: """ Show scheduled repos for a given account. For each scheduled show whether scheduled time is elapsed or not. """ role_ids = get_all_role_ids_for_account(account_number) roles = RoleList.from_ids(role_ids) # filter to show only roles that are scheduled roles = roles.get_active().get_scheduled() header = ["Role name", "Scheduled", "Scheduled Time Elapsed?"] rows = [] curtime = int(time.time()) for role in roles: rows.append([ role.role_name, dt.fromtimestamp(role.repo_scheduled).strftime("%Y-%m-%d %H:%M"), role.repo_scheduled < curtime, ]) print(tabulate(rows, headers=header))
def _repo_all_roles( account_number: str, config: RepokidConfig, hooks: RepokidHooks, commit: bool = False, scheduled: bool = True, limit: int = -1, ) -> None: """ Repo all scheduled or eligible roles in an account. Collect any errors and display them at the end. Args: account_number (string) dynamo_table config commit (bool): actually make the changes scheduled (bool): if True only repo the scheduled roles, if False repo all the (eligible) roles limit (int): limit number of roles to be repoed per run (< 0 is unlimited) Returns: None """ errors = [] role_ids_in_account = get_all_role_ids_for_account(account_number) roles = RoleList.from_ids(role_ids_in_account) roles = roles.get_active() if scheduled: roles = roles.get_scheduled() LOGGER.info("Repoing these {}roles from account {}:\n\t{}".format( "scheduled " if scheduled else "", account_number, ", ".join([role.role_name for role in roles]), )) repokid.hooks.call_hooks(hooks, "BEFORE_REPO_ROLES", { "account_number": account_number, "roles": roles }) count = 0 repoed = RoleList([]) for role in roles: if limit >= 0 and count == limit: break error = _repo_role( account_number, role.role_name, config, hooks, commit=commit, scheduled=scheduled, ) if error: errors.append(error) repoed.append(role) count += 1 if errors: LOGGER.error( f"Error(s) during repo: {errors} (account: {account_number})") else: LOGGER.info( f"Successfully repoed {count} roles in account {account_number}") repokid.hooks.call_hooks( hooks, "AFTER_REPO_ROLES", { "account_number": account_number, "roles": repoed, "errors": errors }, )