From 09b4548445a7355b25bef7bab2bb3bcc0b734ab6 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:31:39 +0100 Subject: [PATCH] feat(compliance): execute all compliance by default (#3003) Co-authored-by: Pepe Fagoaga --- docs/tutorials/compliance.md | 31 +- docs/tutorials/img/compliance.png | Bin 0 -> 95639 bytes prowler/__main__.py | 25 +- prowler/config/config.py | 7 +- prowler/lib/outputs/compliance/__init__.py | 0 .../aws_well_architected_framework.py | 55 ++ prowler/lib/outputs/compliance/cis.py | 36 ++ prowler/lib/outputs/compliance/cis_aws.py | 34 ++ prowler/lib/outputs/compliance/cis_gcp.py | 35 ++ prowler/lib/outputs/compliance/compliance.py | 472 ++++++++++++++++++ .../lib/outputs/compliance/ens_rd2022_aws.py | 45 ++ prowler/lib/outputs/compliance/generic.py | 51 ++ .../outputs/compliance/iso27001_2013_aws.py | 53 ++ .../outputs/compliance/mitre_attack_aws.py | 66 +++ prowler/lib/outputs/csv.py | 10 + prowler/lib/outputs/file_descriptors.py | 45 +- prowler/lib/outputs/models.py | 11 +- prowler/lib/outputs/outputs.py | 37 +- prowler/providers/common/outputs.py | 1 - .../lib/outputs/compliance/compliance_test.py | 111 ++++ 20 files changed, 1069 insertions(+), 56 deletions(-) create mode 100644 docs/tutorials/img/compliance.png create mode 100644 prowler/lib/outputs/compliance/__init__.py create mode 100644 prowler/lib/outputs/compliance/aws_well_architected_framework.py create mode 100644 prowler/lib/outputs/compliance/cis.py create mode 100644 prowler/lib/outputs/compliance/cis_aws.py create mode 100644 prowler/lib/outputs/compliance/cis_gcp.py create mode 100644 prowler/lib/outputs/compliance/compliance.py create mode 100644 prowler/lib/outputs/compliance/ens_rd2022_aws.py create mode 100644 prowler/lib/outputs/compliance/generic.py create mode 100644 prowler/lib/outputs/compliance/iso27001_2013_aws.py create mode 100644 prowler/lib/outputs/compliance/mitre_attack_aws.py create mode 100644 prowler/lib/outputs/csv.py create mode 100644 tests/lib/outputs/compliance/compliance_test.py diff --git a/docs/tutorials/compliance.md b/docs/tutorials/compliance.md index a85037a0dc..6d39e96e84 100644 --- a/docs/tutorials/compliance.md +++ b/docs/tutorials/compliance.md @@ -1,5 +1,18 @@ # Compliance -Prowler allows you to execute checks based on requirements defined in compliance frameworks. +Prowler allows you to execute checks based on requirements defined in compliance frameworks. By default, it will execute and give you an overview of the status of each compliance framework: + + + +> You can find CSVs containing detailed compliance results inside the compliance folder within Prowler's output folder. + +## Execute Prowler based on Compliance Frameworks +Prowler can analyze your environment based on a specific compliance framework and get more details, to do it, you can use option `--compliance`: +```sh +prowler --compliance +``` +Standard results will be shown and additionally the framework information as the sample below for CIS AWS 1.5. For details a CSV file has been generated as well. + + ## List Available Compliance Frameworks In order to see which compliance frameworks are cover by Prowler, you can use option `--list-compliance`: @@ -10,9 +23,12 @@ Currently, the available frameworks are: - `cis_1.4_aws` - `cis_1.5_aws` +- `cis_2.0_aws` +- `cisa_aws` - `ens_rd2022_aws` - `aws_audit_manager_control_tower_guardrails_aws` - `aws_foundational_security_best_practices_aws` +- `aws_well_architected_framework_reliability_pillar_aws` - `aws_well_architected_framework_security_pillar_aws` - `cisa_aws` - `fedramp_low_revision_4_aws` @@ -22,6 +38,9 @@ Currently, the available frameworks are: - `gxp_eu_annex_11_aws` - `gxp_21_cfr_part_11_aws` - `hipaa_aws` +- `iso27001_2013_aws` +- `iso27001_2013_aws` +- `mitre_attack_aws` - `nist_800_53_revision_4_aws` - `nist_800_53_revision_5_aws` - `nist_800_171_revision_2_aws` @@ -38,7 +57,6 @@ prowler --list-compliance-requirements ``` Example for the first requirements of CIS 1.5 for AWS: - ``` Listing CIS 1.5 AWS Compliance Requirements: @@ -71,15 +89,6 @@ Requirement Id: 1.5 ``` -## Execute Prowler based on Compliance Frameworks -As we mentioned, Prowler can be execute to analyse you environment based on a specific compliance framework, to do it, you can use option `--compliance`: -```sh -prowler --compliance -``` -Standard results will be shown and additionally the framework information as the sample below for CIS AWS 1.5. For details a CSV file has been generated as well. - - - ## Create and contribute adding other Security Frameworks This information is part of the Developer Guide and can be found here: https://docs.prowler.cloud/en/latest/tutorials/developer-guide/. diff --git a/docs/tutorials/img/compliance.png b/docs/tutorials/img/compliance.png new file mode 100644 index 0000000000000000000000000000000000000000..1492089c86d26394aad475a1a8c77252a4c5ea32 GIT binary patch literal 95639 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

@0=hi}`uM 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 literal 0 HcmV?d00001 diff --git a/prowler/__main__.py b/prowler/__main__.py index f7d0aa3813..621b48bded 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -28,13 +28,10 @@ from prowler.lib.check.custom_checks_metadata import ( parse_custom_checks_metadata_file, update_checks_metadata, ) -<<<<<<< HEAD from prowler.lib.check.managers import ExecutionManager -======= ->>>>>>> 856afb396 (chore(update): rebase from master (#3067)) from prowler.lib.cli.parser import ProwlerArgumentParser from prowler.lib.logger import logger, set_logging_config -from prowler.lib.outputs.compliance import display_compliance_table +from prowler.lib.outputs.compliance.compliance import display_compliance_table from prowler.lib.outputs.html import add_html_footer, fill_html_overview_statistics from prowler.lib.outputs.json import close_json from prowler.lib.outputs.outputs import extract_findings_statistics @@ -86,6 +83,9 @@ def prowler(): # We treat the compliance framework as another output format if compliance_framework: args.output_modes.extend(compliance_framework) + # If no input compliance framework, set all + else: + args.output_modes.extend(get_available_compliance_frameworks(provider)) # Set Logger configuration set_logging_config(args.log_level, args.log_file, args.only_logs) @@ -197,11 +197,7 @@ def prowler(): findings = [] if len(checks_to_execute): -<<<<<<< HEAD execution_manager = ExecutionManager( -======= - findings = execute_checks( ->>>>>>> 856afb396 (chore(update): rebase from master (#3067)) checks_to_execute, provider, audit_info, @@ -322,8 +318,12 @@ def prowler(): provider, ) - if compliance_framework and findings: - for compliance in compliance_framework: + if findings: + compliance_overview = False + if not compliance_framework: + compliance_overview = True + compliance_framework = get_available_compliance_frameworks(provider) + for compliance in sorted(compliance_framework): # Display compliance table display_compliance_table( findings, @@ -331,6 +331,11 @@ def prowler(): compliance, audit_output_options.output_filename, audit_output_options.output_directory, + compliance_overview, + ) + if compliance_overview: + print( + f"\nDetailed compliance results are in {Fore.YELLOW}{audit_output_options.output_directory}/compliance/{Style.RESET_ALL}\n" ) # If custom checks were passed, remove the modules diff --git a/prowler/config/config.py b/prowler/config/config.py index dbeaf5cb9d..0aa3d6da60 100644 --- a/prowler/config/config.py +++ b/prowler/config/config.py @@ -29,9 +29,12 @@ valid_severities = ["critical", "high", "medium", "low", "informational"] actual_directory = pathlib.Path(os.path.dirname(os.path.realpath(__file__))) -def get_available_compliance_frameworks(): +def get_available_compliance_frameworks(provider=None): available_compliance_frameworks = [] - for provider in ["aws", "gcp", "azure"]: + providers = ["aws", "gcp", "azure"] + if provider: + providers = [provider] + for provider in providers: with os.scandir(f"{actual_directory}/../compliance/{provider}") as files: for file in files: if file.is_file() and file.name.endswith(".json"): diff --git a/prowler/lib/outputs/compliance/__init__.py b/prowler/lib/outputs/compliance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/lib/outputs/compliance/aws_well_architected_framework.py b/prowler/lib/outputs/compliance/aws_well_architected_framework.py new file mode 100644 index 0000000000..f13d2f935e --- /dev/null +++ b/prowler/lib/outputs/compliance/aws_well_architected_framework.py @@ -0,0 +1,55 @@ +from csv import DictWriter + +from prowler.config.config import timestamp +from prowler.lib.outputs.models import ( + Check_Output_CSV_AWS_Well_Architected, + generate_csv_fields, +) +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def write_compliance_row_aws_well_architected_framework( + file_descriptors, finding, compliance, output_options, audit_info +): + compliance_output = compliance.Framework + if compliance.Version != "": + compliance_output += "_" + compliance.Version + if compliance.Provider != "": + compliance_output += "_" + compliance.Provider + compliance_output = compliance_output.lower().replace("-", "_") + csv_header = generate_csv_fields(Check_Output_CSV_AWS_Well_Architected) + csv_writer = DictWriter( + file_descriptors[compliance_output], + fieldnames=csv_header, + delimiter=";", + ) + for requirement in compliance.Requirements: + requirement_description = requirement.Description + requirement_id = requirement.Id + for attribute in requirement.Attributes: + compliance_row = Check_Output_CSV_AWS_Well_Architected( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + AccountId=audit_info.audited_account, + Region=finding.region, + AssessmentDate=outputs_unix_timestamp( + output_options.unix_timestamp, timestamp + ), + Requirements_Id=requirement_id, + Requirements_Description=requirement_description, + Requirements_Attributes_Name=attribute.Name, + Requirements_Attributes_WellArchitectedQuestionId=attribute.WellArchitectedQuestionId, + Requirements_Attributes_WellArchitectedPracticeId=attribute.WellArchitectedPracticeId, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_LevelOfRisk=attribute.LevelOfRisk, + Requirements_Attributes_AssessmentMethod=attribute.AssessmentMethod, + Requirements_Attributes_Description=attribute.Description, + Requirements_Attributes_ImplementationGuidanceUrl=attribute.ImplementationGuidanceUrl, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + CheckId=finding.check_metadata.CheckID, + ) + + csv_writer.writerow(compliance_row.__dict__) diff --git a/prowler/lib/outputs/compliance/cis.py b/prowler/lib/outputs/compliance/cis.py new file mode 100644 index 0000000000..5cdfa250f9 --- /dev/null +++ b/prowler/lib/outputs/compliance/cis.py @@ -0,0 +1,36 @@ +from prowler.lib.outputs.compliance.cis_aws import generate_compliance_row_cis_aws +from prowler.lib.outputs.compliance.cis_gcp import generate_compliance_row_cis_gcp +from prowler.lib.outputs.csv import write_csv + + +def write_compliance_row_cis( + file_descriptors, + finding, + compliance, + output_options, + audit_info, + input_compliance_frameworks, +): + compliance_output = "cis_" + compliance.Version + "_" + compliance.Provider.lower() + + # Only with the version of CIS that was selected + if compliance_output in str(input_compliance_frameworks): + for requirement in compliance.Requirements: + for attribute in requirement.Attributes: + if compliance.Provider == "AWS": + (compliance_row, csv_header) = generate_compliance_row_cis_aws( + finding, + compliance, + requirement, + attribute, + output_options, + audit_info, + ) + elif compliance.Provider == "GCP": + (compliance_row, csv_header) = generate_compliance_row_cis_gcp( + finding, compliance, output_options + ) + + write_csv( + file_descriptors[compliance_output], csv_header, compliance_row + ) diff --git a/prowler/lib/outputs/compliance/cis_aws.py b/prowler/lib/outputs/compliance/cis_aws.py new file mode 100644 index 0000000000..31604da5ee --- /dev/null +++ b/prowler/lib/outputs/compliance/cis_aws.py @@ -0,0 +1,34 @@ +from prowler.config.config import timestamp +from prowler.lib.outputs.models import Check_Output_CSV_AWS_CIS, generate_csv_fields +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def generate_compliance_row_cis_aws( + finding, compliance, requirement, attribute, output_options, audit_info +): + compliance_row = Check_Output_CSV_AWS_CIS( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + AccountId=audit_info.audited_account, + Region=finding.region, + AssessmentDate=outputs_unix_timestamp(output_options.unix_timestamp, timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_Profile=attribute.Profile, + Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus, + Requirements_Attributes_Description=attribute.Description, + Requirements_Attributes_RationaleStatement=attribute.RationaleStatement, + Requirements_Attributes_ImpactStatement=attribute.ImpactStatement, + Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure, + Requirements_Attributes_AuditProcedure=attribute.AuditProcedure, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_References=attribute.References, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + CheckId=finding.check_metadata.CheckID, + ) + csv_header = generate_csv_fields(Check_Output_CSV_AWS_CIS) + + return compliance_row, csv_header diff --git a/prowler/lib/outputs/compliance/cis_gcp.py b/prowler/lib/outputs/compliance/cis_gcp.py new file mode 100644 index 0000000000..bbcbb2ff33 --- /dev/null +++ b/prowler/lib/outputs/compliance/cis_gcp.py @@ -0,0 +1,35 @@ +from prowler.config.config import timestamp +from prowler.lib.outputs.models import Check_Output_CSV_GCP_CIS, generate_csv_fields +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def generate_compliance_row_cis_gcp( + finding, compliance, requirement, attribute, output_options +): + compliance_row = Check_Output_CSV_GCP_CIS( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + ProjectId=finding.project_id, + Location=finding.location.lower(), + AssessmentDate=outputs_unix_timestamp(output_options.unix_timestamp, timestamp), + Requirements_Id=requirement.Id, + Requirements_Description=requirement.Description, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_Profile=attribute.Profile, + Requirements_Attributes_AssessmentStatus=attribute.AssessmentStatus, + Requirements_Attributes_Description=attribute.Description, + Requirements_Attributes_RationaleStatement=attribute.RationaleStatement, + Requirements_Attributes_ImpactStatement=attribute.ImpactStatement, + Requirements_Attributes_RemediationProcedure=attribute.RemediationProcedure, + Requirements_Attributes_AuditProcedure=attribute.AuditProcedure, + Requirements_Attributes_AdditionalInformation=attribute.AdditionalInformation, + Requirements_Attributes_References=attribute.References, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + ResourceName=finding.resource_name, + CheckId=finding.check_metadata.CheckID, + ) + csv_header = generate_csv_fields(Check_Output_CSV_GCP_CIS) + + return compliance_row, csv_header diff --git a/prowler/lib/outputs/compliance/compliance.py b/prowler/lib/outputs/compliance/compliance.py new file mode 100644 index 0000000000..b567546590 --- /dev/null +++ b/prowler/lib/outputs/compliance/compliance.py @@ -0,0 +1,472 @@ +import sys + +from colorama import Fore, Style +from tabulate import tabulate + +from prowler.config.config import orange_color +from prowler.lib.check.models import Check_Report +from prowler.lib.logger import logger +from prowler.lib.outputs.compliance.aws_well_architected_framework import ( + write_compliance_row_aws_well_architected_framework, +) +from prowler.lib.outputs.compliance.cis import write_compliance_row_cis +from prowler.lib.outputs.compliance.ens_rd2022_aws import ( + write_compliance_row_ens_rd2022_aws, +) +from prowler.lib.outputs.compliance.generic import write_compliance_row_generic +from prowler.lib.outputs.compliance.iso27001_2013_aws import ( + write_compliance_row_iso27001_2013_aws, +) +from prowler.lib.outputs.compliance.mitre_attack_aws import ( + write_compliance_row_mitre_attack_aws, +) + + +def add_manual_controls( + output_options, audit_info, file_descriptors, input_compliance_frameworks +): + try: + # Check if MANUAL control was already added to output + if "manual_check" in output_options.bulk_checks_metadata: + manual_finding = Check_Report( + output_options.bulk_checks_metadata["manual_check"].json() + ) + manual_finding.status = "INFO" + manual_finding.status_extended = "Manual check" + manual_finding.resource_id = "manual_check" + manual_finding.resource_name = "Manual check" + manual_finding.region = "" + manual_finding.location = "" + manual_finding.project_id = "" + fill_compliance( + output_options, + manual_finding, + audit_info, + file_descriptors, + input_compliance_frameworks, + ) + del output_options.bulk_checks_metadata["manual_check"] + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +def get_check_compliance_frameworks_in_input( + check_id, bulk_checks_metadata, input_compliance_frameworks +): + """get_check_compliance_frameworks_in_input returns a list of Compliance for the given check if the compliance framework is present in the input compliance to execute""" + check_compliances = [] + if bulk_checks_metadata and bulk_checks_metadata[check_id]: + for compliance in bulk_checks_metadata[check_id].Compliance: + compliance_name = "" + if compliance.Version: + compliance_name = ( + compliance.Framework.lower() + + "_" + + compliance.Version.lower() + + "_" + + compliance.Provider.lower() + ) + else: + compliance_name = ( + compliance.Framework.lower() + "_" + compliance.Provider.lower() + ) + if compliance_name.replace("-", "_") in input_compliance_frameworks: + check_compliances.append(compliance) + + return check_compliances + + +def fill_compliance( + output_options, finding, audit_info, file_descriptors, input_compliance_frameworks +): + try: + # We have to retrieve all the check's compliance requirements and get the ones matching with the input ones + check_compliances = get_check_compliance_frameworks_in_input( + finding.check_metadata.CheckID, + output_options.bulk_checks_metadata, + input_compliance_frameworks, + ) + + for compliance in check_compliances: + if compliance.Framework == "ENS" and compliance.Version == "RD2022": + write_compliance_row_ens_rd2022_aws( + file_descriptors, finding, compliance, output_options, audit_info + ) + + elif compliance.Framework == "CIS": + write_compliance_row_cis( + file_descriptors, + finding, + compliance, + output_options, + audit_info, + input_compliance_frameworks, + ) + + elif ( + "AWS-Well-Architected-Framework" in compliance.Framework + and compliance.Provider == "AWS" + ): + write_compliance_row_aws_well_architected_framework( + file_descriptors, finding, compliance, output_options, audit_info + ) + + elif ( + compliance.Framework == "ISO27001" + and compliance.Version == "2013" + and compliance.Provider == "AWS" + ): + write_compliance_row_iso27001_2013_aws( + file_descriptors, finding, compliance, output_options, audit_info + ) + + elif ( + compliance.Framework == "MITRE-ATTACK" + and compliance.Version == "" + and compliance.Provider == "AWS" + ): + write_compliance_row_mitre_attack_aws( + file_descriptors, finding, compliance, output_options, audit_info + ) + + else: + write_compliance_row_generic( + file_descriptors, finding, compliance, output_options, audit_info + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +def display_compliance_table( + findings: list, + bulk_checks_metadata: dict, + compliance_framework: str, + output_filename: str, + output_directory: str, + compliance_overview: bool, +): + try: + if "ens_rd2022_aws" == compliance_framework: + marcos = {} + ens_compliance_table = { + "Proveedor": [], + "Marco/Categoria": [], + "Estado": [], + "Alto": [], + "Medio": [], + "Bajo": [], + "Opcional": [], + } + pass_count = fail_count = 0 + for finding in findings: + check = bulk_checks_metadata[finding.check_metadata.CheckID] + check_compliances = check.Compliance + for compliance in check_compliances: + if ( + compliance.Framework == "ENS" + and compliance.Provider == "AWS" + and compliance.Version == "RD2022" + ): + compliance_version = compliance.Version + compliance_fm = compliance.Framework + compliance_provider = compliance.Provider + for requirement in compliance.Requirements: + for attribute in requirement.Attributes: + marco_categoria = ( + f"{attribute.Marco}/{attribute.Categoria}" + ) + # Check if Marco/Categoria exists + if marco_categoria not in marcos: + marcos[marco_categoria] = { + "Estado": f"{Fore.GREEN}CUMPLE{Style.RESET_ALL}", + "Opcional": 0, + "Alto": 0, + "Medio": 0, + "Bajo": 0, + } + if finding.status == "FAIL": + fail_count += 1 + marcos[marco_categoria][ + "Estado" + ] = f"{Fore.RED}NO CUMPLE{Style.RESET_ALL}" + elif finding.status == "PASS": + pass_count += 1 + if attribute.Nivel == "opcional": + marcos[marco_categoria]["Opcional"] += 1 + elif attribute.Nivel == "alto": + marcos[marco_categoria]["Alto"] += 1 + elif attribute.Nivel == "medio": + marcos[marco_categoria]["Medio"] += 1 + elif attribute.Nivel == "bajo": + marcos[marco_categoria]["Bajo"] += 1 + + # Add results to table + for marco in sorted(marcos): + ens_compliance_table["Proveedor"].append(compliance.Provider) + ens_compliance_table["Marco/Categoria"].append(marco) + ens_compliance_table["Estado"].append(marcos[marco]["Estado"]) + ens_compliance_table["Opcional"].append( + f"{Fore.BLUE}{marcos[marco]['Opcional']}{Style.RESET_ALL}" + ) + ens_compliance_table["Alto"].append( + f"{Fore.LIGHTRED_EX}{marcos[marco]['Alto']}{Style.RESET_ALL}" + ) + ens_compliance_table["Medio"].append( + f"{orange_color}{marcos[marco]['Medio']}{Style.RESET_ALL}" + ) + ens_compliance_table["Bajo"].append( + f"{Fore.YELLOW}{marcos[marco]['Bajo']}{Style.RESET_ALL}" + ) + if fail_count + pass_count < 0: + print( + f"\n {Style.BRIGHT}There are no resources for {Fore.YELLOW}{compliance_fm}_{compliance_version}_{compliance_provider}{Style.RESET_ALL}.\n" + ) + else: + print( + f"\nEstado de Cumplimiento de {Fore.YELLOW}{compliance_fm}_{compliance_version}_{compliance_provider}{Style.RESET_ALL}:" + ) + overview_table = [ + [ + f"{Fore.RED}{round(fail_count / (fail_count + pass_count) * 100, 2)}% ({fail_count}) NO CUMPLE{Style.RESET_ALL}", + f"{Fore.GREEN}{round(pass_count / (fail_count + pass_count) * 100, 2)}% ({pass_count}) CUMPLE{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + if not compliance_overview: + print( + f"\nResultados de {Fore.YELLOW}{compliance_fm}_{compliance_version}_{compliance_provider}{Style.RESET_ALL}:" + ) + print( + tabulate( + ens_compliance_table, + headers="keys", + tablefmt="rounded_grid", + ) + ) + print( + f"{Style.BRIGHT}* Solo aparece el Marco/Categoria que contiene resultados.{Style.RESET_ALL}" + ) + print(f"\nResultados detallados de {compliance_fm} en:") + print( + f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n" + ) + elif "cis_" in compliance_framework: + sections = {} + cis_compliance_table = { + "Provider": [], + "Section": [], + "Level 1": [], + "Level 2": [], + } + pass_count = fail_count = 0 + for finding in findings: + check = bulk_checks_metadata[finding.check_metadata.CheckID] + check_compliances = check.Compliance + for compliance in check_compliances: + if ( + compliance.Framework == "CIS" + and compliance.Version in compliance_framework + ): + compliance_version = compliance.Version + compliance_fm = compliance.Framework + for requirement in compliance.Requirements: + for attribute in requirement.Attributes: + section = attribute.Section + # Check if Section exists + if section not in sections: + sections[section] = { + "Status": f"{Fore.GREEN}PASS{Style.RESET_ALL}", + "Level 1": {"FAIL": 0, "PASS": 0}, + "Level 2": {"FAIL": 0, "PASS": 0}, + } + if finding.status == "FAIL": + fail_count += 1 + elif finding.status == "PASS": + pass_count += 1 + if attribute.Profile == "Level 1": + if finding.status == "FAIL": + sections[section]["Level 1"]["FAIL"] += 1 + else: + sections[section]["Level 1"]["PASS"] += 1 + elif attribute.Profile == "Level 2": + if finding.status == "FAIL": + sections[section]["Level 2"]["FAIL"] += 1 + else: + sections[section]["Level 2"]["PASS"] += 1 + + # Add results to table + sections = dict(sorted(sections.items())) + for section in sections: + cis_compliance_table["Provider"].append(compliance.Provider) + cis_compliance_table["Section"].append(section) + if sections[section]["Level 1"]["FAIL"] > 0: + cis_compliance_table["Level 1"].append( + f"{Fore.RED}FAIL({sections[section]['Level 1']['FAIL']}){Style.RESET_ALL}" + ) + else: + cis_compliance_table["Level 1"].append( + f"{Fore.GREEN}PASS({sections[section]['Level 1']['PASS']}){Style.RESET_ALL}" + ) + if sections[section]["Level 2"]["FAIL"] > 0: + cis_compliance_table["Level 2"].append( + f"{Fore.RED}FAIL({sections[section]['Level 2']['FAIL']}){Style.RESET_ALL}" + ) + else: + cis_compliance_table["Level 2"].append( + f"{Fore.GREEN}PASS({sections[section]['Level 2']['PASS']}){Style.RESET_ALL}" + ) + if fail_count + pass_count < 1: + print( + f"\n {Style.BRIGHT}There are no resources for {Fore.YELLOW}{compliance_fm}_{compliance_version}{Style.RESET_ALL}.\n" + ) + else: + print( + f"\nCompliance Status of {Fore.YELLOW}{compliance_fm}_{compliance_version}{Style.RESET_ALL} Framework:" + ) + overview_table = [ + [ + f"{Fore.RED}{round(fail_count / (fail_count + pass_count) * 100, 2)}% ({fail_count}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(pass_count / (fail_count + pass_count) * 100, 2)}% ({pass_count}) PASS{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + if not compliance_overview: + print( + f"\nFramework {Fore.YELLOW}{compliance_fm}_{compliance_version}{Style.RESET_ALL} Results:" + ) + print( + tabulate( + cis_compliance_table, + headers="keys", + tablefmt="rounded_grid", + ) + ) + print( + f"{Style.BRIGHT}* Only sections containing results appear.{Style.RESET_ALL}" + ) + print(f"\nDetailed results of {compliance_fm} are in:") + print( + f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n" + ) + elif "mitre_attack" in compliance_framework: + tactics = {} + mitre_compliance_table = { + "Provider": [], + "Tactic": [], + "Status": [], + } + pass_count = fail_count = 0 + for finding in findings: + check = bulk_checks_metadata[finding.check_metadata.CheckID] + check_compliances = check.Compliance + for compliance in check_compliances: + if ( + "MITRE-ATTACK" in compliance.Framework + and compliance.Version in compliance_framework + ): + compliance_fm = compliance.Framework + for requirement in compliance.Requirements: + for tactic in requirement.Tactics: + if tactic not in tactics: + tactics[tactic] = {"FAIL": 0, "PASS": 0} + if finding.status == "FAIL": + fail_count += 1 + tactics[tactic]["FAIL"] += 1 + elif finding.status == "PASS": + pass_count += 1 + tactics[tactic]["PASS"] += 1 + + # Add results to table + tactics = dict(sorted(tactics.items())) + for tactic in tactics: + mitre_compliance_table["Provider"].append(compliance.Provider) + mitre_compliance_table["Tactic"].append(tactic) + if tactics[tactic]["FAIL"] > 0: + mitre_compliance_table["Status"].append( + f"{Fore.RED}FAIL({tactics[tactic]['FAIL']}){Style.RESET_ALL}" + ) + else: + mitre_compliance_table["Status"].append( + f"{Fore.GREEN}PASS({tactics[tactic]['PASS']}){Style.RESET_ALL}" + ) + if fail_count + pass_count < 1: + print( + f"\n {Style.BRIGHT}There are no resources for {Fore.YELLOW}{compliance_fm}{Style.RESET_ALL}.\n" + ) + else: + print( + f"\nCompliance Status of {Fore.YELLOW}{compliance_fm}{Style.RESET_ALL} Framework:" + ) + overview_table = [ + [ + f"{Fore.RED}{round(fail_count / (fail_count + pass_count) * 100, 2)}% ({fail_count}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(pass_count / (fail_count + pass_count) * 100, 2)}% ({pass_count}) PASS{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + if not compliance_overview: + print( + f"\nFramework {Fore.YELLOW}{compliance_fm}{Style.RESET_ALL} Results:" + ) + print( + tabulate( + mitre_compliance_table, + headers="keys", + tablefmt="rounded_grid", + ) + ) + print( + f"{Style.BRIGHT}* Only sections containing results appear.{Style.RESET_ALL}" + ) + print(f"\nDetailed results of {compliance_fm} are in:") + print( + f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n" + ) + else: + pass_count = fail_count = 0 + for finding in findings: + check = bulk_checks_metadata[finding.check_metadata.CheckID] + check_compliances = check.Compliance + for compliance in check_compliances: + if ( + compliance.Framework.upper() + in compliance_framework.upper().replace("_", "-") + and compliance.Version in compliance_framework.upper() + and compliance.Provider in compliance_framework.upper() + ): + for requirement in compliance.Requirements: + for attribute in requirement.Attributes: + if finding.status == "FAIL": + fail_count += 1 + elif finding.status == "PASS": + pass_count += 1 + if fail_count + pass_count < 1: + print( + f"\n {Style.BRIGHT}There are no resources for {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL}.\n" + ) + else: + print( + f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" + ) + overview_table = [ + [ + f"{Fore.RED}{round(fail_count / (fail_count + pass_count) * 100, 2)}% ({fail_count}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(pass_count / (fail_count + pass_count) * 100, 2)}% ({pass_count}) PASS{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + if not compliance_overview: + print(f"\nDetailed results of {compliance_framework.upper()} are in:") + print( + f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n" + ) + except Exception as error: + logger.critical( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + sys.exit(1) diff --git a/prowler/lib/outputs/compliance/ens_rd2022_aws.py b/prowler/lib/outputs/compliance/ens_rd2022_aws.py new file mode 100644 index 0000000000..6b8759acf6 --- /dev/null +++ b/prowler/lib/outputs/compliance/ens_rd2022_aws.py @@ -0,0 +1,45 @@ +from csv import DictWriter + +from prowler.config.config import timestamp +from prowler.lib.outputs.models import Check_Output_CSV_ENS_RD2022, generate_csv_fields +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def write_compliance_row_ens_rd2022_aws( + file_descriptors, finding, compliance, output_options, audit_info +): + compliance_output = "ens_rd2022_aws" + csv_header = generate_csv_fields(Check_Output_CSV_ENS_RD2022) + csv_writer = DictWriter( + file_descriptors[compliance_output], + fieldnames=csv_header, + delimiter=";", + ) + for requirement in compliance.Requirements: + requirement_description = requirement.Description + requirement_id = requirement.Id + for attribute in requirement.Attributes: + compliance_row = Check_Output_CSV_ENS_RD2022( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + AccountId=audit_info.audited_account, + Region=finding.region, + AssessmentDate=outputs_unix_timestamp( + output_options.unix_timestamp, timestamp + ), + Requirements_Id=requirement_id, + Requirements_Description=requirement_description, + Requirements_Attributes_IdGrupoControl=attribute.IdGrupoControl, + Requirements_Attributes_Marco=attribute.Marco, + Requirements_Attributes_Categoria=attribute.Categoria, + Requirements_Attributes_DescripcionControl=attribute.DescripcionControl, + Requirements_Attributes_Nivel=attribute.Nivel, + Requirements_Attributes_Tipo=attribute.Tipo, + Requirements_Attributes_Dimensiones=",".join(attribute.Dimensiones), + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + CheckId=finding.check_metadata.CheckID, + ) + + csv_writer.writerow(compliance_row.__dict__) diff --git a/prowler/lib/outputs/compliance/generic.py b/prowler/lib/outputs/compliance/generic.py new file mode 100644 index 0000000000..f71621aa82 --- /dev/null +++ b/prowler/lib/outputs/compliance/generic.py @@ -0,0 +1,51 @@ +from csv import DictWriter + +from prowler.config.config import timestamp +from prowler.lib.outputs.models import ( + Check_Output_CSV_Generic_Compliance, + generate_csv_fields, +) +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def write_compliance_row_generic( + file_descriptors, finding, compliance, output_options, audit_info +): + compliance_output = compliance.Framework + if compliance.Version != "": + compliance_output += "_" + compliance.Version + if compliance.Provider != "": + compliance_output += "_" + compliance.Provider + + compliance_output = compliance_output.lower().replace("-", "_") + csv_header = generate_csv_fields(Check_Output_CSV_Generic_Compliance) + csv_writer = DictWriter( + file_descriptors[compliance_output], + fieldnames=csv_header, + delimiter=";", + ) + for requirement in compliance.Requirements: + requirement_description = requirement.Description + requirement_id = requirement.Id + for attribute in requirement.Attributes: + compliance_row = Check_Output_CSV_Generic_Compliance( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + AccountId=audit_info.audited_account, + Region=finding.region, + AssessmentDate=outputs_unix_timestamp( + output_options.unix_timestamp, timestamp + ), + Requirements_Id=requirement_id, + Requirements_Description=requirement_description, + Requirements_Attributes_Section=attribute.Section, + Requirements_Attributes_SubSection=attribute.SubSection, + Requirements_Attributes_SubGroup=attribute.SubGroup, + Requirements_Attributes_Service=attribute.Service, + Requirements_Attributes_Soc_Type=attribute.Soc_Type, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + CheckId=finding.check_metadata.CheckID, + ) + csv_writer.writerow(compliance_row.__dict__) diff --git a/prowler/lib/outputs/compliance/iso27001_2013_aws.py b/prowler/lib/outputs/compliance/iso27001_2013_aws.py new file mode 100644 index 0000000000..8b6f7c7030 --- /dev/null +++ b/prowler/lib/outputs/compliance/iso27001_2013_aws.py @@ -0,0 +1,53 @@ +from csv import DictWriter + +from prowler.config.config import timestamp +from prowler.lib.outputs.models import ( + Check_Output_CSV_AWS_ISO27001_2013, + generate_csv_fields, +) +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def write_compliance_row_iso27001_2013_aws( + file_descriptors, finding, compliance, output_options, audit_info +): + compliance_output = compliance.Framework + if compliance.Version != "": + compliance_output += "_" + compliance.Version + if compliance.Provider != "": + compliance_output += "_" + compliance.Provider + + compliance_output = compliance_output.lower().replace("-", "_") + csv_header = generate_csv_fields(Check_Output_CSV_AWS_ISO27001_2013) + csv_writer = DictWriter( + file_descriptors[compliance_output], + fieldnames=csv_header, + delimiter=";", + ) + for requirement in compliance.Requirements: + requirement_description = requirement.Description + requirement_id = requirement.Id + requirement_name = requirement.Name + for attribute in requirement.Attributes: + compliance_row = Check_Output_CSV_AWS_ISO27001_2013( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + AccountId=audit_info.audited_account, + Region=finding.region, + AssessmentDate=outputs_unix_timestamp( + output_options.unix_timestamp, timestamp + ), + Requirements_Id=requirement_id, + Requirements_Name=requirement_name, + Requirements_Description=requirement_description, + Requirements_Attributes_Category=attribute.Category, + Requirements_Attributes_Objetive_ID=attribute.Objetive_ID, + Requirements_Attributes_Objetive_Name=attribute.Objetive_Name, + Requirements_Attributes_Check_Summary=attribute.Check_Summary, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + CheckId=finding.check_metadata.CheckID, + ) + + csv_writer.writerow(compliance_row.__dict__) diff --git a/prowler/lib/outputs/compliance/mitre_attack_aws.py b/prowler/lib/outputs/compliance/mitre_attack_aws.py new file mode 100644 index 0000000000..cfffa62f7b --- /dev/null +++ b/prowler/lib/outputs/compliance/mitre_attack_aws.py @@ -0,0 +1,66 @@ +from csv import DictWriter + +from prowler.config.config import timestamp +from prowler.lib.outputs.models import ( + Check_Output_MITRE_ATTACK, + generate_csv_fields, + unroll_list, +) +from prowler.lib.utils.utils import outputs_unix_timestamp + + +def write_compliance_row_mitre_attack_aws( + file_descriptors, finding, compliance, output_options, audit_info +): + compliance_output = compliance.Framework + if compliance.Version != "": + compliance_output += "_" + compliance.Version + if compliance.Provider != "": + compliance_output += "_" + compliance.Provider + + compliance_output = compliance_output.lower().replace("-", "_") + csv_header = generate_csv_fields(Check_Output_MITRE_ATTACK) + csv_writer = DictWriter( + file_descriptors[compliance_output], + fieldnames=csv_header, + delimiter=";", + ) + for requirement in compliance.Requirements: + requirement_description = requirement.Description + requirement_id = requirement.Id + requirement_name = requirement.Name + attributes_aws_services = "" + attributes_categories = "" + attributes_values = "" + attributes_comments = "" + for attribute in requirement.Attributes: + attributes_aws_services += attribute.AWSService + "\n" + attributes_categories += attribute.Category + "\n" + attributes_values += attribute.Value + "\n" + attributes_comments += attribute.Comment + "\n" + compliance_row = Check_Output_MITRE_ATTACK( + Provider=finding.check_metadata.Provider, + Description=compliance.Description, + AccountId=audit_info.audited_account, + Region=finding.region, + AssessmentDate=outputs_unix_timestamp( + output_options.unix_timestamp, timestamp + ), + Requirements_Id=requirement_id, + Requirements_Description=requirement_description, + Requirements_Name=requirement_name, + Requirements_Tactics=unroll_list(requirement.Tactics), + Requirements_SubTechniques=unroll_list(requirement.SubTechniques), + Requirements_Platforms=unroll_list(requirement.Platforms), + Requirements_TechniqueURL=requirement.TechniqueURL, + Requirements_Attributes_AWSServices=attributes_aws_services, + Requirements_Attributes_Categories=attributes_categories, + Requirements_Attributes_Values=attributes_values, + Requirements_Attributes_Comments=attributes_comments, + Status=finding.status, + StatusExtended=finding.status_extended, + ResourceId=finding.resource_id, + CheckId=finding.check_metadata.CheckID, + ) + + csv_writer.writerow(compliance_row.__dict__) diff --git a/prowler/lib/outputs/csv.py b/prowler/lib/outputs/csv.py new file mode 100644 index 0000000000..c3ebfd7e33 --- /dev/null +++ b/prowler/lib/outputs/csv.py @@ -0,0 +1,10 @@ +from csv import DictWriter + + +def write_csv(file_descriptor, headers, row): + csv_writer = DictWriter( + file_descriptor, + fieldnames=headers, + delimiter=";", + ) + csv_writer.writerow(row.__dict__) diff --git a/prowler/lib/outputs/file_descriptors.py b/prowler/lib/outputs/file_descriptors.py index 9b5def4d22..1e3a0d0b9c 100644 --- a/prowler/lib/outputs/file_descriptors.py +++ b/prowler/lib/outputs/file_descriptors.py @@ -23,6 +23,7 @@ from prowler.lib.outputs.models import ( ) from prowler.lib.utils.utils import file_exists, open_file from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info +from prowler.providers.azure.lib.audit_info.models import Azure_Audit_Info from prowler.providers.common.outputs import get_provider_output_model from prowler.providers.gcp.lib.audit_info.models import GCP_Audit_Info @@ -108,7 +109,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit elif isinstance(audit_info, GCP_Audit_Info): if output_mode == "cis_2.0_gcp": - filename = f"{output_directory}/{output_filename}_cis_2.0_gcp{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_cis_2.0_gcp{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, audit_info, Check_Output_CSV_GCP_CIS ) @@ -123,7 +124,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit file_descriptors.update({output_mode: file_descriptor}) elif output_mode == "ens_rd2022_aws": - filename = f"{output_directory}/{output_filename}_ens_rd2022_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_ens_rd2022_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, @@ -133,14 +134,14 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit file_descriptors.update({output_mode: file_descriptor}) elif output_mode == "cis_1.5_aws": - filename = f"{output_directory}/{output_filename}_cis_1.5_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_cis_1.5_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, audit_info, Check_Output_CSV_AWS_CIS ) file_descriptors.update({output_mode: file_descriptor}) elif output_mode == "cis_1.4_aws": - filename = f"{output_directory}/{output_filename}_cis_1.4_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_cis_1.4_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, audit_info, Check_Output_CSV_AWS_CIS ) @@ -150,7 +151,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit output_mode == "aws_well_architected_framework_security_pillar_aws" ): - filename = f"{output_directory}/{output_filename}_aws_well_architected_framework_security_pillar_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_aws_well_architected_framework_security_pillar_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, @@ -163,7 +164,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit output_mode == "aws_well_architected_framework_reliability_pillar_aws" ): - filename = f"{output_directory}/{output_filename}_aws_well_architected_framework_reliability_pillar_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_aws_well_architected_framework_reliability_pillar_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, @@ -173,7 +174,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit file_descriptors.update({output_mode: file_descriptor}) elif output_mode == "iso27001_2013_aws": - filename = f"{output_directory}/{output_filename}_iso27001_2013_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_iso27001_2013_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, @@ -183,7 +184,7 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit file_descriptors.update({output_mode: file_descriptor}) elif output_mode == "mitre_attack_aws": - filename = f"{output_directory}/{output_filename}_mitre_attack_aws{csv_file_suffix}" + filename = f"{output_directory}/compliance/{output_filename}_mitre_attack_aws{csv_file_suffix}" file_descriptor = initialize_file_descriptor( filename, output_mode, @@ -194,14 +195,26 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, audit else: # Generic Compliance framework - filename = f"{output_directory}/{output_filename}_{output_mode}{csv_file_suffix}" - file_descriptor = initialize_file_descriptor( - filename, - output_mode, - audit_info, - Check_Output_CSV_Generic_Compliance, - ) - file_descriptors.update({output_mode: file_descriptor}) + if ( + isinstance(audit_info, AWS_Audit_Info) + and "aws" in output_mode + or ( + isinstance(audit_info, Azure_Audit_Info) + and "azure" in output_mode + ) + or ( + isinstance(audit_info, GCP_Audit_Info) + and "gcp" in output_mode + ) + ): + filename = f"{output_directory}/compliance/{output_filename}_{output_mode}{csv_file_suffix}" + file_descriptor = initialize_file_descriptor( + filename, + output_mode, + audit_info, + Check_Output_CSV_Generic_Compliance, + ) + file_descriptors.update({output_mode: file_descriptor}) except Exception as error: logger.error( diff --git a/prowler/lib/outputs/models.py b/prowler/lib/outputs/models.py index 09f42b6e12..faddd07f54 100644 --- a/prowler/lib/outputs/models.py +++ b/prowler/lib/outputs/models.py @@ -13,7 +13,16 @@ from prowler.lib.utils.utils import outputs_unix_timestamp from prowler.providers.aws.lib.audit_info.models import AWS_Organizations_Info -def get_check_compliance(finding, provider, output_options): +def get_check_compliance(finding, provider, output_options) -> dict: + """get_check_compliance returns a map with the compliance framework as key and the requirements where the finding's check is present. + + Example: + + { + "CIS-1.4": ["2.1.3"], + "CIS-1.5": ["2.1.3"], + } + """ try: check_compliance = {} # We have to retrieve all the check's compliance requirements diff --git a/prowler/lib/outputs/outputs.py b/prowler/lib/outputs/outputs.py index 288a58152c..f0666516aa 100644 --- a/prowler/lib/outputs/outputs.py +++ b/prowler/lib/outputs/outputs.py @@ -4,7 +4,10 @@ from colorama import Fore, Style from prowler.config.config import available_compliance_frameworks, orange_color from prowler.lib.logger import logger -from prowler.lib.outputs.compliance import add_manual_controls, fill_compliance +from prowler.lib.outputs.compliance.compliance import ( + add_manual_controls, + fill_compliance, +) from prowler.lib.outputs.file_descriptors import fill_file_descriptors from prowler.lib.outputs.html import fill_html from prowler.lib.outputs.json import fill_json_asff, fill_json_ocsf @@ -63,22 +66,26 @@ def report(check_findings, output_options, audit_info): if file_descriptors: # Check if --quiet to only add fails to outputs if not (finding.status != "FAIL" and output_options.is_quiet): - if any( - compliance in output_options.output_modes - for compliance in available_compliance_frameworks - ): - fill_compliance( - output_options, - finding, - audit_info, - file_descriptors, + input_compliance_frameworks = list( + set(output_options.output_modes).intersection( + available_compliance_frameworks ) + ) - add_manual_controls( - output_options, - audit_info, - file_descriptors, - ) + fill_compliance( + output_options, + finding, + audit_info, + file_descriptors, + input_compliance_frameworks, + ) + + add_manual_controls( + output_options, + audit_info, + file_descriptors, + input_compliance_frameworks, + ) # AWS specific outputs if finding.check_metadata.Provider == "aws": diff --git a/prowler/providers/common/outputs.py b/prowler/providers/common/outputs.py index 65acbfa92b..4541e1cd0f 100644 --- a/prowler/providers/common/outputs.py +++ b/prowler/providers/common/outputs.py @@ -72,7 +72,6 @@ class Provider_Output_Options: # exist_ok is set to True not to raise FileExistsError makedirs(arguments.output_directory, exist_ok=True) - class Azure_Output_Options(Provider_Output_Options): def __init__(self, arguments, audit_info, mutelist_file, bulk_checks_metadata): # First call Provider_Output_Options init diff --git a/tests/lib/outputs/compliance/compliance_test.py b/tests/lib/outputs/compliance/compliance_test.py new file mode 100644 index 0000000000..6be964a8fa --- /dev/null +++ b/tests/lib/outputs/compliance/compliance_test.py @@ -0,0 +1,111 @@ +from mock import MagicMock + +from prowler.lib.check.compliance_models import ( + CIS_Requirement_Attribute, + Compliance_Base_Model, + Compliance_Requirement, +) +from prowler.lib.outputs.compliance.compliance import ( + get_check_compliance_frameworks_in_input, +) + +CIS_1_4_AWS_NAME = "cis_1.4_aws" +CIS_1_4_AWS = Compliance_Base_Model( + Framework="CIS", + Provider="AWS", + Version="1.4", + Description="The CIS Benchmark for CIS Amazon Web Services Foundations Benchmark, v1.4.0, Level 1 and 2 provides prescriptive guidance for configuring security options for a subset of Amazon Web Services. It has an emphasis on foundational, testable, and architecture agnostic settings", + Requirements=[ + Compliance_Requirement( + Checks=[], + Id="2.1.3", + Description="Ensure MFA Delete is enabled on S3 buckets", + Attributes=[ + CIS_Requirement_Attribute( + Section="2.1. Simple Storage Service (S3)", + Profile="Level 1", + AssessmentStatus="Automated", + Description="Once MFA Delete is enabled on your sensitive and classified S3 bucket it requires the user to have two forms of authentication.", + RationaleStatement="Adding MFA delete to an S3 bucket, requires additional authentication when you change the version state of your bucket or you delete and object version adding another layer of security in the event your security credentials are compromised or unauthorized access is granted.", + ImpactStatement="", + RemediationProcedure="Perform the steps below to enable MFA delete on an S3 bucket.\n\nNote:\n-You cannot enable MFA Delete using the AWS Management Console. You must use the AWS CLI or API.\n-You must use your 'root' account to enable MFA Delete on S3 buckets.\n\n**From Command line:**\n\n1. Run the s3api put-bucket-versioning command\n\n```\naws s3api put-bucket-versioning --profile my-root-profile --bucket Bucket_Name --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa “arn:aws:iam::aws_account_id:mfa/root-account-mfa-device passcode”\n```", + AuditProcedure='Perform the steps below to confirm MFA delete is configured on an S3 Bucket\n\n**From Console:**\n\n1. Login to the S3 console at `https://console.aws.amazon.com/s3/`\n\n2. Click the `Check` box next to the Bucket name you want to confirm\n\n3. In the window under `Properties`\n\n4. Confirm that Versioning is `Enabled`\n\n5. Confirm that MFA Delete is `Enabled`\n\n**From Command Line:**\n\n1. Run the `get-bucket-versioning`\n```\naws s3api get-bucket-versioning --bucket my-bucket\n```\n\nOutput example:\n```\n \n Enabled\n Enabled \n\n```\n\nIf the Console or the CLI output does not show Versioning and MFA Delete `enabled` refer to the remediation below.', + AdditionalInformation="", + References="https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html#MultiFactorAuthenticationDelete:https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMFADelete.html:https://aws.amazon.com/blogs/security/securing-access-to-aws-using-mfa-part-3/:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_lost-or-broken.html", + ) + ], + ) + ], +) +CIS_1_5_AWS_NAME = "cis_1.5_aws" +CIS_1_5_AWS = Compliance_Base_Model( + Framework="CIS", + Provider="AWS", + Version="1.5", + Description="The CIS Amazon Web Services Foundations Benchmark provides prescriptive guidance for configuring security options for a subset of Amazon Web Services with an emphasis on foundational, testable, and architecture agnostic settings.", + Requirements=[ + Compliance_Requirement( + Checks=[], + Id="2.1.3", + Description="Ensure MFA Delete is enabled on S3 buckets", + Attributes=[ + CIS_Requirement_Attribute( + Section="2.1. Simple Storage Service (S3)", + Profile="Level 1", + AssessmentStatus="Automated", + Description="Once MFA Delete is enabled on your sensitive and classified S3 bucket it requires the user to have two forms of authentication.", + RationaleStatement="Adding MFA delete to an S3 bucket, requires additional authentication when you change the version state of your bucket or you delete and object version adding another layer of security in the event your security credentials are compromised or unauthorized access is granted.", + ImpactStatement="", + RemediationProcedure="Perform the steps below to enable MFA delete on an S3 bucket.\n\nNote:\n-You cannot enable MFA Delete using the AWS Management Console. You must use the AWS CLI or API.\n-You must use your 'root' account to enable MFA Delete on S3 buckets.\n\n**From Command line:**\n\n1. Run the s3api put-bucket-versioning command\n\n```\naws s3api put-bucket-versioning --profile my-root-profile --bucket Bucket_Name --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa “arn:aws:iam::aws_account_id:mfa/root-account-mfa-device passcode”\n```", + AuditProcedure='Perform the steps below to confirm MFA delete is configured on an S3 Bucket\n\n**From Console:**\n\n1. Login to the S3 console at `https://console.aws.amazon.com/s3/`\n\n2. Click the `Check` box next to the Bucket name you want to confirm\n\n3. In the window under `Properties`\n\n4. Confirm that Versioning is `Enabled`\n\n5. Confirm that MFA Delete is `Enabled`\n\n**From Command Line:**\n\n1. Run the `get-bucket-versioning`\n```\naws s3api get-bucket-versioning --bucket my-bucket\n```\n\nOutput example:\n```\n \n Enabled\n Enabled \n\n```\n\nIf the Console or the CLI output does not show Versioning and MFA Delete `enabled` refer to the remediation below.', + AdditionalInformation="", + References="https://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html#MultiFactorAuthenticationDelete:https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMFADelete.html:https://aws.amazon.com/blogs/security/securing-access-to-aws-using-mfa-part-3/:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_lost-or-broken.html", + ) + ], + ) + ], +) + +NOT_PRESENT_COMPLIANCE_NAME = "not_present_compliance_name" +NOT_PRESENT_COMPLIANCE = Compliance_Base_Model( + Framework="NOT_EXISTENT", + Provider="NOT_EXISTENT", + Version="NOT_EXISTENT", + Description="NOT_EXISTENT", + Requirements=[], +) + + +class TestCompliance: + def test_get_check_compliance_frameworks_all_none(self): + check_id = None + bulk_checks_metadata = None + input_compliance_frameworks = None + assert ( + get_check_compliance_frameworks_in_input( + check_id, bulk_checks_metadata, input_compliance_frameworks + ) + == [] + ) + + def test_get_check_compliance_frameworks_all(self): + check_id = "test-check" + bulk_check_metadata = [CIS_1_4_AWS, CIS_1_5_AWS] + bulk_checks_metadata = {} + bulk_checks_metadata[check_id] = MagicMock() + bulk_checks_metadata[check_id].Compliance = bulk_check_metadata + input_compliance_frameworks = [CIS_1_4_AWS_NAME, CIS_1_5_AWS_NAME] + assert get_check_compliance_frameworks_in_input( + check_id, bulk_checks_metadata, input_compliance_frameworks + ) == [CIS_1_4_AWS, CIS_1_5_AWS] + + def test_get_check_compliance_frameworks_two_of_three(self): + check_id = "test-check" + bulk_check_metadata = [CIS_1_4_AWS, CIS_1_5_AWS, NOT_PRESENT_COMPLIANCE] + bulk_checks_metadata = {} + bulk_checks_metadata[check_id] = MagicMock() + bulk_checks_metadata[check_id].Compliance = bulk_check_metadata + input_compliance_frameworks = [CIS_1_4_AWS_NAME, CIS_1_5_AWS_NAME] + assert get_check_compliance_frameworks_in_input( + check_id, bulk_checks_metadata, input_compliance_frameworks + ) == [CIS_1_4_AWS, CIS_1_5_AWS]