def __init__(self, policy_details, exclusions=DEFAULT_EXCLUSIONS): self.policy_details = [] if not isinstance(exclusions, Exclusions): raise Exception( "The exclusions provided is not an Exclusions type object. " "Please supply an Exclusions object and try again." ) self.exclusions = exclusions for policy_detail in policy_details: this_policy_name = policy_detail.get("PolicyName") this_policy_id = policy_detail.get("PolicyId") this_policy_path = policy_detail.get("Path") # Always exclude the AWS service role policies if ( is_name_excluded(this_policy_path, "aws-service-role*") or is_name_excluded(this_policy_path, "/aws-service-role*") ): logger.debug("The %s Policy with the policy ID %s is excluded because it is " "an immutable AWS Service role with a path of %s", this_policy_name, this_policy_id, this_policy_path) continue # Exclude the managed policies if ( exclusions.is_policy_excluded(this_policy_name) or exclusions.is_policy_excluded(this_policy_id) or exclusions.is_policy_excluded(this_policy_path) ): logger.debug("The %s Managed Policy with the policy ID %s and %s path is excluded.", this_policy_name, this_policy_id, this_policy_path) continue self.policy_details.append(ManagedPolicy(policy_detail, exclusions))
def __init__( self, policy_details: List[Dict[str, Any]], exclusions: Exclusions = DEFAULT_EXCLUSIONS, flag_conditional_statements: bool = False, flag_resource_arn_statements: bool = False, ) -> None: self.policy_details = [] if not isinstance(exclusions, Exclusions): raise Exception( "The exclusions provided is not an Exclusions type object. " "Please supply an Exclusions object and try again.") self.exclusions = exclusions self.flag_conditional_statements = flag_conditional_statements self.flag_resource_arn_statements = flag_resource_arn_statements for policy_detail in policy_details: this_policy_name = policy_detail["PolicyName"] this_policy_id = policy_detail["PolicyId"] this_policy_path = policy_detail["Path"] # Always exclude the AWS service role policies if is_name_excluded(this_policy_path, "aws-service-role*") or is_name_excluded( this_policy_path, "/aws-service-role*"): logger.debug( "The %s Policy with the policy ID %s is excluded because it is " "an immutable AWS Service role with a path of %s", this_policy_name, this_policy_id, this_policy_path, ) continue # Exclude the managed policies if (exclusions.is_policy_excluded(this_policy_name) or exclusions.is_policy_excluded(this_policy_id) or exclusions.is_policy_excluded(this_policy_path)): logger.debug( "The %s Managed Policy with the policy ID %s and %s path is excluded.", this_policy_name, this_policy_id, this_policy_path, ) continue self.policy_details.append( ManagedPolicy(policy_detail, exclusions=exclusions, flag_resource_arn_statements=self. flag_resource_arn_statements, flag_conditional_statements=self. flag_conditional_statements))
def _is_excluded(self, exclusions): """Determine whether the principal name or principal ID is excluded""" return bool( exclusions.is_principal_excluded(self.role_name, "Role") or exclusions.is_principal_excluded(self.role_id, "Role") or exclusions.is_principal_excluded(self.path, "Role") or is_name_excluded(self.path, "/aws-service-role*"))
def _is_excluded(self, exclusions): """Determine whether the policy name or policy ID is excluded""" return bool( exclusions.is_policy_excluded(self.policy_name) or exclusions.is_policy_excluded(self.policy_id) or exclusions.is_policy_excluded(self.path) or is_name_excluded(self.path, "/aws-service-role*"))
def __init__( self, role_details: List[Dict[str, Any]], policy_details: ManagedPolicyDetails, exclusions: Exclusions = DEFAULT_EXCLUSIONS, ) -> None: self.roles = [] if not isinstance(exclusions, Exclusions): raise Exception( "For exclusions, please provide an object of the Exclusions type" ) self.exclusions = exclusions for role_detail in role_details: this_role_name = role_detail.get("RoleName") this_role_path = role_detail["Path"] if is_name_excluded(this_role_path, "/aws-service-role*"): logger.debug( "%s role is excluded because it is an immutable AWS Service role with a path of %s", this_role_name, this_role_path, ) else: self.roles.append( RoleDetail(role_detail, policy_details, exclusions))
def is_principal_excluded(self, exclusions=DEFAULT_EXCLUSIONS): """According to the exclusions configuration, determine whether or not to skip the Principal according to their name.""" decision = False name = get_resource_path_from_arn(self.arn) if self.principal_type == "User": if is_name_excluded(name, exclusions.users): print(f"\tExcluded user: {name}") decision = True if self.principal_type == "Group": if is_name_excluded(name, exclusions.groups): print(f"\tExcluded group: {name}") decision = True if self.principal_type == "Role": if is_name_excluded(name, exclusions.roles): print(f"\tExcluded role: {name}") decision = True return decision
def is_principal_excluded(self, exclusions_cfg=DEFAULT_EXCLUSIONS_CONFIG): """According to the exclusions configuration, determine whether or not to skip the Principal according to their name.""" decision = False if self.principal_type == "User": if is_name_excluded( get_resource_path_from_arn(self.arn), exclusions_cfg.get("users") ): decision = True if self.principal_type == "Group": if is_name_excluded( get_resource_path_from_arn(self.arn), exclusions_cfg.get("groups") ): decision = True if self.principal_type == "Role": if is_name_excluded( get_resource_path_from_arn(self.arn), exclusions_cfg.get("roles") ): decision = True return decision
def _actions(self, actions): results = [] if self.always_exclude_actions: for action in actions: if is_name_excluded(action.lower(), self.always_exclude_actions): # logger.info("Excluded action: %s" % action) pass else: results.append(action) return results else: return actions
def _actions(self, actions): results = [] if self.always_exclude_actions: for action in actions: if is_name_excluded(action.lower(), self.always_exclude_actions): pass # pragma: no cover else: results.append(action) return results else: return actions
def scan_policy_details(self, exclusions_cfg=DEFAULT_EXCLUSIONS_CONFIG, modify_only=True): """Scan the PolicyDetails block of the account authorization details output.""" excluded_actions = exclusions_cfg.get("exclude-actions", None) for policy in self.policies.policy_details: print(f"Scanning policy: {policy.policy_name}") always_include_actions = exclusions_cfg.get("include-actions") actions_missing_resource_constraints = [] if is_name_excluded(policy.policy_name, exclusions_cfg.get("policies")): print(f"\tExcluded policy name: {policy.policy_name}") elif is_name_excluded(policy.full_policy_path, exclusions_cfg.get("policies")): print(f"\tExcluded policy path: {policy.full_policy_path}") else: for statement in policy.policy_document.statements: if modify_only: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement. missing_resource_constraints_for_modify_actions( always_include_actions)) else: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement.missing_resource_constraints) if actions_missing_resource_constraints: actions_missing_resource_constraints = list( dict.fromkeys(actions_missing_resource_constraints) ) # remove duplicates actions_missing_resource_constraints.sort() finding = Finding( policy_name=policy.policy_name, arn=policy.arn, actions=actions_missing_resource_constraints, policy_document=policy.policy_document, always_exclude_actions=excluded_actions) self.findings.add(finding)
def scan_policy(policy_json, policy_name, exclusions_cfg=DEFAULT_EXCLUSIONS_CONFIG): """ Scan a policy document for missing resource constraints. :param policy_json: The AWS IAM policy document. :param exclusions_cfg: Defaults to the embedded exclusions file, which has no effect here. :param policy_name: The name of the IAM policy. Defaults to the filename when used from command line. :return: """ policy_document = PolicyDocument(policy_json) actions_missing_resource_constraints = [] # EXCLUDED ACTIONS - actions to exclude if they are false positives excluded_actions = exclusions_cfg.get("exclude-actions", None) if excluded_actions == [""]: excluded_actions = None # convert to lowercase for comparison purposes # some weird if/else logic to reduce loops and improve performance slightly if excluded_actions: excluded_actions = [x.lower() for x in excluded_actions] always_include_actions = exclusions_cfg.get("include-actions") findings = Findings() for statement in policy_document.statements: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement.missing_resource_constraints_for_modify_actions( always_include_actions ) ) if actions_missing_resource_constraints: results_placeholder = [] for action in actions_missing_resource_constraints: if excluded_actions: if not is_name_excluded(action.lower(), excluded_actions): results_placeholder.append(action) # pragma: no cover else: results_placeholder.append(action) actions_missing_resource_constraints = list( dict.fromkeys(results_placeholder) ) # remove duplicates actions_missing_resource_constraints.sort() finding = Finding( policy_name=policy_name, arn=policy_name, actions=actions_missing_resource_constraints, policy_document=policy_document, ) findings.add(finding) return findings.json
def permissions_management_actions_without_constraints(self): """Return a list of actions that could cause resource exposure via actions at the 'Permissions management' access level, if applicable.""" results = [] if self.always_exclude_actions: for action in self.policy_document.permissions_management_without_constraints: if is_name_excluded(action.lower(), self.always_exclude_actions): pass else: results.append(action) return results else: return self.policy_document.permissions_management_without_constraints
def scan_principal_type_details( self, principal_type_detail_list, exclusions_cfg=DEFAULT_EXCLUSIONS_CONFIG, modify_only=True, ): """Scan the UserDetailList, GroupDetailList, or RoleDetailList blocks of the account authorization details output.""" excluded_actions = exclusions_cfg.get("exclude-actions", None) for principal in principal_type_detail_list.principals: always_include_actions = exclusions_cfg.get("include-actions") print(f"Scanning {principal.principal_type}: {principal.name}") for policy in principal.policy_list: print(f"\tScanning Policy: {policy['PolicyName']}") if is_name_excluded(policy["PolicyName"], exclusions_cfg.get("policies")): print(f"\tExcluded policy name: {policy['PolicyName']}") elif principal.is_principal_excluded(exclusions_cfg): print(f"\tExcluded principal name: {principal.name}") else: policy_document = policy["PolicyDocument"] actions_missing_resource_constraints = [] for statement in policy_document.statements: if modify_only: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement. missing_resource_constraints_for_modify_actions( always_include_actions)) else: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement.missing_resource_constraints) if actions_missing_resource_constraints: finding = Finding( policy_name=policy["PolicyName"], arn=principal.arn, actions=actions_missing_resource_constraints, policy_document=policy["PolicyDocument"], assume_role_policy_document=principal. assume_role_policy_document, always_exclude_actions=excluded_actions) self.findings.add(finding)
def __init__( self, role_details: List[Dict[str, Any]], policy_details: ManagedPolicyDetails, exclusions: Exclusions = DEFAULT_EXCLUSIONS, flag_conditional_statements: bool = False, flag_resource_arn_statements: bool = False, ) -> None: self.roles = [] if not isinstance(exclusions, Exclusions): raise Exception( "For exclusions, please provide an object of the Exclusions type" ) self.exclusions = exclusions # Fix Issue #254 - Allow flagging risky actions even when there are resource constraints self.flag_conditional_statements = flag_conditional_statements self.flag_resource_arn_statements = flag_resource_arn_statements for role_detail in role_details: this_role_name = role_detail.get("RoleName") this_role_path = role_detail["Path"] if is_name_excluded(this_role_path, "/aws-service-role*"): logger.debug( "%s role is excluded because it is an immutable AWS Service role with a path of %s", this_role_name, this_role_path, ) else: self.roles.append( RoleDetail(role_detail, policy_details, exclusions=exclusions, flag_conditional_statements=self. flag_conditional_statements, flag_resource_arn_statements=self. flag_resource_arn_statements))
def test_exclusions_suffix_match(self): """test_exclusions_suffix_match: Test exclusions function with suffix wildcard logic.""" exclusions_list = ["*ish"] policy_name = "Secure-ish" result = is_name_excluded(policy_name, exclusions_list) self.assertTrue(result)
def test_exclusions_prefix_match(self): """test_exclusions_prefix_match: Test exclusions function with prefix wildcard logic.""" exclusions_list = ["ThePerfectManDoesntExi*"] policy_name = "ThePerfectManDoesntExist" result = is_name_excluded(policy_name, exclusions_list) self.assertTrue(result)
def test_exclusions_exact_match(self): """test_exclusions_exact_match: If there is an exact match in the exclusions list""" exclusions_list = ["Beyonce"] policy_name = "Beyonce" result = is_name_excluded(policy_name, exclusions_list) self.assertTrue(result)