From ca6506ea4c2640eadd171a173691c7773a8a84fe Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 1 Apr 2024 18:34:49 +0200 Subject: [PATCH 01/17] feat(fixer): add fixer flag --- prowler/__main__.py | 21 ++++++++++++++---- prowler/lib/check/check.py | 22 +++++++++++++++++-- .../providers/aws/lib/arguments/arguments.py | 8 +++++++ .../ec2_ebs_default_encryption.py | 3 +++ .../providers/aws/services/ec2/ec2_service.py | 12 ++++++++++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 712e6ec363..8470592790 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -73,8 +73,14 @@ def prowler(): # We treat the compliance framework as another output format if compliance_framework: args.output_formats.extend(compliance_framework) - # If no input compliance framework, set all - else: + # If no input compliance framework, set all, unless a specific service or check is input + elif ( + not checks + and not services + and not categories + and not excluded_checks + and not excluded_services + ): args.output_formats.extend(get_available_compliance_frameworks(provider)) # Set Logger configuration @@ -193,7 +199,7 @@ def prowler(): global_provider, custom_checks_metadata, getattr(args, "mutelist_file", None), - args.config_file, + args, ) else: logger.error( @@ -305,7 +311,14 @@ def prowler(): global_provider.output_options, ) - if findings: + if ( + findings + and not checks + and not services + and not categories + and not excluded_checks + and not excluded_services + ): compliance_overview = False if not compliance_framework: compliance_framework = get_available_compliance_frameworks(provider) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index f442400e30..6c8b871163 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -424,7 +424,7 @@ def execute_checks( global_provider: Any, custom_checks_metadata: Any, mutelist_file: str, - config_file: str, + args: Any, ) -> list: # List to store all the check's findings all_findings = [] @@ -476,6 +476,7 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, + args ) all_findings.extend(check_findings) @@ -491,7 +492,7 @@ def execute_checks( else: # Prepare your messages messages = [ - f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{config_file}{Style.RESET_ALL}" + f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{args.config_file}{Style.RESET_ALL}" ] if mutelist_file: messages.append( @@ -536,6 +537,7 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, + args, ) all_findings.extend(check_findings) @@ -562,6 +564,7 @@ def execute( services_executed: set, checks_executed: set, custom_checks_metadata: Any, + args: Any, ): try: # Import check module @@ -595,6 +598,21 @@ def execute( # Report the check's findings report(check_findings, global_provider) + # Prowler Fixer + if args.fix and args.check: + try: + fixer = getattr(c, "fixer") + # Check if there are any FAIL findings + if any("FAIL" in finding.status for finding in check_findings): + print(f"Fixing fails for check {Fore.YELLOW}{check_name}{Style.RESET_ALL}...\n") + for finding in check_findings: + if finding.status == "FAIL": + print(f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {Fore.GREEN + "DONE" if fixer(finding.region) else Fore.RED + "ERROR"}{Style.RESET_ALL}\n") + except AttributeError: + logger.error( + f"Fixer method not implemented for check {check_name}" + ) + if os.environ.get("PROWLER_REPORT_LIB_PATH"): try: logger.info("Using custom report interface ...") diff --git a/prowler/providers/aws/lib/arguments/arguments.py b/prowler/providers/aws/lib/arguments/arguments.py index d3be9400d2..ab972e336a 100644 --- a/prowler/providers/aws/lib/arguments/arguments.py +++ b/prowler/providers/aws/lib/arguments/arguments.py @@ -159,6 +159,14 @@ def init_parser(self): help="Scan unused services", ) + # Prowler Fixer + prowler_fixer_subparser = aws_parser.add_argument_group("Prowler Fixer") + prowler_fixer_subparser.add_argument( + "--fix", + action="store_true", + help="Fix the failed findings that can be fixed by Prowler", + ) + def validate_session_duration(duration): """validate_session_duration validates that the AWS STS Assume Role Session Duration is between 900 and 43200 seconds.""" diff --git a/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py b/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py index 8ef98bc92c..ce20785441 100644 --- a/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py +++ b/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py @@ -22,3 +22,6 @@ def execute(self): findings.append(report) return findings + + def fixer(self, region): + return ec2_client.__enable_ebs_encryption_by_default__(region) diff --git a/prowler/providers/aws/services/ec2/ec2_service.py b/prowler/providers/aws/services/ec2/ec2_service.py index e9e9865017..ed23b6890b 100644 --- a/prowler/providers/aws/services/ec2/ec2_service.py +++ b/prowler/providers/aws/services/ec2/ec2_service.py @@ -410,6 +410,18 @@ def __get_ebs_encryption_settings__(self, regional_client): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __enable_ebs_encryption_by_default__(self, region): + try: + regional_client = self.regional_clients[region] + return regional_client.enable_ebs_encryption_by_default()[ + "EbsEncryptionByDefault" + ] + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False + class Instance(BaseModel): id: str From 9d47288bf376a7b701464d1bbba5e0aa302a05d4 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 09:43:54 +0200 Subject: [PATCH 02/17] solve comments --- prowler/lib/check/check.py | 36 +++++++++++++++++++++++------------- tests/lib/cli/parser_test.py | 6 ++++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 6c8b871163..918314842d 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -419,6 +419,27 @@ def run_check(check: Check, output_options) -> list: return findings +def run_fixer(check_findings, check_name, c): + try: + fixer = getattr(c, "fixer") + # Check if there are any FAIL findings + if any("FAIL" in finding.status for finding in check_findings): + print( + f"Fixing fails for check {Fore.YELLOW}{check_name}{Style.RESET_ALL}...\n" + ) + for finding in check_findings: + if finding.status == "FAIL": + print( + f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}\n" + ) + except AttributeError: + logger.error(f"Fixer method not implemented for check {check_name}") + except Exception as error: + logger.error( + f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def execute_checks( checks_to_execute: list, global_provider: Any, @@ -476,7 +497,7 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, - args + args, ) all_findings.extend(check_findings) @@ -600,18 +621,7 @@ def execute( # Prowler Fixer if args.fix and args.check: - try: - fixer = getattr(c, "fixer") - # Check if there are any FAIL findings - if any("FAIL" in finding.status for finding in check_findings): - print(f"Fixing fails for check {Fore.YELLOW}{check_name}{Style.RESET_ALL}...\n") - for finding in check_findings: - if finding.status == "FAIL": - print(f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {Fore.GREEN + "DONE" if fixer(finding.region) else Fore.RED + "ERROR"}{Style.RESET_ALL}\n") - except AttributeError: - logger.error( - f"Fixer method not implemented for check {check_name}" - ) + run_fixer(check_findings, check_name, c) if os.environ.get("PROWLER_REPORT_LIB_PATH"): try: diff --git a/tests/lib/cli/parser_test.py b/tests/lib/cli/parser_test.py index da66e9b883..0de4032ead 100644 --- a/tests/lib/cli/parser_test.py +++ b/tests/lib/cli/parser_test.py @@ -1048,6 +1048,12 @@ def test_aws_parser_scan_unused_services(self): parsed = self.parser.parse(command) assert parsed.scan_unused_services + def test_aws_parser_fixer(self): + argument = "--fix" + command = [prowler_command, argument] + parsed = self.parser.parse(command) + assert parsed.fix + def test_aws_parser_config_file(self): argument = "--config-file" config_file = "./test-config.yaml" From 5e18a82e53a3324d7200aea259c58fcde4e42873 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 09:49:19 +0200 Subject: [PATCH 03/17] add changes to other PR --- prowler/__main__.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 8470592790..f441ce6620 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -73,14 +73,8 @@ def prowler(): # We treat the compliance framework as another output format if compliance_framework: args.output_formats.extend(compliance_framework) - # If no input compliance framework, set all, unless a specific service or check is input - elif ( - not checks - and not services - and not categories - and not excluded_checks - and not excluded_services - ): + # If no input compliance framework, set all + else: args.output_formats.extend(get_available_compliance_frameworks(provider)) # Set Logger configuration @@ -311,14 +305,7 @@ def prowler(): global_provider.output_options, ) - if ( - findings - and not checks - and not services - and not categories - and not excluded_checks - and not excluded_services - ): + if findings: compliance_overview = False if not compliance_framework: compliance_framework = get_available_compliance_frameworks(provider) From 059ba69d2b056ff04eaf19829f3d91dc95fd1da6 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 11:06:02 +0200 Subject: [PATCH 04/17] solve comments --- prowler/lib/check/check.py | 33 +++++++++++++++---- .../aws/services/ec2/ec2_service_test.py | 16 +++++++++ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 918314842d..c5d8e3728d 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -399,6 +399,14 @@ def import_check(check_path: str) -> ModuleType: def run_check(check: Check, output_options) -> list: + """ + Run the check and return the findings + Args: + check (Check): check class + output_options (Any): output options + Returns: + list: list of findings + """ findings = [] if output_options.verbose: print( @@ -419,9 +427,16 @@ def run_check(check: Check, output_options) -> list: return findings -def run_fixer(check_findings, check_name, c): +def run_fixer(check_findings: list, check_name: str, check_class: Check): + """ + Run the fixer for the check if it exists and there are any FAIL findings + Args: + check_findings (list): list of findings + check_name (str): check name + check_class (Check): check class + """ try: - fixer = getattr(c, "fixer") + fixer = getattr(check_class, "fixer") # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in check_findings): print( @@ -593,14 +608,18 @@ def execute( lib = import_check(check_module_path) # Recover functions from check check_to_execute = getattr(lib, check_name) - c = check_to_execute() + check_class = check_to_execute() # Update check metadata to reflect that in the outputs - if custom_checks_metadata and custom_checks_metadata["Checks"].get(c.CheckID): - c = update_check_metadata(c, custom_checks_metadata["Checks"][c.CheckID]) + if custom_checks_metadata and custom_checks_metadata["Checks"].get( + check_class.CheckID + ): + check_class = update_check_metadata( + check_class, custom_checks_metadata["Checks"][check_class.CheckID] + ) # Run check - check_findings = run_check(c, global_provider.output_options) + check_findings = run_check(check_class, global_provider.output_options) # Update Audit Status services_executed.add(service) @@ -621,7 +640,7 @@ def execute( # Prowler Fixer if args.fix and args.check: - run_fixer(check_findings, check_name, c) + run_fixer(check_findings, check_name, check_class) if os.environ.get("PROWLER_REPORT_LIB_PATH"): try: diff --git a/tests/providers/aws/services/ec2/ec2_service_test.py b/tests/providers/aws/services/ec2/ec2_service_test.py index 936bcf5beb..bf1f825aaa 100644 --- a/tests/providers/aws/services/ec2/ec2_service_test.py +++ b/tests/providers/aws/services/ec2/ec2_service_test.py @@ -544,3 +544,19 @@ def test__describe_volumes__(self): assert ec2.volumes[0].tags == [ {"Key": "test", "Value": "test"}, ] + + # Test EC2 EBS Enabling Encryption by Default + @mock_aws + def test__describe_ebs_encryption_by_default__(self): + # Generate EC2 Client + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + + # EC2 client for this test class + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + ec2 = EC2(aws_provider) + + assert not ec2.__enable_ebs_encryption_by_default__() + ec2_client.enable_ebs_encryption_by_default() + assert ec2.__enable_ebs_encryption_by_default__() From aeae83bb4fb7027b9b9f351cdf6f5cfb9a48d6d3 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 11:24:37 +0200 Subject: [PATCH 05/17] solve comments --- prowler/lib/check/check.py | 11 +++++------ tests/providers/aws/services/ec2/ec2_service_test.py | 7 +------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index c5d8e3728d..97111c7244 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -427,12 +427,11 @@ def run_check(check: Check, output_options) -> list: return findings -def run_fixer(check_findings: list, check_name: str, check_class: Check): +def run_fixer(check_findings: list, check_class: Check): """ Run the fixer for the check if it exists and there are any FAIL findings Args: check_findings (list): list of findings - check_name (str): check name check_class (Check): check class """ try: @@ -440,7 +439,7 @@ def run_fixer(check_findings: list, check_name: str, check_class: Check): # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in check_findings): print( - f"Fixing fails for check {Fore.YELLOW}{check_name}{Style.RESET_ALL}...\n" + f"Fixing fails for check {Fore.YELLOW}{check_class.CheckID}{Style.RESET_ALL}...\n" ) for finding in check_findings: if finding.status == "FAIL": @@ -448,10 +447,10 @@ def run_fixer(check_findings: list, check_name: str, check_class: Check): f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}\n" ) except AttributeError: - logger.error(f"Fixer method not implemented for check {check_name}") + logger.error(f"Fixer method not implemented for check {check_class.CheckID}") except Exception as error: logger.error( - f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + f"{check_class.CheckID} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) @@ -640,7 +639,7 @@ def execute( # Prowler Fixer if args.fix and args.check: - run_fixer(check_findings, check_name, check_class) + run_fixer(check_findings, check_class) if os.environ.get("PROWLER_REPORT_LIB_PATH"): try: diff --git a/tests/providers/aws/services/ec2/ec2_service_test.py b/tests/providers/aws/services/ec2/ec2_service_test.py index bf1f825aaa..01d3b8ee2d 100644 --- a/tests/providers/aws/services/ec2/ec2_service_test.py +++ b/tests/providers/aws/services/ec2/ec2_service_test.py @@ -548,15 +548,10 @@ def test__describe_volumes__(self): # Test EC2 EBS Enabling Encryption by Default @mock_aws def test__describe_ebs_encryption_by_default__(self): - # Generate EC2 Client - ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) - # EC2 client for this test class aws_provider = set_mocked_aws_provider( [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] ) ec2 = EC2(aws_provider) - assert not ec2.__enable_ebs_encryption_by_default__() - ec2_client.enable_ebs_encryption_by_default() - assert ec2.__enable_ebs_encryption_by_default__() + assert ec2.__enable_ebs_encryption_by_default__(region=AWS_REGION_US_EAST_1) From cdd1a9b3757c59332a968b85b210920ae0c53c41 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 12:00:11 +0200 Subject: [PATCH 06/17] exit when fixing --- prowler/lib/check/check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 97111c7244..e88ed383b3 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -446,6 +446,7 @@ def run_fixer(check_findings: list, check_class: Check): print( f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}\n" ) + sys.exit() except AttributeError: logger.error(f"Fixer method not implemented for check {check_class.CheckID}") except Exception as error: From 6fdec63aca3997d2d6f361048ad47aa2fffefd3f Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 13:18:59 +0200 Subject: [PATCH 07/17] execute fix after scan --- prowler/__main__.py | 9 +++++++ prowler/lib/check/check.py | 55 +++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 2cf6b281f1..68cd36fe89 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -24,6 +24,7 @@ print_compliance_requirements, print_services, remove_custom_checks_module, + run_fixer, ) from prowler.lib.check.checks_loader import load_checks_to_execute from prowler.lib.check.compliance import update_checks_metadata_with_compliance @@ -210,6 +211,14 @@ def prowler(): "There are no checks to execute. Please, check your input arguments" ) + # Prowler Fixer + if args.fix: + # Check if there are any FAIL findings + if any("FAIL" in finding.status for finding in findings): + run_fixer(findings) + else: + print(f"{Style.BRIGHT}{Fore.GREEN}\nNo findings to fix!{Style.RESET_ALL}") + sys.exit() # Extract findings stats stats = extract_findings_statistics(findings) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index e88ed383b3..f59d51b017 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -427,32 +427,34 @@ def run_check(check: Check, output_options) -> list: return findings -def run_fixer(check_findings: list, check_class: Check): +def run_fixer(check_findings: list): """ Run the fixer for the check if it exists and there are any FAIL findings Args: check_findings (list): list of findings - check_class (Check): check class """ - try: - fixer = getattr(check_class, "fixer") - # Check if there are any FAIL findings - if any("FAIL" in finding.status for finding in check_findings): - print( - f"Fixing fails for check {Fore.YELLOW}{check_class.CheckID}{Style.RESET_ALL}...\n" - ) - for finding in check_findings: - if finding.status == "FAIL": - print( - f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}\n" - ) - sys.exit() - except AttributeError: - logger.error(f"Fixer method not implemented for check {check_class.CheckID}") - except Exception as error: - logger.error( - f"{check_class.CheckID} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) + for finding in check_findings: + if finding.status == "FAIL": + try: + check_module_path = f"prowler.providers.{finding.check_metadata.Provider}.services.{finding.check_metadata.ServiceName}.{finding.check_metadata.CheckID}.{finding.check_metadata.CheckID}" + lib = import_check(check_module_path) + check_to_fix = getattr(lib, finding.check_metadata.CheckID) + check_class = check_to_fix() + fixer = getattr(check_class, "fixer") + print( + f"\n{orange_color}FIXING{Style.RESET_ALL}{Fore.YELLOW} {finding.check_metadata.CheckID}{Style.RESET_ALL} in {finding.region}..." + ) + print( + f"{(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}" + ) + except AttributeError: + logger.error( + f"Fixer method not implemented for check {finding.check_metadata.CheckID}" + ) + except Exception as error: + logger.error( + f"{finding.check_metadata.CheckID} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) def execute_checks( @@ -460,7 +462,7 @@ def execute_checks( global_provider: Any, custom_checks_metadata: Any, mutelist_file: str, - args: Any, + config_file: str, ) -> list: # List to store all the check's findings all_findings = [] @@ -512,7 +514,6 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, - args, ) all_findings.extend(check_findings) @@ -528,7 +529,7 @@ def execute_checks( else: # Prepare your messages messages = [ - f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{args.config_file}{Style.RESET_ALL}" + f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{config_file}{Style.RESET_ALL}" ] if mutelist_file: messages.append( @@ -573,7 +574,6 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, - args, ) all_findings.extend(check_findings) @@ -600,7 +600,6 @@ def execute( services_executed: set, checks_executed: set, custom_checks_metadata: Any, - args: Any, ): try: # Import check module @@ -638,10 +637,6 @@ def execute( # Report the check's findings report(check_findings, global_provider) - # Prowler Fixer - if args.fix and args.check: - run_fixer(check_findings, check_class) - if os.environ.get("PROWLER_REPORT_LIB_PATH"): try: logger.info("Using custom report interface ...") From 387ba70a07a4bebbe2d9d7b9d48299c76dc7ef68 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 13:20:18 +0200 Subject: [PATCH 08/17] solve comments --- prowler/__main__.py | 2 +- prowler/providers/aws/aws_provider.py | 4 +++- prowler/providers/azure/azure_provider.py | 4 +++- prowler/providers/gcp/gcp_provider.py | 4 +++- prowler/providers/kubernetes/kubernetes_provider.py | 4 +++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 68cd36fe89..812db5ed93 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -204,7 +204,7 @@ def prowler(): global_provider, custom_checks_metadata, getattr(args, "mutelist_file", None), - args, + args.config_file, ) else: logger.error( diff --git a/prowler/providers/aws/aws_provider.py b/prowler/providers/aws/aws_provider.py index d2bfa2065a..6f619f11c6 100644 --- a/prowler/providers/aws/aws_provider.py +++ b/prowler/providers/aws/aws_provider.py @@ -507,7 +507,9 @@ def print_credentials(self): report_lines.append( f"Assumed Role ARN: {Fore.YELLOW}[{self._assumed_role.info.role_arn.arn}]{Style.RESET_ALL}" ) - report_title = f"{Style.BRIGHT}Prowler is using the AWS credentials below:{Style.RESET_ALL}" + report_title = ( + f"{Style.BRIGHT}Using the AWS credentials below:{Style.RESET_ALL}" + ) print_boxes(report_lines, report_title) def generate_regional_clients( diff --git a/prowler/providers/azure/azure_provider.py b/prowler/providers/azure/azure_provider.py index c9aefea985..91ecf08f41 100644 --- a/prowler/providers/azure/azure_provider.py +++ b/prowler/providers/azure/azure_provider.py @@ -182,7 +182,9 @@ def print_credentials(self): f"{Style.BRIGHT}Azure Subscriptions:{Style.RESET_ALL} {Fore.YELLOW}{printed_subscriptions}{Style.RESET_ALL}", f"{Style.BRIGHT}Azure Identity Type:{Style.RESET_ALL} {Fore.YELLOW}{self._identity.identity_type}{Style.RESET_ALL} {Style.BRIGHT}Azure Identity ID:{Style.RESET_ALL} {Fore.YELLOW}{self._identity.identity_id}{Style.RESET_ALL}", ] - report_title = f"{Style.BRIGHT}Prowler is using the Azure credentials below:{Style.RESET_ALL}" + report_title = ( + f"{Style.BRIGHT}Using the Azure credentials below:{Style.RESET_ALL}" + ) print_boxes(report_lines, report_title) # TODO: setup_session or setup_credentials? diff --git a/prowler/providers/gcp/gcp_provider.py b/prowler/providers/gcp/gcp_provider.py index 4cd718501f..bdddbcb5b0 100644 --- a/prowler/providers/gcp/gcp_provider.py +++ b/prowler/providers/gcp/gcp_provider.py @@ -172,7 +172,9 @@ def print_credentials(self): f"{Style.BRIGHT}GCP Account:{Style.RESET_ALL} {Fore.YELLOW}{self.identity.profile}{Style.RESET_ALL}", f"{Style.BRIGHT}GCP Project IDs:{Style.RESET_ALL} {Fore.YELLOW}{', '.join(self.project_ids)}{Style.RESET_ALL}", ] - report_title = f"{Style.BRIGHT}Prowler is using the GCP credentials below:{Style.RESET_ALL}" + report_title = ( + f"{Style.BRIGHT}Using the GCP credentials below:{Style.RESET_ALL}" + ) print_boxes(report_lines, report_title) def get_projects(self) -> dict[str, GCPProject]: diff --git a/prowler/providers/kubernetes/kubernetes_provider.py b/prowler/providers/kubernetes/kubernetes_provider.py index 2e2b062df0..914b93db38 100644 --- a/prowler/providers/kubernetes/kubernetes_provider.py +++ b/prowler/providers/kubernetes/kubernetes_provider.py @@ -279,5 +279,7 @@ def print_credentials(self): f"{Style.BRIGHT}Namespaces:{Style.RESET_ALL} {Fore.YELLOW}{', '.join(self.namespaces)}{Style.RESET_ALL}", f"{Style.BRIGHT}Roles:{Style.RESET_ALL} {Fore.YELLOW}{roles_str}{Style.RESET_ALL}", ] - report_title = f"{Style.BRIGHT}Prowler is using the Kubernetes credentials below:{Style.RESET_ALL}" + report_title = ( + f"{Style.BRIGHT}Using the Kubernetes credentials below:{Style.RESET_ALL}" + ) print_boxes(report_lines, report_title) From 8c75b1ade26b620018351f0d3eec1431f7a08849 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 13:48:26 +0200 Subject: [PATCH 09/17] clean code --- prowler/__main__.py | 4 +- prowler/lib/check/check.py | 54 ++++++++++++++++----------- prowler/providers/aws/aws_provider.py | 3 +- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 812db5ed93..d019b3febe 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -213,12 +213,14 @@ def prowler(): # Prowler Fixer if args.fix: + print(f"{Style.BRIGHT}\nRunning Prowler Fixer...{Style.RESET_ALL}") # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in findings): run_fixer(findings) else: - print(f"{Style.BRIGHT}{Fore.GREEN}\nNo findings to fix!{Style.RESET_ALL}") + print(f"{Style.BRIGHT}{Fore.GREEN}\nNo findings to fix!{Style.RESET_ALL}\n") sys.exit() + # Extract findings stats stats = extract_findings_statistics(findings) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index f59d51b017..44c1beb095 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -433,28 +433,38 @@ def run_fixer(check_findings: list): Args: check_findings (list): list of findings """ - for finding in check_findings: - if finding.status == "FAIL": - try: - check_module_path = f"prowler.providers.{finding.check_metadata.Provider}.services.{finding.check_metadata.ServiceName}.{finding.check_metadata.CheckID}.{finding.check_metadata.CheckID}" - lib = import_check(check_module_path) - check_to_fix = getattr(lib, finding.check_metadata.CheckID) - check_class = check_to_fix() - fixer = getattr(check_class, "fixer") - print( - f"\n{orange_color}FIXING{Style.RESET_ALL}{Fore.YELLOW} {finding.check_metadata.CheckID}{Style.RESET_ALL} in {finding.region}..." - ) - print( - f"{(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}" - ) - except AttributeError: - logger.error( - f"Fixer method not implemented for check {finding.check_metadata.CheckID}" - ) - except Exception as error: - logger.error( - f"{finding.check_metadata.CheckID} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) + try: + # Map findings to each check + findings_dict = {} + for finding in check_findings: + if finding.check_metadata.CheckID not in findings_dict: + findings_dict[finding.check_metadata.CheckID] = [] + findings_dict[finding.check_metadata.CheckID].append(finding) + + for check, findings in findings_dict.items(): + # Check if there are any FAIL findings for the check + if any("FAIL" in finding.status for finding in findings): + try: + check_module_path = f"prowler.providers.{findings[0].check_metadata.Provider}.services.{findings[0].check_metadata.ServiceName}.{check}.{check}" + lib = import_check(check_module_path) + check_to_fix = getattr(lib, check) + check_class = check_to_fix() + fixer = getattr(check_class, "fixer") + except AttributeError: + logger.error(f"Fixer method not implemented for check {check}") + else: + print( + f"\nFixing fails for check {Fore.YELLOW}{check}{Style.RESET_ALL}...\n" + ) + for finding in findings: + if finding.status == "FAIL": + print( + f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}\n" + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) def execute_checks( diff --git a/prowler/providers/aws/aws_provider.py b/prowler/providers/aws/aws_provider.py index 6f619f11c6..a24ae8a9da 100644 --- a/prowler/providers/aws/aws_provider.py +++ b/prowler/providers/aws/aws_provider.py @@ -496,8 +496,7 @@ def print_credentials(self): f"{Style.BRIGHT}AWS Regions: {Style.RESET_ALL}{Fore.YELLOW}{regions}{Style.RESET_ALL}", f"{Style.BRIGHT}AWS Account: {Style.RESET_ALL}{Fore.YELLOW}{self._identity.account}{Style.RESET_ALL}", f"{Style.BRIGHT}User Id: {Style.RESET_ALL}{Fore.YELLOW}{self._identity.user_id}{Style.RESET_ALL}", - f"{Style.BRIGHT}Caller Identity ARN: {Style.RESET_ALL}", - f"{Fore.YELLOW}{self._identity.identity_arn}{Style.RESET_ALL}", + f"{Style.BRIGHT}Caller Identity ARN: {Style.RESET_ALL}{Fore.YELLOW}{self._identity.identity_arn}{Style.RESET_ALL}", ] # If -A is set, print Assumed Role ARN if ( From a7a4ce99b2b352f8cb1926c7950751537e36e975 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 14:02:46 +0200 Subject: [PATCH 10/17] solve comments --- prowler/__main__.py | 2 +- prowler/lib/check/check.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index d019b3febe..12a38fc46b 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -213,7 +213,7 @@ def prowler(): # Prowler Fixer if args.fix: - print(f"{Style.BRIGHT}\nRunning Prowler Fixer...{Style.RESET_ALL}") + print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}") # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in findings): run_fixer(findings) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 44c1beb095..b27c1019de 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -454,13 +454,14 @@ def run_fixer(check_findings: list): logger.error(f"Fixer method not implemented for check {check}") else: print( - f"\nFixing fails for check {Fore.YELLOW}{check}{Style.RESET_ALL}...\n" + f"\nFixing fails for check {Fore.YELLOW}{check}{Style.RESET_ALL}..." ) for finding in findings: if finding.status == "FAIL": print( - f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}\n" + f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}...\t{(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}" ) + print() except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" From 37f587412d66256cda71b0107747e0a43fdb12db Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 14:13:57 +0200 Subject: [PATCH 11/17] do not create outputs in fix and put verbose --- prowler/__main__.py | 2 +- prowler/lib/banner.py | 2 +- prowler/lib/check/check.py | 11 +++++++---- prowler/lib/outputs/outputs.py | 12 ++++++++---- prowler/providers/common/models.py | 3 ++- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 12a38fc46b..143330c955 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -204,7 +204,7 @@ def prowler(): global_provider, custom_checks_metadata, getattr(args, "mutelist_file", None), - args.config_file, + args, ) else: logger.error( diff --git a/prowler/lib/banner.py b/prowler/lib/banner.py index 0b00d729ad..1a45c9a5c2 100644 --- a/prowler/lib/banner.py +++ b/prowler/lib/banner.py @@ -15,7 +15,7 @@ def print_banner(args): """ print(banner) - if args.verbose: + if args.verbose or args.fix: print( f""" Color code for results: diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index b27c1019de..83ad7fb0c2 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -408,7 +408,7 @@ def run_check(check: Check, output_options) -> list: list: list of findings """ findings = [] - if output_options.verbose: + if output_options.verbose or output_options.fix: print( f"\nCheck ID: {check.CheckID} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW} [{check.Severity}]{Style.RESET_ALL}" ) @@ -473,7 +473,7 @@ def execute_checks( global_provider: Any, custom_checks_metadata: Any, mutelist_file: str, - config_file: str, + args: Any, ) -> list: # List to store all the check's findings all_findings = [] @@ -525,6 +525,7 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, + args, ) all_findings.extend(check_findings) @@ -540,7 +541,7 @@ def execute_checks( else: # Prepare your messages messages = [ - f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{config_file}{Style.RESET_ALL}" + f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{args.config_file}{Style.RESET_ALL}" ] if mutelist_file: messages.append( @@ -585,6 +586,7 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, + args, ) all_findings.extend(check_findings) @@ -611,6 +613,7 @@ def execute( services_executed: set, checks_executed: set, custom_checks_metadata: Any, + args: Any, ): try: # Import check module @@ -629,7 +632,7 @@ def execute( ) # Run check - check_findings = run_check(check_class, global_provider.output_options) + check_findings = run_check(check_class, global_provider.output_options, args) # Update Audit Status services_executed.add(service) diff --git a/prowler/lib/outputs/outputs.py b/prowler/lib/outputs/outputs.py index 236cd6dc1c..e182cac773 100644 --- a/prowler/lib/outputs/outputs.py +++ b/prowler/lib/outputs/outputs.py @@ -23,7 +23,7 @@ from prowler.lib.outputs.utils import unroll_dict -def stdout_report(finding, color, verbose, status): +def stdout_report(finding, color, verbose, status, fix): if finding.check_metadata.Provider == "aws": details = finding.region if finding.check_metadata.Provider == "azure": @@ -33,7 +33,7 @@ def stdout_report(finding, color, verbose, status): if finding.check_metadata.Provider == "kubernetes": details = finding.namespace.lower() - if verbose and (not status or finding.status in status): + if (verbose or fix) and (not status or finding.status in status): if finding.muted: print( f"\t{color}MUTED ({finding.status}){Style.RESET_ALL} {details}: {finding.status_extended}" @@ -57,7 +57,7 @@ def report(check_findings, provider): check_findings.sort(key=lambda x: x.subscription) # Generate the required output files - if output_options.output_modes: + if output_options.output_modes and not output_options.fix: # We have to create the required output files file_descriptors = fill_file_descriptors( output_options.output_modes, @@ -70,7 +70,11 @@ def report(check_findings, provider): # Print findings by stdout color = set_report_color(finding.status, finding.muted) stdout_report( - finding, color, output_options.verbose, output_options.status + finding, + color, + output_options.verbose, + output_options.status, + output_options.fix, ) if file_descriptors: diff --git a/prowler/providers/common/models.py b/prowler/providers/common/models.py index c4d6c62fea..b7f6a23d97 100644 --- a/prowler/providers/common/models.py +++ b/prowler/providers/common/models.py @@ -35,13 +35,14 @@ def __init__(self, arguments, bulk_checks_metadata): self.only_logs = arguments.only_logs self.unix_timestamp = arguments.unix_timestamp self.shodan_api_key = arguments.shodan + self.fix = arguments.fix # Shodan API Key if arguments.shodan: update_provider_config("shodan_api_key", arguments.shodan) # Check output directory, if it is not created -> create it - if arguments.output_directory: + if arguments.output_directory and not arguments.fix: if not isdir(arguments.output_directory): if arguments.output_formats: makedirs(arguments.output_directory, exist_ok=True) From 170e301f11978e5818ef44cedb916add55ab5c63 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 14:18:10 +0200 Subject: [PATCH 12/17] add last changes --- prowler/__main__.py | 2 +- prowler/lib/check/check.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 143330c955..12a38fc46b 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -204,7 +204,7 @@ def prowler(): global_provider, custom_checks_metadata, getattr(args, "mutelist_file", None), - args, + args.config_file, ) else: logger.error( diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index 83ad7fb0c2..f974f61ca2 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -473,7 +473,7 @@ def execute_checks( global_provider: Any, custom_checks_metadata: Any, mutelist_file: str, - args: Any, + config_file: str, ) -> list: # List to store all the check's findings all_findings = [] @@ -525,7 +525,6 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, - args, ) all_findings.extend(check_findings) @@ -541,7 +540,7 @@ def execute_checks( else: # Prepare your messages messages = [ - f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{args.config_file}{Style.RESET_ALL}" + f"{Style.BRIGHT}Config File: {Style.RESET_ALL}{Fore.YELLOW}{config_file}{Style.RESET_ALL}" ] if mutelist_file: messages.append( @@ -586,7 +585,6 @@ def execute_checks( services_executed, checks_executed, custom_checks_metadata, - args, ) all_findings.extend(check_findings) @@ -613,7 +611,6 @@ def execute( services_executed: set, checks_executed: set, custom_checks_metadata: Any, - args: Any, ): try: # Import check module @@ -632,7 +629,7 @@ def execute( ) # Run check - check_findings = run_check(check_class, global_provider.output_options, args) + check_findings = run_check(check_class, global_provider.output_options) # Update Audit Status services_executed.add(service) From d5518a994967a91a2df420c72e4a6de1827839ea Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 14:21:29 +0200 Subject: [PATCH 13/17] fix other providers --- prowler/__main__.py | 2 +- prowler/lib/banner.py | 2 +- prowler/lib/check/checks_loader.py | 2 +- prowler/providers/common/models.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 12a38fc46b..4fc3ea8e8a 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -212,7 +212,7 @@ def prowler(): ) # Prowler Fixer - if args.fix: + if getattr(args, "fix", None): print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}") # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in findings): diff --git a/prowler/lib/banner.py b/prowler/lib/banner.py index 1a45c9a5c2..99fbd4a88d 100644 --- a/prowler/lib/banner.py +++ b/prowler/lib/banner.py @@ -15,7 +15,7 @@ def print_banner(args): """ print(banner) - if args.verbose or args.fix: + if args.verbose or getattr(args, "fix", False): print( f""" Color code for results: diff --git a/prowler/lib/check/checks_loader.py b/prowler/lib/check/checks_loader.py index 4c290df161..2091e64561 100644 --- a/prowler/lib/check/checks_loader.py +++ b/prowler/lib/check/checks_loader.py @@ -101,7 +101,7 @@ def load_checks_to_execute( # Only execute threat detection checks if threat-detection category is set if "threat-detection" not in categories: - for threat_detection_check in check_categories["threat-detection"]: + for threat_detection_check in check_categories.get("threat-detection", []): checks_to_execute.discard(threat_detection_check) # Check Aliases diff --git a/prowler/providers/common/models.py b/prowler/providers/common/models.py index b7f6a23d97..5073219012 100644 --- a/prowler/providers/common/models.py +++ b/prowler/providers/common/models.py @@ -35,14 +35,14 @@ def __init__(self, arguments, bulk_checks_metadata): self.only_logs = arguments.only_logs self.unix_timestamp = arguments.unix_timestamp self.shodan_api_key = arguments.shodan - self.fix = arguments.fix + self.fix = getattr(arguments, "fix", None) # Shodan API Key if arguments.shodan: update_provider_config("shodan_api_key", arguments.shodan) # Check output directory, if it is not created -> create it - if arguments.output_directory and not arguments.fix: + if arguments.output_directory and not self.fix: if not isdir(arguments.output_directory): if arguments.output_formats: makedirs(arguments.output_directory, exist_ok=True) From 2b6ec76dc57a86d4b6d49b7d74756932a6710823 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 14:39:10 +0200 Subject: [PATCH 14/17] remove getattr --- prowler/__main__.py | 2 +- prowler/lib/banner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prowler/__main__.py b/prowler/__main__.py index 4fc3ea8e8a..7bea429ecc 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -212,7 +212,7 @@ def prowler(): ) # Prowler Fixer - if getattr(args, "fix", None): + if global_provider.output_options.fix: print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}") # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in findings): diff --git a/prowler/lib/banner.py b/prowler/lib/banner.py index 99fbd4a88d..46219067cb 100644 --- a/prowler/lib/banner.py +++ b/prowler/lib/banner.py @@ -15,7 +15,7 @@ def print_banner(args): """ print(banner) - if args.verbose or getattr(args, "fix", False): + if args.verbose or getattr(args, "fix", None): print( f""" Color code for results: From 99fb615ebd6391be1a167a601c0bef095b9eb7cc Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 16:21:45 +0200 Subject: [PATCH 15/17] final version --- prowler/__main__.py | 8 +++- prowler/lib/check/check.py | 45 ++++++++++++++++--- prowler/lib/cli/parser.py | 7 +++ prowler/lib/outputs/outputs.py | 4 +- .../providers/aws/lib/arguments/arguments.py | 2 +- .../ec2_ebs_default_encryption.py | 3 -- .../ec2_ebs_default_encryption_fixer.py | 33 ++++++++++++++ .../providers/aws/services/ec2/ec2_service.py | 12 ----- prowler/providers/common/models.py | 4 +- tests/lib/cli/parser_test.py | 10 ++++- 10 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption_fixer.py diff --git a/prowler/__main__.py b/prowler/__main__.py index 7bea429ecc..b06609594a 100644 --- a/prowler/__main__.py +++ b/prowler/__main__.py @@ -16,12 +16,14 @@ execute_checks, list_categories, list_checks_json, + list_fixers, list_services, parse_checks_from_folder, print_categories, print_checks, print_compliance_frameworks, print_compliance_requirements, + print_fixers, print_services, remove_custom_checks_module, run_fixer, @@ -95,6 +97,10 @@ def prowler(): print_services(list_services(provider)) sys.exit() + if args.list_fixer: + print_fixers(list_fixers(provider)) + sys.exit() + # Load checks metadata logger.debug("Loading checks metadata from .metadata.json files") bulk_checks_metadata = bulk_load_checks_metadata(provider) @@ -212,7 +218,7 @@ def prowler(): ) # Prowler Fixer - if global_provider.output_options.fix: + if global_provider.output_options.fixer: print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}") # Check if there are any FAIL findings if any("FAIL" in finding.status for finding in findings): diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index f974f61ca2..c2ffea9103 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -36,6 +36,9 @@ def bulk_load_checks_metadata(provider: str) -> dict: # Build check path name check_name = check_info[0] check_path = check_info[1] + # Ignore fixer files + if check_name.endswith("_fixer"): + continue # Append metadata file extension metadata_file = f"{check_path}/{check_name}.metadata.json" # Load metadata @@ -194,6 +197,7 @@ def list_services(provider: str) -> set: available_services = set() checks_tuple = recover_checks_from_provider(provider) for _, check_path in checks_tuple: + print(check_path) # Format: /absolute_path/prowler/providers/{provider}/services/{service_name}/{check_name} if os.name == "nt": service_name = check_path.split("\\")[-2] @@ -203,6 +207,20 @@ def list_services(provider: str) -> set: return sorted(available_services) +def list_fixers(provider: str) -> set: + available_fixers = set() + checks = recover_checks_from_provider(provider) + # Build list of check's metadata files + for check_info in checks: + # Build check path name + check_name = check_info[0] + # Ignore non fixer files + if not check_name.endswith("_fixer"): + continue + available_fixers.add(check_name) + return sorted(available_fixers) + + def list_categories(bulk_checks_metadata: dict) -> set: available_categories = set() for check in bulk_checks_metadata.values(): @@ -239,6 +257,23 @@ def print_services(service_list: set): print(message) +def print_fixers(fixers_list: set): + services_num = len(fixers_list) + plural_string = ( + f"\nThere are {Fore.YELLOW}{services_num}{Style.RESET_ALL} available fixers.\n" + ) + singular_string = ( + f"\nThere is {Fore.YELLOW}{services_num}{Style.RESET_ALL} available fixer.\n" + ) + + message = plural_string if services_num > 1 else singular_string + + for service in fixers_list: + print(f"- {service}") + + print(message) + + def print_compliance_frameworks( bulk_compliance_frameworks: dict, ): @@ -408,7 +443,7 @@ def run_check(check: Check, output_options) -> list: list: list of findings """ findings = [] - if output_options.verbose or output_options.fix: + if output_options.verbose or output_options.fixer: print( f"\nCheck ID: {check.CheckID} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW} [{check.Severity}]{Style.RESET_ALL}" ) @@ -445,11 +480,9 @@ def run_fixer(check_findings: list): # Check if there are any FAIL findings for the check if any("FAIL" in finding.status for finding in findings): try: - check_module_path = f"prowler.providers.{findings[0].check_metadata.Provider}.services.{findings[0].check_metadata.ServiceName}.{check}.{check}" + check_module_path = f"prowler.providers.{findings[0].check_metadata.Provider}.services.{findings[0].check_metadata.ServiceName}.{check}.{check}_fixer" lib = import_check(check_module_path) - check_to_fix = getattr(lib, check) - check_class = check_to_fix() - fixer = getattr(check_class, "fixer") + fixer = getattr(lib, "fixer") except AttributeError: logger.error(f"Fixer method not implemented for check {check}") else: @@ -459,7 +492,7 @@ def run_fixer(check_findings: list): for finding in findings: if finding.status == "FAIL": print( - f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}...\t{(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}" + f"\t{orange_color}FIXING{Style.RESET_ALL} {finding.region}... {(Fore.GREEN + 'DONE') if fixer(finding.region) else (Fore.RED + 'ERROR')}{Style.RESET_ALL}" ) print() except Exception as error: diff --git a/prowler/lib/cli/parser.py b/prowler/lib/cli/parser.py index 9ef1124001..c451c0eb0c 100644 --- a/prowler/lib/cli/parser.py +++ b/prowler/lib/cli/parser.py @@ -291,6 +291,7 @@ def __init_list_checks_parser__(self): ) list_group.add_argument( "--list-compliance", + "--list-compliances", action="store_true", help="List all available compliance frameworks", ) @@ -305,6 +306,12 @@ def __init_list_checks_parser__(self): action="store_true", help="List the available check's categories", ) + list_group.add_argument( + "--list-fixer", + "--list-fixers", + action="store_true", + help="List fixers available for the provider", + ) def __init_mutelist_parser__(self): mutelist_subparser = self.common_providers_parser.add_argument_group( diff --git a/prowler/lib/outputs/outputs.py b/prowler/lib/outputs/outputs.py index e182cac773..d1ff4e953c 100644 --- a/prowler/lib/outputs/outputs.py +++ b/prowler/lib/outputs/outputs.py @@ -57,7 +57,7 @@ def report(check_findings, provider): check_findings.sort(key=lambda x: x.subscription) # Generate the required output files - if output_options.output_modes and not output_options.fix: + if output_options.output_modes and not output_options.fixer: # We have to create the required output files file_descriptors = fill_file_descriptors( output_options.output_modes, @@ -74,7 +74,7 @@ def report(check_findings, provider): color, output_options.verbose, output_options.status, - output_options.fix, + output_options.fixer, ) if file_descriptors: diff --git a/prowler/providers/aws/lib/arguments/arguments.py b/prowler/providers/aws/lib/arguments/arguments.py index ab972e336a..bf41d76976 100644 --- a/prowler/providers/aws/lib/arguments/arguments.py +++ b/prowler/providers/aws/lib/arguments/arguments.py @@ -162,7 +162,7 @@ def init_parser(self): # Prowler Fixer prowler_fixer_subparser = aws_parser.add_argument_group("Prowler Fixer") prowler_fixer_subparser.add_argument( - "--fix", + "--fixer", action="store_true", help="Fix the failed findings that can be fixed by Prowler", ) diff --git a/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py b/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py index ce20785441..8ef98bc92c 100644 --- a/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py +++ b/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption.py @@ -22,6 +22,3 @@ def execute(self): findings.append(report) return findings - - def fixer(self, region): - return ec2_client.__enable_ebs_encryption_by_default__(region) diff --git a/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption_fixer.py b/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption_fixer.py new file mode 100644 index 0000000000..4dda348bec --- /dev/null +++ b/prowler/providers/aws/services/ec2/ec2_ebs_default_encryption/ec2_ebs_default_encryption_fixer.py @@ -0,0 +1,33 @@ +from prowler.lib.logger import logger +from prowler.providers.aws.services.ec2.ec2_client import ec2_client + + +def fixer(region): + """ + Enable EBS encryption by default in a region. + Requires the ec2:EnableEbsEncryptionByDefault permission: + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "ec2:EnableEbsEncryptionByDefault", + "Resource": "*" + } + ] + } + Args: + region (str): AWS region + Returns: + bool: True if EBS encryption by default is enabled, False otherwise + """ + try: + regional_client = ec2_client.regional_clients[region] + return regional_client.enable_ebs_encryption_by_default()[ + "EbsEncryptionByDefault" + ] + except Exception as error: + logger.error( + f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return False diff --git a/prowler/providers/aws/services/ec2/ec2_service.py b/prowler/providers/aws/services/ec2/ec2_service.py index ed23b6890b..e9e9865017 100644 --- a/prowler/providers/aws/services/ec2/ec2_service.py +++ b/prowler/providers/aws/services/ec2/ec2_service.py @@ -410,18 +410,6 @@ def __get_ebs_encryption_settings__(self, regional_client): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def __enable_ebs_encryption_by_default__(self, region): - try: - regional_client = self.regional_clients[region] - return regional_client.enable_ebs_encryption_by_default()[ - "EbsEncryptionByDefault" - ] - except Exception as error: - logger.error( - f"{region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - return False - class Instance(BaseModel): id: str diff --git a/prowler/providers/common/models.py b/prowler/providers/common/models.py index 5073219012..0f66ffc2e0 100644 --- a/prowler/providers/common/models.py +++ b/prowler/providers/common/models.py @@ -35,14 +35,14 @@ def __init__(self, arguments, bulk_checks_metadata): self.only_logs = arguments.only_logs self.unix_timestamp = arguments.unix_timestamp self.shodan_api_key = arguments.shodan - self.fix = getattr(arguments, "fix", None) + self.fixer = getattr(arguments, "fixer", None) # Shodan API Key if arguments.shodan: update_provider_config("shodan_api_key", arguments.shodan) # Check output directory, if it is not created -> create it - if arguments.output_directory and not self.fix: + if arguments.output_directory and not self.fixer: if not isdir(arguments.output_directory): if arguments.output_formats: makedirs(arguments.output_directory, exist_ok=True) diff --git a/tests/lib/cli/parser_test.py b/tests/lib/cli/parser_test.py index 0de4032ead..dd3efdb867 100644 --- a/tests/lib/cli/parser_test.py +++ b/tests/lib/cli/parser_test.py @@ -705,6 +705,12 @@ def test_list_checks_parser_list_categories(self): parsed = self.parser.parse(command) assert parsed.list_categories + def test_list_checks_parser_list_fixers(self): + argument = "--list-fixers" + command = [prowler_command, argument] + parsed = self.parser.parse(command) + assert parsed.list_fixers + def test_list_checks_parser_list_compliance_requirements_no_arguments(self): argument = "--list-compliance-requirements" command = [prowler_command, argument] @@ -1049,10 +1055,10 @@ def test_aws_parser_scan_unused_services(self): assert parsed.scan_unused_services def test_aws_parser_fixer(self): - argument = "--fix" + argument = "--fixer" command = [prowler_command, argument] parsed = self.parser.parse(command) - assert parsed.fix + assert parsed.fixer def test_aws_parser_config_file(self): argument = "--config-file" From e59245a4076f40ea3318362c5919b106a39e7ea5 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 16:23:55 +0200 Subject: [PATCH 16/17] fixes --- prowler/lib/check/check.py | 1 - tests/providers/aws/services/ec2/ec2_service_test.py | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/prowler/lib/check/check.py b/prowler/lib/check/check.py index c2ffea9103..a1408f6afb 100644 --- a/prowler/lib/check/check.py +++ b/prowler/lib/check/check.py @@ -197,7 +197,6 @@ def list_services(provider: str) -> set: available_services = set() checks_tuple = recover_checks_from_provider(provider) for _, check_path in checks_tuple: - print(check_path) # Format: /absolute_path/prowler/providers/{provider}/services/{service_name}/{check_name} if os.name == "nt": service_name = check_path.split("\\")[-2] diff --git a/tests/providers/aws/services/ec2/ec2_service_test.py b/tests/providers/aws/services/ec2/ec2_service_test.py index 01d3b8ee2d..936bcf5beb 100644 --- a/tests/providers/aws/services/ec2/ec2_service_test.py +++ b/tests/providers/aws/services/ec2/ec2_service_test.py @@ -544,14 +544,3 @@ def test__describe_volumes__(self): assert ec2.volumes[0].tags == [ {"Key": "test", "Value": "test"}, ] - - # Test EC2 EBS Enabling Encryption by Default - @mock_aws - def test__describe_ebs_encryption_by_default__(self): - # EC2 client for this test class - aws_provider = set_mocked_aws_provider( - [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] - ) - ec2 = EC2(aws_provider) - - assert ec2.__enable_ebs_encryption_by_default__(region=AWS_REGION_US_EAST_1) From 87949b695fdc7e986c7ded1ffe66a6c294bc7794 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 2 Apr 2024 16:46:59 +0200 Subject: [PATCH 17/17] fix test --- tests/lib/cli/parser_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/cli/parser_test.py b/tests/lib/cli/parser_test.py index dd3efdb867..cc91218eec 100644 --- a/tests/lib/cli/parser_test.py +++ b/tests/lib/cli/parser_test.py @@ -709,7 +709,7 @@ def test_list_checks_parser_list_fixers(self): argument = "--list-fixers" command = [prowler_command, argument] parsed = self.parser.parse(command) - assert parsed.list_fixers + assert parsed.list_fixer def test_list_checks_parser_list_compliance_requirements_no_arguments(self): argument = "--list-compliance-requirements"