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.warning( "Unknown permissions found: {}".format(weird_permissions)) return total_permissions, eligible_permissions
def test_get_actions_from_statement(self): statement = { "Action": "ec2:thispermissiondoesntexist", "NotAction": list(all_permissions), "Resource": "*", "Effect": "Allow" } expected_result = {"ec2:thispermissiondoesntexist"} result = get_actions_from_statement(statement) self.assertEqual(result, expected_result) get_actions_from_statement(dict(NotAction="abc"))
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 _get_role_permissions(role): """ 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 add them to our global list of WEIRD permissions to warn about later. 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)) global WEIRD weird_permissions = permissions.difference(all_permissions) if weird_permissions: WEIRD = WEIRD.union(weird_permissions) return permissions
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 _get_repoed_policy(policies, repoable_permissions): """ This function contains the logic to rewrite the policy to remove any repoable permissions. To do so we: - Iterate over role policies - Iterate over policy statements - Skip Deny statements - Remove any actions that are in repoable_permissions - Remove any statements that now have zero actions - Remove any policies that now have zero statements Args: policies (dict): All of the inline policies as a dict with name and policy contents repoable_permissions (set): A set of all of the repoable permissions for policies Returns: dict: The rewritten set of all inline policies list: Any policies that are now empty as a result of the rewrites """ # work with our own copy; don't mess with the CACHE copy. role_policies = copy.deepcopy(policies) empty_policies = [] for policy_name, policy in role_policies.items(): # list of indexes in the policy that are empty empty_statements = [] if type(policy['Statement']) is dict: policy['Statement'] = [policy['Statement']] for idx, statement in enumerate(policy['Statement']): if statement['Effect'].lower() == 'allow': statement_actions = get_actions_from_statement(statement) statement_actions = statement_actions.difference( repoable_permissions) # get_actions_from_statement has already inverted this so our new statement should be 'Action' if 'NotAction' in statement: del statement['NotAction'] # by putting this into a set, we lose order, which may be confusing to someone. statement['Action'] = sorted(list(statement_actions)) # mark empty statements to be removed if len(statement['Action']) == 0: empty_statements.append(idx) # do the actual removal of empty statements for idx in sorted(empty_statements, reverse=True): del policy['Statement'][idx] # mark empty policies to be removed if len(policy['Statement']) == 0: empty_policies.append(policy_name) # do the actual removal of empty policies. for policy_name in empty_policies: del role_policies[policy_name] return role_policies, empty_policies
def _get_repoed_policy(role, repoable_permissions): """ Iterate over role policies. Iterate over policy statements. Skip Deny statements. Remove any actions that are in repoable_permissions. Remove any statements that now have zero actions. Remove any policies that now have zero statements. return """ # work with our own copy; don't mess with the CACHE copy. role_policies = dict(role['Policies'][-1]['Policy']) empty_policies = [] for policy_name, policy in role_policies.items(): empty_statements = [] if type(policy['Statement']) is dict: policy['Statement'] = [policy['Statement']] for idx, statement in enumerate(policy['Statement']): if statement['Effect'].lower() == 'allow': statement_actions = get_actions_from_statement(statement) statement_actions = statement_actions.difference( repoable_permissions) if 'NotAction' in statement: del statement['NotAction'] # by putting this into a set, we lose order, which may be confusing to someone. statement['Action'] = sorted(list(statement_actions)) # mark empty statements to be removed if len(statement['Action']) == 0: empty_statements.append(idx) # do the actual removal of empty statements for idx in sorted(empty_statements, reverse=True): del policy['Statement'][idx] # mark empty policies to be removed if len(policy['Statement']) == 0: empty_policies.append(policy_name) # do the actual removal of empty policies. for policy_name in empty_policies: del role_policies[policy_name] return role_policies, empty_policies
def _get_role_permissions(role): permissions = set() for policy_name, policy in _get_current_policies(role).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)) for permission in permissions: if permission.startswith('tag:'): LOGGER.info('Role {} has {}'.format(role['RoleName'], permission)) global WEIRD weird_permissions = permissions.difference(all_permissions) if weird_permissions: WEIRD = WEIRD.union(weird_permissions) return permissions
def get_repoed_policy( policies: Dict[str, Any], repoable_permissions: Set[str]) -> Tuple[Dict[str, Any], List[str]]: """ This function contains the logic to rewrite the policy to remove any repoable permissions. To do so we: - Iterate over role policies - Iterate over policy statements - Skip Deny statements - Remove any actions that are in repoable_permissions - Remove any statements that now have zero actions - Remove any policies that now have zero statements Args: policies (dict): All of the inline policies as a dict with name and policy contents repoable_permissions (set): A set of all of the repoable permissions for policies Returns: dict: The rewritten set of all inline policies list: Any policies that are now empty as a result of the rewrites """ # work with our own copy; don't mess with the CACHE copy. role_policies = copy.deepcopy(policies) empty_policies = [] for policy_name, policy in list(role_policies.items()): # list of indexes in the policy that are empty empty_statements = [] if type(policy["Statement"]) is dict: policy["Statement"] = [policy["Statement"]] for idx, statement in enumerate(policy["Statement"]): if statement["Effect"].lower() == "allow": if "Sid" in statement and statement["Sid"].startswith( STATEMENT_SKIP_SID): continue statement_actions = get_actions_from_statement(statement) new_actions = { action for action in statement_actions if action not in repoable_permissions and action.split(":")[0] not in repoable_permissions } if statement_actions == new_actions: # No permissions are being taken away; let's not modify this statement at all. continue # get_actions_from_statement has already inverted this so our new statement should be 'Action' if "NotAction" in statement: del statement["NotAction"] # by putting this into a set, we lose order, which may be confusing to someone. statement["Action"] = sorted(list(new_actions)) # mark empty statements to be removed if len(statement["Action"]) == 0: empty_statements.append(idx) # do the actual removal of empty statements for idx in sorted(empty_statements, reverse=True): del policy["Statement"][idx] # mark empty policies to be removed if len(policy["Statement"]) == 0: empty_policies.append(policy_name) # do the actual removal of empty policies. for policy_name in empty_policies: del role_policies[policy_name] return role_policies, empty_policies