def get_repoable_permissions( account_number: str, role_name: str, permissions: Set[str], aa_data: List[Dict[str, Any]], no_repo_permissions: Dict[str, Any], role_id: str, minimum_age: int, hooks: RepokidHooks, ) -> Set[str]: """ Generate a list of repoable permissions for a role based on the list of all permissions the role's policies currently allow and Access Advisor data for the services included in the role's policies. The first step is to come up with a list of services that were used within the time threshold (the same defined) in the age filter config. Permissions are repoable if they aren't in the used list, aren't in the constant list of unsupported services/actions (IAM_ACCESS_ADVISOR_UNSUPPORTED_SERVICES, IAM_ACCESS_ADVISOR_UNSUPPORTED_ACTIONS), and aren't being temporarily ignored because they're on the no_repo_permissions list (newly added). Args: account_number role_name permissions (set): The full set of permissions that the role's permissions allow aa_data (list): A list of Access Advisor data for a role. Each element is a dictionary with a couple required attributes: lastAuthenticated (epoch time in milliseconds when the service was last used and serviceNamespace (the service used) no_repo_permissions (dict): Keys are the name of permissions and values are the time the entry expires minimum_age: Minimum age of a role (in days) for it to be repoable hooks: Dict containing hook names and functions to run Returns: set: Permissions that are 'repoable' (not used within the time threshold) """ potentially_repoable_permissions = _get_potentially_repoable_permissions( role_name, account_number, aa_data, permissions, no_repo_permissions, minimum_age, ) hooks_output = call_hooks( hooks, "DURING_REPOABLE_CALCULATION", { "account_number": account_number, "role_name": role_name, "potentially_repoable_permissions": potentially_repoable_permissions, "minimum_age": minimum_age, "role_id": role_id, }, ) logger.debug( "Repoable permissions for role {role_name} in {account_number}:\n{repoable}" .format( role_name=role_name, account_number=account_number, repoable="".join( "{}: {}\n".format(perm, decision.decider) for perm, decision in list( hooks_output["potentially_repoable_permissions"].items())), )) return { permission_name for permission_name, permission_value in list( hooks_output["potentially_repoable_permissions"].items()) if permission_value.repoable }
def repo(self, hooks: RepokidHooks, commit: bool = False, scheduled: bool = False) -> List[str]: errors: List[str] = [] eligible, reason = self.is_eligible_for_repo() if not eligible: errors.append( f"Role {self.role_name} not eligible for repo: {reason}") return errors self.calculate_repo_scores( self.config["filter_config"]["AgeFilter"]["minimum_age"], hooks # type: ignore ) try: repoed_policies, deleted_policy_names = self.get_repoed_policy( scheduled=scheduled) except MissingRepoableServices as e: errors.append(f"Role {self.role_name} cannot be repoed: {e}") return errors if inline_policies_size_exceeds_maximum(repoed_policies): error = ( "Policies would exceed the AWS size limit after repo for role: {} in account {}. " "Please manually minify.".format(self.role_name, self.account)) logger.error(error) errors.append(error) self.repo_scheduled = 0 self.scheduled_perms = [] self.store(["repo_scheduled", "scheduled_perms"]) return errors if not commit: log_deleted_and_repoed_policies(deleted_policy_names, repoed_policies, self.role_name, self.account) return errors conn = self.config["connection_iam"] # type: ignore conn["account_number"] = self.account for name in deleted_policy_names: try: delete_policy(name, self.role_name, self.account, conn) except IAMError as e: logger.error(e) errors.append(str(e)) if repoed_policies: try: replace_policies(repoed_policies, self.role_name, self.account, conn) except IAMError as e: logger.error(e) errors.append(str(e)) current_policies = (get_role_inline_policies(self.dict(by_alias=True), **conn) or {}) self.add_policy_version(current_policies, source="Repo") # regardless of whether we're successful we want to unschedule the repo self.repo_scheduled = 0 self.scheduled_perms = [] call_hooks(hooks, "AFTER_REPO", {"role": self, "errors": errors}) if not errors: # repos will stay scheduled until they are successful self.repoed = datetime.datetime.now( tz=datetime.timezone.utc).isoformat() update_repoed_description(self.role_name, conn) logger.info("Successfully repoed role: {} in account {}".format( self.role_name, self.account)) try: self.store() except RoleStoreError: logger.exception("failed to store role after repo", exc_info=True) return errors