def test_findings_add_list(self): """output.findings.findings.add: Make sure the add function works with adding it as a list""" test_policy = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] } policy_document = PolicyDocument(test_policy) finding_1 = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SNSNotifications", actions=["s3:GetObject"], policy_document=policy_document) finding_2 = Finding(policy_name="MyPolicy", arn="arn:aws:iam::aws:policy/BadAWSManagedPolicy", actions=["s3:GetObject"], policy_document=policy_document) findings = Findings() findings.add([finding_1, finding_2]) result = findings.json self.assertTrue(len(result) == 2)
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
class AuthorizationDetails: """ Represents the entire JSON file generated by the aws iam get-account-authorization-details command. """ def __init__(self, auth_json): self.auth_json = auth_json self.policies = PolicyDetails(auth_json.get("Policies", None)) self.user_detail_list = PrincipalTypeDetails( auth_json.get("UserDetailList", None)) self.group_detail_list = PrincipalTypeDetails( auth_json.get("GroupDetailList", None)) self.role_detail_list = PrincipalTypeDetails( auth_json.get("RoleDetailList", None)) self.findings = Findings() self.customer_managed_policies_in_use = self._customer_managed_policies_in_use( ) self.aws_managed_policies_in_use = self._aws_managed_policies_in_use() def _aws_managed_policies_in_use(self): aws_managed_policies = [] for policy in self.policies.policy_details: if "arn:aws:iam::aws:" in policy.arn: aws_managed_policies.append(policy.policy_name) # Policies attached to groups for principal in self.group_detail_list.principals: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" in attached_managed_policy.get( "PolicyArn"): aws_managed_policies.append( attached_managed_policy.get("PolicyName")) # Policies attached to users for principal in self.user_detail_list.principals: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" in attached_managed_policy.get( "PolicyArn"): aws_managed_policies.append( attached_managed_policy.get("PolicyName")) # Policies attached to roles for principal in self.role_detail_list.principals: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" in attached_managed_policy.get( "PolicyArn"): aws_managed_policies.append( attached_managed_policy.get("PolicyName")) return aws_managed_policies def _customer_managed_policies_in_use(self): customer_managed_policies = [] for policy in self.policies.policy_details: if "arn:aws:iam::aws:" not in policy.arn: customer_managed_policies.append(policy.policy_name) # Policies attached to groups for principal in self.group_detail_list.principals: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" not in attached_managed_policy.get( "PolicyArn"): customer_managed_policies.append( attached_managed_policy.get("PolicyName")) # Policies attached to users for principal in self.user_detail_list.principals: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" not in attached_managed_policy.get( "PolicyArn"): customer_managed_policies.append( attached_managed_policy.get("PolicyName")) # Policies attached to roles for principal in self.role_detail_list.principals: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" not in attached_managed_policy.get( "PolicyArn"): customer_managed_policies.append( attached_managed_policy.get("PolicyName")) return customer_managed_policies @property def groups(self): """A list of the group names in the account, according to the account authorization details.""" group_names = [] for principal in self.group_detail_list.principals: group_names.append(principal.name) return group_names @property def roles(self): """A list of the role names in the account, according to the account authorization details.""" role_names = [] for principal in self.role_detail_list.principals: role_names.append(principal.name) return role_names @property def users(self): """A list of the user names in the account, according to the account authorization details.""" user_names = [] for principal in self.user_detail_list.principals: user_names.append(principal.name) return user_names @property def principals(self): """Get a list of PrincipalDetail objects for all principals - Users, Groups, or Roles - in the account.""" all_principals = [] # Users for principal in self.user_detail_list.principals: all_principals.append(principal) # Groups for principal in self.group_detail_list.principals: all_principals.append(principal) # Roles for principal in self.role_detail_list.principals: all_principals.append(principal) return all_principals @property def principal_policy_mapping(self): """ Returns a mapping of principals vs the policies that they are attached to - either inline or managed. """ principal_policy_mapping = [] for principal in self.principals: # Inline Policies if principal.inline_principal_policies: for inline_policy in principal.inline_principal_policies: entry = dict(Principal=principal.name, Type=principal.principal_type, PolicyType="Inline", ManagedBy="Customer", PolicyName=inline_policy.get("PolicyName"), Comment=None) principal_policy_mapping.append(entry) # AttachedManagedPolicies if principal.attached_managed_policies: for attached_managed_policy in principal.attached_managed_policies: if "arn:aws:iam::aws:" in attached_managed_policy.get( "PolicyArn"): managed_by = "AWS" else: managed_by = "Customer" entry = dict( Principal=principal.name, Type=principal.principal_type, PolicyType="Managed", ManagedBy=managed_by, PolicyName=attached_managed_policy.get("PolicyName"), Comment=None, ) principal_policy_mapping.append(entry) # While users might have inline policies or managed policies, # their findings will need to reflect their group membership findings as well. # So we need to run that loop again, finding the data for their groups and adding it. if principal.principal_type == "User": group_memberships = principal.group_member for group_membership in group_memberships: for some_principal in self.principals: if some_principal.principal_type == "Group" and some_principal.name == group_membership: if some_principal.inline_principal_policies: for inline_policy in some_principal.inline_principal_policies: entry = dict(Principal=principal.name, Type=principal.principal_type, PolicyType="Inline", ManagedBy="Customer", PolicyName=inline_policy.get( "PolicyName"), Comment="Group Membership") principal_policy_mapping.append(entry) # AttachedManagedPolicies if some_principal.attached_managed_policies: for attached_managed_policy in some_principal.attached_managed_policies: if "arn:aws:iam::aws:" in attached_managed_policy.get( "PolicyArn"): managed_by = "AWS" else: managed_by = "Customer" entry = dict( Principal=principal.name, Type=principal.principal_type, PolicyType="Managed", ManagedBy=managed_by, PolicyName=attached_managed_policy.get( "PolicyName"), Comment="Group Membership") principal_policy_mapping.append(entry) # Sort it principal_policy_mapping = sorted(principal_policy_mapping, key=itemgetter( "Type", "Principal", "PolicyType", "PolicyName")) return principal_policy_mapping def missing_resource_constraints(self, exclusions_cfg=DEFAULT_EXCLUSIONS_CONFIG, modify_only=True): """Scan the account authorization details for missing resource constraints.""" print("-----USERS-----") self.scan_principal_type_details(self.user_detail_list, exclusions_cfg, modify_only) print("-----GROUPS-----") self.scan_principal_type_details(self.group_detail_list, exclusions_cfg, modify_only) print("-----ROLES-----") self.scan_principal_type_details(self.role_detail_list, exclusions_cfg, modify_only) print("-----POLICIES-----") self.scan_policy_details(exclusions_cfg, modify_only) return self.findings.json 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_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)