def test_new_findings(self): """output.new_findings.Findings""" self.maxDiff = None test_policy = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] } policy_document = PolicyDocument(test_policy) # (1) If the user is a member of an excluded group, return True exclusions_cfg = dict(users=["obama"], groups=["exclude-group"], roles=["MyRole"], policies=["exclude-policy"]) exclusions = Exclusions(exclusions_cfg) attached_managed_policies = [{ "PolicyArn": "arn:aws:iam::aws:policy/AWSLambdaFullAccess", "PolicyName": "AWSLambdaFullAccess" }] # Let's just re-use the same policy for users groups and roles user_finding = UserFinding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:user/SomeUser", actions=["s3:GetObject"], policy_document=policy_document, group_membership=["admin"], attached_managed_policies=attached_managed_policies, exclusions=exclusions) group_finding = GroupFinding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SomeGroup", actions=["s3:GetObject"], policy_document=policy_document, members=["obama"], exclusions=exclusions) trust_policy_from_compute_service_ecs_tasks = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["sts:AssumeRole"], "Principal": { "Service": "ecs-tasks.amazonaws.com", "AWS": "arn:aws:iam::012345678910:root", } }] } assume_role_policy_document = AssumeRolePolicyDocument( trust_policy_from_compute_service_ecs_tasks) role_finding = RoleFinding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:role/SomeRole", actions=["s3:GetObject"], policy_document=policy_document, assume_role_policy_document=assume_role_policy_document, exclusions=exclusions) policy_finding = PolicyFinding( policy_name="AWSLambdaFullAccess", arn="arn:aws:iam::aws:policy/AWSLambdaFullAccess", actions=["s3:GetObject"], policy_document=policy_document, exclusions=exclusions) all_findings = Findings(exclusions) all_findings.add_user_finding(user_finding) result = all_findings.users[0] expected_user_result = { "AccountID": "123456789012", "ManagedBy": "Customer", "Name": "SomeUser", "PolicyName": "MyPolicy", "Type": "User", "Arn": "arn:aws:iam::123456789012:user/SomeUser", "ActionsCount": 1, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] }, "AssumeRolePolicyDocument": None, "AssumableByComputeService": [], "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [] } self.assertDictEqual(result.json, expected_user_result) principal_policy_mapping = [ { "Principal": "SomeUser", "Type": "User", "PolicyType": "Managed", "ManagedBy": "AWS", "PolicyName": "MyPolicy", "GroupMembership": None, }, { "Principal": "SomeUser", "Type": "User", "PolicyType": "Managed", "ManagedBy": "AWS", "PolicyName": "AWSLambdaFullAccess", "GroupMembership": None, }, { "Principal": "SomeGroup", "Type": "Group", "PolicyType": "Managed", "ManagedBy": "AWS", "PolicyName": "MyPolicy", "GroupMembership": None, }, { "Principal": "SomeRole", "Type": "Role", "PolicyType": "Managed", "ManagedBy": "AWS", "PolicyName": "MyPolicy", "GroupMembership": None, }, ] all_findings.principal_policy_mapping = principal_policy_mapping all_findings.add_group_finding(group_finding) all_findings.add_role_finding(role_finding) all_findings.add_policy_finding(policy_finding) print(len(all_findings)) self.assertEqual(len(all_findings), 4) results = all_findings.json expected_results = [{ "AccountID": "N/A", "ManagedBy": "AWS", "Name": "AWSLambdaFullAccess", "PolicyName": "AWSLambdaFullAccess", "Type": "Policy", "Arn": "arn:aws:iam::aws:policy/AWSLambdaFullAccess", "ActionsCount": 1, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] }, "AssumeRolePolicyDocument": None, "AssumableByComputeService": [], "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [] }, { "AccountID": "123456789012", "ManagedBy": "Customer", "Name": "SomeUser", "PolicyName": "MyPolicy", "Type": "User", "Arn": "arn:aws:iam::123456789012:user/SomeUser", "ActionsCount": 1, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] }, "AssumeRolePolicyDocument": None, "AssumableByComputeService": [], "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [] }, { "AccountID": "123456789012", "ManagedBy": "Customer", "Name": "SomeGroup", "PolicyName": "MyPolicy", "Type": "Group", "Arn": "arn:aws:iam::123456789012:group/SomeGroup", "ActionsCount": 1, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] }, "AssumeRolePolicyDocument": None, "AssumableByComputeService": [], "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [] }, { "AccountID": "123456789012", "ManagedBy": "Customer", "Name": "SomeRole", "PolicyName": "MyPolicy", "Type": "Role", "Arn": "arn:aws:iam::123456789012:role/SomeRole", "ActionsCount": 1, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] }, "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["sts:AssumeRole"], "Principal": { "Service": "ecs-tasks.amazonaws.com", "AWS": "arn:aws:iam::012345678910:root" } }] }, "AssumableByComputeService": ["ecs-tasks"], "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [] }] # print(json.dumps(all_findings.json, indent=4)) self.assertListEqual(results, expected_results)
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 = ManagedPolicyDetails(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._update_group_membership() 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() # New Authorization file stuff self.new_group_detail_list = GroupDetailList( auth_json.get("GroupDetailList"), self.policies) self.new_user_detail_list = UserDetailList( auth_json.get("UserDetailList"), self.policies, self.new_group_detail_list) self.new_role_detail_list = RoleDetailList( auth_json.get("RoleDetailList"), self.policies) @property def new_principal_policy_mapping(self): """Get the new JSON format of the Principals data""" results = { "groups": self.new_group_detail_list.json, "users": self.new_user_detail_list.json, "roles": self.new_role_detail_list.json, "policies": self.policies.json } return results def _update_group_membership(self): """A hacky approach to ensuring that groups have their list of members, as that is not included in the initial AWS API call response and we have to calculate it ourselves.""" # Let's make sure the groups know who their members are # First, skim through the user_detail_list, and compile the dict of groups and their users groups = {} for principal in self.user_detail_list.principals: if principal.principal_type == "User": if principal.group_member: logger.debug( f"User {principal.name} is a member of the group(s): {principal.group_member}" ) for group in principal.group_member: if group not in groups: groups[group] = [] groups[group].append(principal.name) else: groups[group].append(principal.name) # Let's make sure we add the list of group members to their groups We do that by skimming through that # groups list and then adding them as class attributes to the principal class. logger.debug(f"_update_group_membership: groups: {groups}") for principal in self.group_detail_list.principals: if principal.principal_type == "Group": for group in groups: if group.lower() == principal.name.lower(): principal.members.extend(groups[group]) logger.debug( f"_update_group_membership: The group {group} has members: {groups[group]}" ) 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")) aws_managed_policies = list(dict.fromkeys(aws_managed_policies)) 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: # Skipping coverage here because it would be redundant if "arn:aws:iam::aws:" not in attached_managed_policy.get( "PolicyArn"): # pragma: no cover 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: # Skipping coverage here because it would be redundant if "arn:aws:iam::aws:" not in attached_managed_policy.get( "PolicyArn"): # pragma: no cover 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: # Skipping coverage here because it would be redundant if "arn:aws:iam::aws:" not in attached_managed_policy.get( "PolicyArn"): # pragma: no cover customer_managed_policies.append( attached_managed_policy.get("PolicyName")) customer_managed_policies = list( dict.fromkeys(customer_managed_policies)) 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: if principal.principal_type == "User": group_membership = principal.group_member elif principal.principal_type == "Group": group_membership = principal.members else: group_membership = None entry = dict( Principal=principal.name, Type=principal.principal_type, PolicyType="Inline", ManagedBy="Customer", PolicyName=inline_policy.get("PolicyName"), GroupMembership=group_membership, ) 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" if principal.principal_type == "User": group_membership = principal.group_member elif principal.principal_type == "Group": group_membership = principal.members else: group_membership = None entry = dict( Principal=principal.name, Type=principal.principal_type, PolicyType="Managed", ManagedBy=managed_by, PolicyName=attached_managed_policy.get("PolicyName"), GroupMembership=group_membership, ) 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"), GroupMembership=principal.group_member, ) 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"), GroupMembership=principal.group_member, ) 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=DEFAULT_EXCLUSIONS, modify_only=True): """Scan the account authorization details for missing resource constraints.""" if not isinstance(exclusions, Exclusions): raise Exception( "The provided exclusions is not the Exclusions object type. " "Please use the Exclusions object.") self.findings = Findings(exclusions) print("-----USERS-----") self.scan_principal_type_details(self.user_detail_list, exclusions, modify_only) print("-----GROUPS-----") self.scan_principal_type_details(self.group_detail_list, exclusions, modify_only) print("-----ROLES-----") self.scan_principal_type_details(self.role_detail_list, exclusions, modify_only) print("-----POLICIES-----") self.findings.principal_policy_mapping = self.principal_policy_mapping self.scan_policy_details(exclusions, modify_only) return self.findings.json def scan_policy_details(self, exclusions=DEFAULT_EXCLUSIONS, modify_only=True): """Scan the ManagedPolicyDetails block of the account authorization details output.""" if not isinstance(exclusions, Exclusions): raise Exception( "The provided exclusions is not the Exclusions object type. " "Please use the Exclusions object.") for policy in self.policies.policy_details: print(f"Scanning policy: {policy.policy_name}") actions_missing_resource_constraints = [] if exclusions.is_policy_excluded( policy.policy_name) or exclusions.is_policy_excluded( policy.full_policy_path): print(f"\tExcluded policy name: {policy.policy_name}") 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( exclusions)) else: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement.missing_resource_constraints( exclusions)) if actions_missing_resource_constraints: actions_missing_resource_constraints = list( dict.fromkeys(actions_missing_resource_constraints) ) # remove duplicates actions_missing_resource_constraints.sort() policy_finding = PolicyFinding( policy_name=policy.policy_name, arn=policy.arn, actions=actions_missing_resource_constraints, policy_document=policy.policy_document, exclusions=exclusions, ) self.findings.add_policy_finding(policy_finding) def scan_principal_type_details( self, principal_type_detail_list, exclusions=DEFAULT_EXCLUSIONS, modify_only=True, ): """Scan the UserDetailList, GroupDetailList, or RoleDetailList blocks of the account authorization details output.""" if not isinstance(exclusions, Exclusions): raise Exception( "The provided exclusions is not the Exclusions object type. " "Please use the Exclusions object.") for principal in principal_type_detail_list.principals: print(f"Scanning {principal.principal_type}: {principal.name}") for policy in principal.policy_list: print(f"\tScanning Policy: {policy['PolicyName']}") if exclusions.is_policy_excluded(policy["PolicyName"]): pass elif principal.is_principal_excluded(exclusions): 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( exclusions)) else: if statement.effect == "Allow": actions_missing_resource_constraints.extend( statement.missing_resource_constraints( exclusions)) if actions_missing_resource_constraints: if principal.principal_type == "User": user_finding = UserFinding( policy_name=policy["PolicyName"], arn=principal.arn, actions=actions_missing_resource_constraints, policy_document=policy["PolicyDocument"], exclusions=exclusions, attached_managed_policies=principal. attached_managed_policies, group_membership=principal.group_member, ) self.findings.add_user_finding(user_finding) elif principal.principal_type == "Group": group_finding = GroupFinding( policy_name=policy["PolicyName"], arn=principal.arn, actions=actions_missing_resource_constraints, policy_document=policy["PolicyDocument"], exclusions=exclusions, members=principal.members, attached_managed_policies=principal. attached_managed_policies, ) logger.debug( f"scan_principal_type_details: The Group {principal.name} has the members {principal.members}" ) self.findings.add_group_finding(group_finding) elif principal.principal_type == "Role": role_finding = RoleFinding( policy_name=policy["PolicyName"], arn=principal.arn, actions=actions_missing_resource_constraints, policy_document=policy["PolicyDocument"], exclusions=exclusions, assume_role_policy_document=principal. assume_role_policy_document, attached_managed_policies=principal. attached_managed_policies, ) self.findings.add_role_finding(role_finding)