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 test_finding_actions_excluded(self): test_policy = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject", "logs:CreateLogStream", "logs:PutLogEvents"], "Resource": "*" }] } policy_document = PolicyDocument(test_policy) # (1) EXCLUDE actions exclusions_cfg = { "users": ["obama"], "groups": ["admin"], "roles": ["MyRole"], "policies": ["someOtherName"], "exclude-actions": ["logs:CreateLogStream", "logs:PutLogEvents"] } exclusions = Exclusions(exclusions_cfg) finding = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SNSNotifications", actions=["s3:GetObject"], policy_document=policy_document, exclusions=exclusions) # print(finding.actions) self.assertListEqual(finding.actions, ["s3:GetObject"])
def test_findings_excluded(self): test_policy = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] } policy_document = PolicyDocument(test_policy) always_exclude_actions = [ "logs:CreateLogStream", "logs:PutLogEvents" ] finding = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SNSNotifications", actions=["s3:GetObject"], policy_document=policy_document, always_exclude_actions=always_exclude_actions ) print(finding.actions) self.assertListEqual(finding.actions, ["s3:GetObject"])
def test_finding_attributes(self): """scan.findings.new_finding""" test_policy = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] } policy_document = PolicyDocument(test_policy) finding = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SNSNotifications", actions=["s3:GetObject"], policy_document=policy_document) self.assertEqual(finding.account_id, "123456789012") self.assertEqual(finding.managed_by, "Customer") self.assertEqual(len(finding.services_affected), 1) self.assertEqual(len(finding.actions), 1) self.assertDictEqual(finding.policy_document.json, policy_document.json) expected_finding_json = { "AccountID": "123456789012", "ManagedBy": "Customer", "Name": "SNSNotifications", "PolicyName": "MyPolicy", "Type": "Group", "Arn": "arn:aws:iam::123456789012:group/SNSNotifications", "ActionsCount": 1, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "*" }] }, "AssumableByComputeService": [], "AssumeRolePolicyDocument": None, "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [], } # print(json.dumps(finding.json, indent=4)) self.maxDiff = None self.assertDictEqual(finding.json, expected_finding_json)
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 test_finding_actions_included(self): test_policy = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "ec2:DescribeInstances", # This is a bad thing to include, but just for the hell of it ], "Resource": "*" } ] } policy_document = PolicyDocument(test_policy) # (2) INCLUDE actions exclusions_cfg = { "users": ["obama"], "groups": ["admin"], "roles": ["MyRole"], "policies": ["someOtherName"], "include-actions": [ "ec2:DescribeInstances" ], "exclude-actions": [ "s3:GetObject", ], } exclusions = Exclusions(exclusions_cfg) finding = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SNSNotifications", actions=["s3:GetObject", "ec2:DescribeInstances"], policy_document=policy_document, exclusions=exclusions ) # print(finding.actions) expected_results = [ "ec2:DescribeInstances", ] self.assertListEqual(finding.actions, expected_results) group_finding = GroupFinding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:group/SNSNotifications", actions=["s3:GetObject", "ec2:DescribeInstances"], policy_document=policy_document, exclusions=exclusions, members=None ) self.assertListEqual(group_finding.actions, expected_results)
def test_findings_for_roles_assumable_by_compute_services_ecs_tasks_new(self): 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) test_policy = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject" ], "Resource": "*" } ] } policy_document = PolicyDocument(test_policy) finding = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:role/TestComputeService", actions=["s3:GetObject"], policy_document=policy_document, assume_role_policy_document=assume_role_policy_document ) # print(finding.role_assumable_by_compute_services) self.assertListEqual(finding.role_assumable_by_compute_services, ["ecs-tasks"]) role_finding = RoleFinding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:role/TestComputeService", actions=["s3:GetObject"], policy_document=policy_document, assume_role_policy_document=assume_role_policy_document ) self.assertListEqual(role_finding.role_assumable_by_compute_services, ["ecs-tasks"])
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 test_findings_for_roles_assumable_by_compute_services_empty(self): """output.findings.role_assumable_by_compute_services""" trust_policy_from_non_compute_service = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sts:AssumeRole" ], "Principal": { "Service": ["yolo.amazonaws.com"] } } ] } assume_role_policy_document = AssumeRolePolicyDocument(trust_policy_from_non_compute_service) test_policy = { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject" ], "Resource": "*" } ] } policy_document = PolicyDocument(test_policy) finding = Finding( policy_name="MyPolicy", arn="arn:aws:iam::123456789012:role/TestComputeService", actions=["s3:GetObject"], policy_document=policy_document, assume_role_policy_document=assume_role_policy_document ) self.assertListEqual(finding.role_assumable_by_compute_services, []) # print(json.dumps(finding.assume_role_policy_document_json, indent=4)) self.assertDictEqual(finding.assume_role_policy_document_json, trust_policy_from_non_compute_service)
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)