def test_authorization_file_details_missing_constraints_v2(self): authorization_details = AuthorizationDetails(example_authz_details_for_overrides_complete) result = authorization_details.missing_resource_constraints(modify_only=False) expected_results_file = os.path.abspath( os.path.join( os.path.dirname(__file__), os.path.pardir, "files", "scanning", "test_authorization_file_details_missing_constraints_v2.json", ) ) # print(expected_results_file) with open(expected_results_file) as json_file: expected_result = json.load(json_file) # print(json.dumps(result, indent=4)) self.maxDiff = None self.assertListEqual(result, expected_result)
def scan_account_authorization_details( account_authorization_details_cfg, exclusions, account_name="default", output_directory=os.getcwd(), write_data_files=False): # pragma: no cover """ Given the path to account authorization details files and the exclusions config file, scan all inline and managed policies in the account to identify actions that do not leverage resource constraints. """ logger.debug("Identifying modify-only actions that are not leveraging " "resource constraints...") check_authorization_details_schema(account_authorization_details_cfg) authorization_details = AuthorizationDetails( account_authorization_details_cfg) results = authorization_details.missing_resource_constraints( exclusions, modify_only=True) principal_policy_mapping = authorization_details.principal_policy_mapping # For the IAM Principals tab, add on risk stats per principal for principal_policy_entry in principal_policy_mapping: for finding in results: if principal_policy_entry.get("PolicyName").lower() == finding.get( "PolicyName").lower(): principal_policy_entry["Actions"] = len(finding["Actions"]) principal_policy_entry["PrivilegeEscalation"] = len( finding["PrivilegeEscalation"]) principal_policy_entry["DataExfiltrationActions"] = len( finding["DataExfiltrationActions"]) principal_policy_entry["PermissionsManagementActions"] = len( finding["PermissionsManagementActions"]) principal_name = principal_policy_entry["Principal"] # Customer Managed Policies if finding.get("Type") == "Policy" and finding.get( "ManagedBy" ) == "Customer" and principal_policy_entry.get( "Type") != "Policy": if "Principals" not in finding: finding["Principals"] = [principal_name] else: if principal_name not in finding["Principals"]: finding["Principals"].append(principal_name) # AWS Managed Policies if finding.get("Type") == "Policy" and finding.get( "ManagedBy") == "AWS": if "Principals" not in finding: finding["Principals"] = [principal_name] else: if principal_name not in finding["Principals"]: finding["Principals"].append(principal_name) # Lazy method to get an account ID account_id = None for item in results: if item["ManagedBy"] == "Customer": account_id = item["AccountID"] break html_report = HTMLReport(account_id=account_id, account_name=account_name, results=results, exclusions_cfg=exclusions, principal_policy_mapping=principal_policy_mapping) rendered_report = html_report.get_html_report() # Raw data file if write_data_files: if output_directory is None: output_directory = os.getcwd() # new_data = authorization_details.new_principal_policy_mapping # new_raw_data_file = os.path.join(output_directory, f"iam-new-principal-policy-mapping-{account_name}.json") # new_raw_data_filepath = write_results_data_file(new_data, new_raw_data_file) # print(f"Raw data file saved: {str(new_raw_data_filepath)}") raw_data_file = os.path.join(output_directory, f"iam-results-{account_name}.json") raw_data_filepath = write_results_data_file(results, raw_data_file) print(f"Raw data file saved: {str(raw_data_filepath)}") # Principal policy mapping principal_policy_mapping_file = os.path.join( output_directory, f"iam-principals-{account_name}.json") principal_policy_mapping_filepath = write_results_data_file( principal_policy_mapping, principal_policy_mapping_file) print( f"Principals data file saved: {str(principal_policy_mapping_filepath)}" ) # Create the CSV triage sheet create_triage_worksheet(account_name, results, output_directory) return rendered_report
def test_authorization_file_details_missing_constraints(self): authz_file = { "UserDetailList": [], "GroupDetailList": [], "RoleDetailList": [], "Policies": [{ "PolicyName": "NotYourPolicy", "PolicyId": "YAAAAASSQUEEEN", "Arn": "arn:aws:iam::012345678901:policy/NotYourPolicy", "Path": "/", "DefaultVersionId": "v9", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": True, "CreateDate": "2020-01-29 21:24:20+00:00", "UpdateDate": "2020-01-29 23:23:12+00:00", "PolicyVersionList": [{ "Document": { "Version": "2012-10-17", "Statement": [{ "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage" ], "Resource": ["*"] }] }, "VersionId": "v9", "IsDefaultVersion": True, "CreateDate": "2020-01-29 23:23:12+00:00" }] }, { "PolicyName": "PolicyForTestingOverrides", "PolicyId": "PolicyForTestingOverrides", "Arn": "arn:aws:iam::012345678901:policy/PolicyForTestingOverrides", "Path": "/", "DefaultVersionId": "v9", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": True, "CreateDate": "2020-01-29 21:24:20+00:00", "UpdateDate": "2020-01-29 23:23:12+00:00", "PolicyVersionList": [{ "Document": { "Version": "2012-10-17", "Statement": [{ "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["s3:CreateBucket"], "Resource": ["arn:aws:s3:::mybucket"] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject"], "Resource": ["*"] }] }, "VersionId": "v9", "IsDefaultVersion": True, "CreateDate": "2020-01-29 23:23:12+00:00" }] }] } authorization_details = AuthorizationDetails(authz_file) results = authorization_details.missing_resource_constraints( modify_only=False) expected_results = [{ "AccountID": "012345678901", "ManagedBy": "Customer", "PolicyName": "NotYourPolicy", "Type": "Policy", "Arn": "arn:aws:iam::012345678901:policy/NotYourPolicy", "ActionsCount": 3, "ServicesCount": 1, "Services": ["ecr"], "Actions": ["ecr:CompleteLayerUpload", "ecr:PutImage", "ecr:UploadLayerPart"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage" ], "Resource": ["*"] }] }, "AssumeRolePolicyDocument": None, "AssumableByComputeService": [], "PrivilegeEscalation": [], "DataExfiltrationActions": [], "PermissionsManagementActions": [] }, { "AccountID": "012345678901", "ManagedBy": "Customer", "PolicyName": "PolicyForTestingOverrides", "Type": "Policy", "Arn": "arn:aws:iam::012345678901:policy/PolicyForTestingOverrides", "ActionsCount": 2, "ServicesCount": 1, "Services": ["s3"], "Actions": ["s3:GetObject", "s3:PutObject"], "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["s3:CreateBucket"], "Resource": ["arn:aws:s3:::mybucket"] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject"], "Resource": ["*"] }] }, "AssumeRolePolicyDocument": None, "AssumableByComputeService": [], "PrivilegeEscalation": [], "DataExfiltrationActions": ["s3:GetObject"], "PermissionsManagementActions": [] }] # print(json.dumps(results, indent=4)) self.maxDiff = None self.assertListEqual(results, expected_results)
def test_exclusions_for_service_roles(self): """test_exclusions_for_service_roles: Ensuring that exclusions config of service roles are specifically skipped, as designed""" authz_file = { "UserDetailList": [], "GroupDetailList": [], "RoleDetailList": [ { "Path": "/aws-service-role/cloudwatch-crossaccount.amazonaws.com/", "RoleName": "AWSServiceRoleForCloudWatchCrossAccount", "RoleId": "LALALALALAALALA", "Arn": "arn:aws:iam::115657980943:role/aws-service-role/cloudwatch-crossaccount.amazonaws.com/AWSServiceRoleForCloudWatchCrossAccount", "CreateDate": "2019-11-07 20:21:23+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Service": "cloudwatch-crossaccount.amazonaws.com" }, "Action": "sts:AssumeRole" }] }, "InstanceProfileList": [], "RolePolicyList": [], "AttachedManagedPolicies": [{ "PolicyName": "CloudWatch-CrossAccountAccess", "PolicyArn": "arn:aws:iam::aws:policy/aws-service-role/CloudWatch-CrossAccountAccess" }] }, ], "Policies": [{ "PolicyName": "CloudWatch-CrossAccountAccess", "PolicyId": "LALALALALAALALA", "Arn": "arn:aws:iam::aws:policy/aws-service-role/CloudWatch-CrossAccountAccess", # "Arn": "arn:aws:iam::aws:policy/Sup", "Path": "/aws-service-role/", "DefaultVersionId": "v1", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": None, "IsAttachable": True, "CreateDate": "2019-07-23 09:59:27+00:00", "UpdateDate": "2019-07-23 09:59:27+00:00", "PolicyVersionList": [{ "Document": { "Version": "2012-10-17", # This is fake, I'm just trying to trigger a response "Statement": [{ "Action": ["iam:*"], "Resource": ["*"], "Effect": "Allow" }] }, "VersionId": "v1", "IsDefaultVersion": True, "CreateDate": "2019-07-23 09:59:27+00:00" }] }] } exclusions_cfg = {"policies": ["aws-service-role*"]} authorization_details = AuthorizationDetails(authz_file) exclusions = Exclusions(exclusions_cfg) results = authorization_details.missing_resource_constraints( exclusions) expected_results = [] # print(json.dumps(results, indent=4)) self.assertListEqual(results, expected_results)
def scan_account_authorization_file(input_file, exclusions_cfg, output, all_access_levels, skip_open_report): """ Given the path to account authorization details files and the exclusions config file, scan all inline and managed policies in the account to identify actions that do not leverage resource constraints. """ with open(input_file) as f: contents = f.read() account_authorization_details_cfg = json.loads(contents) check_authorization_details_schema(account_authorization_details_cfg) # Scan authorization details. Defaults to modify-only permissions if all_access_levels: logger.debug( "--all-access-levels selected. Identifying all actions that are not leveraging resource " "constraints...") authorization_details = AuthorizationDetails( account_authorization_details_cfg) results = authorization_details.missing_resource_constraints( exclusions_cfg, modify_only=False) else: logger.debug( "--all-access-levels NOT selected. Identifying modify-only actions that are not leveraging " "resource constraints...") authorization_details = AuthorizationDetails( account_authorization_details_cfg) results = authorization_details.missing_resource_constraints( exclusions_cfg, modify_only=True) principal_policy_mapping = authorization_details.principal_policy_mapping account_name = Path(input_file).stem # Lazy method to get an account ID account_id = None for item in results: if item["ManagedBy"] == "Customer": account_id = item["AccountID"] break # HTML report output_directory = output # Account metadata account_metadata = { "account_name": account_name, "account_id": account_id, "customer_managed_policies": len(authorization_details.customer_managed_policies_in_use), "aws_managed_policies": len(authorization_details.aws_managed_policies_in_use), } # Raw data file raw_data_file = os.path.join(output, f"iam-results-{account_name}.json") raw_data_filepath = write_results_data_file(results, raw_data_file) print(f"Raw data file saved: {str(raw_data_filepath)}") # Principal policy mapping principal_policy_mapping_file = os.path.join( output, f"iam-principals-{account_name}.json") principal_policy_mapping_filepath = write_results_data_file( principal_policy_mapping, principal_policy_mapping_file) print( f"Principals data file saved: {str(principal_policy_mapping_filepath)}" ) print("Creating the HTML Report") generate_html_report( account_metadata, results, principal_policy_mapping, output_directory, exclusions_cfg, skip_open_report=skip_open_report, )
def test_output_html_output_as_string(self): example_authz_details_file = os.path.abspath( os.path.join( os.path.dirname(__file__), os.path.pardir, "files", "example-authz-details.json", )) with open(example_authz_details_file, "r") as json_file: cfg = json.load(json_file) decision = check_authorization_details_schema(cfg) self.assertTrue(decision) # TODO: These values are just for testing account_authorization_details_cfg = cfg exclusions = DEFAULT_EXCLUSIONS authorization_details = AuthorizationDetails( account_authorization_details_cfg) results = authorization_details.missing_resource_constraints( exclusions, modify_only=True) principal_policy_mapping = authorization_details.principal_policy_mapping # For the IAM Principals tab, add on risk stats per principal for principal_policy_entry in principal_policy_mapping: for finding in results: if principal_policy_entry.get("PolicyName").lower( ) == finding.get("PolicyName").lower(): principal_policy_entry["Actions"] = len(finding["Actions"]) principal_policy_entry["PrivilegeEscalation"] = len( finding["PrivilegeEscalation"]) principal_policy_entry["DataExfiltrationActions"] = len( finding["DataExfiltrationActions"]) principal_policy_entry[ "PermissionsManagementActions"] = len( finding["PermissionsManagementActions"]) principal_name = principal_policy_entry["Principal"] # Customer Managed Policies if finding.get("Type") == "Policy" and finding.get( "ManagedBy" ) == "Customer" and principal_policy_entry.get( "Type") != "Policy": if "Principals" not in finding: finding["Principals"] = [principal_name] else: if principal_name not in finding["Principals"]: finding["Principals"].append(principal_name) # AWS Managed Policies if finding.get("Type") == "Policy" and finding.get( "ManagedBy") == "AWS": if "Principals" not in finding: finding["Principals"] = [principal_name] else: if principal_name not in finding["Principals"]: finding["Principals"].append(principal_name) # Lazy method to get an account ID account_id = None for item in results: if item["ManagedBy"] == "Customer": account_id = item["AccountID"] break html_report = HTMLReport( account_id=account_id, account_name="CHANGEME", results=results, exclusions_cfg=exclusions, principal_policy_mapping=principal_policy_mapping) rendered_report = html_report.get_html_report()
def scan_account_authorization_file(input_file, exclusions, output, all_access_levels, skip_open_report): # pragma: no cover """ Given the path to account authorization details files and the exclusions config file, scan all inline and managed policies in the account to identify actions that do not leverage resource constraints. """ with open(input_file) as f: contents = f.read() account_authorization_details_cfg = json.loads(contents) check_authorization_details_schema(account_authorization_details_cfg) # Scan authorization details. Defaults to modify-only permissions if all_access_levels: logger.debug( "--all-access-levels selected. Identifying all actions that are not leveraging resource " "constraints...") authorization_details = AuthorizationDetails( account_authorization_details_cfg) results = authorization_details.missing_resource_constraints( exclusions, modify_only=False) else: logger.debug( "--all-access-levels NOT selected. Identifying modify-only actions that are not leveraging " "resource constraints...") authorization_details = AuthorizationDetails( account_authorization_details_cfg) results = authorization_details.missing_resource_constraints( exclusions, modify_only=True) principal_policy_mapping = authorization_details.principal_policy_mapping # For the IAM Principals tab, add on risk stats per principal for principal_policy_entry in principal_policy_mapping: for finding in results: if principal_policy_entry.get("PolicyName").lower() == finding.get( "PolicyName").lower(): principal_policy_entry["Actions"] = len(finding["Actions"]) principal_policy_entry["PrivilegeEscalation"] = len( finding["PrivilegeEscalation"]) principal_policy_entry["DataExfiltrationActions"] = len( finding["DataExfiltrationActions"]) principal_policy_entry["PermissionsManagementActions"] = len( finding["PermissionsManagementActions"]) principal_name = principal_policy_entry["Principal"] # Customer Managed Policies if finding.get("Type") == "Policy" and finding.get( "ManagedBy" ) == "Customer" and principal_policy_entry.get( "Type") != "Policy": if "Principals" not in finding: finding["Principals"] = [principal_name] else: if principal_name not in finding["Principals"]: finding["Principals"].append(principal_name) # AWS Managed Policies if finding.get("Type") == "Policy" and finding.get( "ManagedBy") == "AWS": if "Principals" not in finding: finding["Principals"] = [principal_name] else: if principal_name not in finding["Principals"]: finding["Principals"].append(principal_name) account_name = Path(input_file).stem # Lazy method to get an account ID account_id = None for item in results: if item["ManagedBy"] == "Customer": account_id = item["AccountID"] break # HTML report output_directory = output # Account metadata account_metadata = { "account_name": account_name, "account_id": account_id, "customer_managed_policies": len(authorization_details.customer_managed_policies_in_use), "aws_managed_policies": len(authorization_details.aws_managed_policies_in_use), } # Raw data file raw_data_file = os.path.join(output, f"iam-results-{account_name}.json") raw_data_filepath = write_results_data_file(results, raw_data_file) print(f"Raw data file saved: {str(raw_data_filepath)}") # Principal policy mapping principal_policy_mapping_file = os.path.join( output, f"iam-principals-{account_name}.json") principal_policy_mapping_filepath = write_results_data_file( principal_policy_mapping, principal_policy_mapping_file) print( f"Principals data file saved: {str(principal_policy_mapping_filepath)}" ) print("Creating the HTML Report") generate_html_report( account_metadata, results, principal_policy_mapping, output_directory, exclusions.config, skip_open_report=skip_open_report, )