From 9204142eafa34db2cf5c056f65efd3ac4f150937 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:18:43 +0100 Subject: [PATCH] feat(display): add progress bar and summary table (#1512) Co-authored-by: sergargar --- Pipfile | 2 + Pipfile.lock | 91 +++++++++----- config/config.py | 5 +- lib/banner.py | 23 ++-- lib/check/check.py | 71 +++++++++-- lib/check/models.py | 1 + lib/outputs/outputs.py | 119 +++++++++++++++++- lib/outputs/outputs_test.py | 3 +- ...ccount_maintain_current_contact_details.py | 15 +-- ...urity_contact_information_is_registered.py | 15 +-- ...tions_are_registered_in_the_aws_account.py | 15 +-- .../aws/services/account/account_service.py | 4 + ...l_s3_dataevents_read_enabled.metadata.json | 6 +- ..._s3_dataevents_write_enabled.metadata.json | 6 +- .../aws/services/ec2/lib/security_groups.py | 5 +- prowler | 45 ++++--- 16 files changed, 320 insertions(+), 106 deletions(-) diff --git a/Pipfile b/Pipfile index 01f1d2c9..13ed6e30 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,8 @@ botocore = "1.27.8" pydantic = "1.9.1" shodan = "1.28.0" detect-secrets = "1.4.0" +alive-progress = "2.4.1" +tabulate = "0.9.0" [dev-packages] black = "22.10.0" diff --git a/Pipfile.lock b/Pipfile.lock index 92d6a8a9..f0599f04 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "881edb3306efd59b84c75bd2bff5acbc29397eb7f12321203c62a013f0491e2e" + "sha256": "2205b1ac0f570e1be2641ea17a1d07a046d4ae547f49622cf993c8f23ee550ae" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,21 @@ ] }, "default": { + "about-time": { + "hashes": [ + "sha256:586b329450c9387d1ae8c42d2db4f5b4c57a54508d0f1b7bb00322ffd5ce9f9b", + "sha256:96841beb3f9b5de1cbb323d2bdb0fa9abdecbc46f2d546b9b3c2483d23daa17a" + ], + "version": "==3.1.1" + }, + "alive-progress": { + "hashes": [ + "sha256:089757c8197f27ad972ba27e1060f6db92368d83c736884e159034fd74865323", + "sha256:5503ffca0a0607d5f0d24d3b10a718fe50e375470fa07602b246333eb7ec88ee" + ], + "index": "pypi", + "version": "==2.4.1" + }, "arnparse": { "hashes": [ "sha256:b0906734e4b8f19e39b1e32944c6cd6274b6da90c066a83882ac7a11d27553e0", @@ -26,19 +41,19 @@ }, "boto3": { "hashes": [ - "sha256:180b4413db1211836c622adfc16dd40a7b99b86cac894bc6e17ddea9d282ab14", - "sha256:73da24667fe1351cef0f402ee9cd4589a0a9d97b617caca3c25b5cdc38f9d62c" + "sha256:853cf4b2136c4deec4e01a17b89126377bfca30223535795d879ca65af4c4a69", + "sha256:a8ad13a23745b6d4a56d5bdde53a7a80cd7b40016cd411b9a94e6bbfb2ca5dd2" ], "index": "pypi", - "version": "==1.26.12" + "version": "==1.26.13" }, "botocore": { "hashes": [ - "sha256:3149b102e3c26c935acef6c330d0f46c717820d118886e983b6e81c306f31405", - "sha256:fdae73306a41a30697be300bdecb1e0d560d453c6d748891856beb87e9f6573f" + "sha256:9c73a180fad9a7da7797530ced3b5069872bff915b1ae9fa11fc1ed79b584c8e", + "sha256:9d39db398f472c0aa97098870c8c4cf12636b2667a18e694fea5fae046af907e" ], "index": "pypi", - "version": "==1.29.12" + "version": "==1.29.13" }, "certifi": { "hashes": [ @@ -53,7 +68,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==2.1.1" }, "click": { @@ -87,6 +102,12 @@ "index": "pypi", "version": "==1.4.0" }, + "grapheme": { + "hashes": [ + "sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca" + ], + "version": "==0.6.0" + }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", @@ -230,6 +251,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "tabulate": { + "hashes": [ + "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", + "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f" + ], + "index": "pypi", + "version": "==0.9.0" + }, "typing-extensions": { "hashes": [ "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", @@ -258,11 +287,11 @@ "develop": { "astroid": { "hashes": [ - "sha256:1c00a14f5a3ed0339d38d2e2e5b74ea2591df5861c0936bb292b84ccf3a78d83", - "sha256:72702205200b2a638358369d90c222d74ebc376787af8fb2f7f2a86f7b5cc85f" + "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907", + "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7" ], "markers": "python_full_version >= '3.7.2'", - "version": "==2.12.12" + "version": "==2.12.13" }, "attrs": { "hashes": [ @@ -309,19 +338,19 @@ }, "boto3": { "hashes": [ - "sha256:180b4413db1211836c622adfc16dd40a7b99b86cac894bc6e17ddea9d282ab14", - "sha256:73da24667fe1351cef0f402ee9cd4589a0a9d97b617caca3c25b5cdc38f9d62c" + "sha256:853cf4b2136c4deec4e01a17b89126377bfca30223535795d879ca65af4c4a69", + "sha256:a8ad13a23745b6d4a56d5bdde53a7a80cd7b40016cd411b9a94e6bbfb2ca5dd2" ], "index": "pypi", - "version": "==1.26.12" + "version": "==1.26.13" }, "botocore": { "hashes": [ - "sha256:3149b102e3c26c935acef6c330d0f46c717820d118886e983b6e81c306f31405", - "sha256:fdae73306a41a30697be300bdecb1e0d560d453c6d748891856beb87e9f6573f" + "sha256:9c73a180fad9a7da7797530ced3b5069872bff915b1ae9fa11fc1ed79b584c8e", + "sha256:9d39db398f472c0aa97098870c8c4cf12636b2667a18e694fea5fae046af907e" ], "index": "pypi", - "version": "==1.29.12" + "version": "==1.29.13" }, "certifi": { "hashes": [ @@ -405,7 +434,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==2.1.1" }, "click": { @@ -604,7 +633,7 @@ "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" ], - "markers": "python_version < '4' and python_full_version >= '3.6.1'", + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", "version": "==5.10.1" }, "jinja2": { @@ -636,7 +665,7 @@ "sha256:1e525177574c23ae0f55cd62382632a083a0339928f0ca846a975a4da9851cec", "sha256:780a22d517cdc857d9714a80d8349c546945063f20853ea32ba7f85bc643ec7d" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", "version": "==0.1.2" }, "lazy-object-proxy": { @@ -728,11 +757,11 @@ }, "moto": { "hashes": [ - "sha256:2fb909d2ea1b732f89604e4268e2c2207c253e590a635a410c3c2aaebb34e113", - "sha256:ba03b638cf3b1cec64cbe9ac0d184ca898b69020c8e3c5b9b4961c1670629010" + "sha256:356bf792b439228891c910e2a0fafd4264334cf9000b508c732ff43d8694fb6a", + "sha256:9ba96d04a472d5682493cad7fee33337da34ebef18b397af1ea6dfb41efbe148" ], "index": "pypi", - "version": "==4.0.9" + "version": "==4.0.10" }, "mypy-extensions": { "hashes": [ @@ -746,7 +775,7 @@ "sha256:34fbd14b7501abe25e64d7b4624a9db02cde1a578d285b3da6f34b290cdf0b3a", "sha256:7cf27585dd7970b7257cefe48e1a3a10d4e34421831bdb472d96967433bc27bd" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", "version": "==0.3.4" }, "openapi-spec-validator": { @@ -770,7 +799,7 @@ "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab", "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_full_version >= '3.7.0' and python_full_version < '4.0.0'", "version": "==0.4.3" }, "pathspec": { @@ -830,11 +859,11 @@ }, "pylint": { "hashes": [ - "sha256:3b120505e5af1d06a5ad76b55d8660d44bf0f2fc3c59c2bdd94e39188ee3a4df", - "sha256:c2108037eb074334d9e874dc3c783752cc03d0796c88c9a9af282d0f161a1004" + "sha256:15060cc22ed6830a4049cf40bc24977744df2e554d38da1b2657591de5bcd052", + "sha256:25b13ddcf5af7d112cf96935e21806c1da60e676f952efb650130f2a4483421c" ], "index": "pypi", - "version": "==2.15.5" + "version": "==2.15.6" }, "pyparsing": { "hashes": [ @@ -1030,11 +1059,11 @@ }, "setuptools": { "hashes": [ - "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31", - "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f" + "sha256:6211d2f5eddad8757bd0484923ca7c0a6302ebc4ab32ea5e94357176e0ca0840", + "sha256:d1eebf881c6114e51df1664bc2c9133d022f78d12d5f4f665b9191f084e2862d" ], "markers": "python_version >= '3.7'", - "version": "==65.5.1" + "version": "==65.6.0" }, "six": { "hashes": [ diff --git a/config/config.py b/config/config.py index 68c2516e..ba8abda4 100644 --- a/config/config.py +++ b/config/config.py @@ -7,7 +7,10 @@ from lib.logger import logger timestamp = datetime.today() timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc) -prowler_version = "3.0-beta-08Aug2022" +prowler_version = "3.0-beta-21Nov2022" + +orange_color = "\033[38;5;208m" +banner_color = "\033[1;92m" # Groups groups_file = "groups.json" diff --git a/lib/banner.py b/lib/banner.py index ad63a8d4..7e0e5dbb 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,14 +1,14 @@ from colorama import Fore, Style -from config.config import prowler_version, timestamp +from config.config import banner_color, orange_color, prowler_version, timestamp def print_version(): print(f"Prowler {prowler_version}") -def print_banner(): - banner = f"""{Fore.CYAN} _ +def print_banner(args): + banner = f"""{banner_color} _ _ __ _ __ _____ _| | ___ _ __ | '_ \| '__/ _ \ \ /\ / / |/ _ \ '__| | |_) | | | (_) \ V V /| | __/ | @@ -16,11 +16,16 @@ def print_banner(): |_|{Fore.BLUE} the handy cloud security tool {Fore.YELLOW}Date: {timestamp.strftime("%Y-%m-%d %H:%M:%S")}{Style.RESET_ALL} - -Color code for results: - - {Fore.YELLOW}INFO (Information){Style.RESET_ALL} - - {Fore.GREEN}PASS (Recommended value){Style.RESET_ALL} - - {Fore.YELLOW}WARNING (Ignored by allowlist){Style.RESET_ALL} - - {Fore.RED}FAIL (Fix required){Style.RESET_ALL} """ print(banner) + + if args.verbose or args.quiet: + print( + f""" + Color code for results: + - {Fore.YELLOW}INFO (Information){Style.RESET_ALL} + - {Fore.GREEN}PASS (Recommended value){Style.RESET_ALL} + - {orange_color}WARNING (Ignored by allowlist){Style.RESET_ALL} + - {Fore.RED}FAIL (Fix required){Style.RESET_ALL} + """ + ) diff --git a/lib/check/check.py b/lib/check/check.py index 086f3dd9..1a31c5ca 100644 --- a/lib/check/check.py +++ b/lib/check/check.py @@ -3,13 +3,15 @@ from pkgutil import walk_packages from types import ModuleType from typing import Any +from alive_progress import alive_bar from colorama import Fore, Style -from config.config import groups_file -from lib.check.models import Output_From_Options, load_check_metadata +from config.config import groups_file, orange_color +from lib.check.models import Check, Output_From_Options, load_check_metadata from lib.logger import logger from lib.outputs.outputs import report from lib.utils.utils import open_file, parse_json_file +from providers.aws.lib.audit_info.models import AWS_Audit_Info # Load all checks metadata @@ -189,6 +191,7 @@ def set_output_options( security_hub_enabled: bool, output_filename: str, allowlist_file: str, + verbose: bool, ): global output_options output_options = Output_From_Options( @@ -198,15 +201,18 @@ def set_output_options( security_hub_enabled=security_hub_enabled, output_filename=output_filename, allowlist_file=allowlist_file, + verbose=verbose, # set input options here ) return output_options -def run_check(check, audit_info, output_options): - print( - f"\nCheck ID: {check.checkID} - {Fore.MAGENTA}{check.serviceName}{Fore.YELLOW} [{check.severity}]{Style.RESET_ALL}" - ) +def run_check(check: Check, output_options: Output_From_Options) -> list: + findings = [] + if output_options.verbose or output_options.is_quiet: + print( + f"\nCheck ID: {check.checkID} - {Fore.MAGENTA}{check.serviceName}{Fore.YELLOW} [{check.severity}]{Style.RESET_ALL}" + ) logger.debug(f"Executing check: {check.checkID}") try: findings = check.execute() @@ -215,7 +221,54 @@ def run_check(check, audit_info, output_options): logger.error( f"{check.checkID} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - else: - report(findings, output_options, audit_info) finally: - pass + return findings + + +def execute_checks( + checks_to_execute: list, + provider: str, + audit_info: AWS_Audit_Info, + audit_output_options: Output_From_Options, +) -> list: + all_findings = [] + print( + f"{Style.BRIGHT}Executing {len(checks_to_execute)} checks, please wait...{Style.RESET_ALL}\n" + ) + with alive_bar( + total=len(checks_to_execute), + ctrl_c=False, + bar="blocks", + spinner="classic", + stats=False, + enrich_print=False, + ) as bar: + for check_name in checks_to_execute: + # Recover service from check name + service = check_name.split("_")[0] + bar.title = f"-> Scanning {orange_color}{service}{Style.RESET_ALL} service" + try: + # Import check module + check_module_path = ( + f"providers.{provider}.services.{service}.{check_name}.{check_name}" + ) + lib = import_check(check_module_path) + # Recover functions from check + check_to_execute = getattr(lib, check_name) + c = check_to_execute() + # Run check + check_findings = run_check(c, audit_output_options) + all_findings.extend(check_findings) + report(check_findings, audit_output_options, audit_info) + bar() + + # If check does not exists in the provider or is from another provider + except ModuleNotFoundError: + logger.error( + f"Check '{check_name}' was not found for the {provider.upper()} provider" + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return all_findings diff --git a/lib/check/models.py b/lib/check/models.py index 8334ed64..03484494 100644 --- a/lib/check/models.py +++ b/lib/check/models.py @@ -16,6 +16,7 @@ class Output_From_Options: security_hub_enabled: bool output_filename: str allowlist_file: str + verbose: str # Testing Pending diff --git a/lib/outputs/outputs.py b/lib/outputs/outputs.py index 16ece968..d3f38b01 100644 --- a/lib/outputs/outputs.py +++ b/lib/outputs/outputs.py @@ -4,11 +4,13 @@ import sys from csv import DictWriter from colorama import Fore, Style +from tabulate import tabulate from config.config import ( csv_file_suffix, json_asff_file_suffix, json_file_suffix, + orange_color, prowler_version, timestamp_iso, timestamp_utc, @@ -25,6 +27,7 @@ from lib.outputs.models import ( ) from lib.utils.utils import file_exists, hash_sha512, open_file from providers.aws.lib.allowlist.allowlist import is_allowlisted +from providers.aws.lib.audit_info.models import AWS_Audit_Info from providers.aws.lib.security_hub.security_hub import send_to_security_hub @@ -62,7 +65,7 @@ def report(check_findings, output_options, audit_info): print( f"\t{color}{finding.status}{Style.RESET_ALL} {finding.region}: {finding.status_extended}" ) - elif not output_options.is_quiet: + elif not output_options.is_quiet and output_options.verbose: print( f"\t{color}{finding.status}{Style.RESET_ALL} {finding.region}: {finding.status_extended}" ) @@ -103,8 +106,8 @@ def report(check_findings, output_options, audit_info): finding.region, finding_output, audit_info.audit_session ) else: # No service resources in the whole account - color = set_report_color("WARNING") - if not output_options.is_quiet: + color = set_report_color("INFO") + if not output_options.is_quiet and output_options.verbose: print(f"\t{color}INFO{Style.RESET_ALL} There are no resources") if file_descriptors: @@ -180,6 +183,8 @@ def set_report_color(status): elif status == "ERROR": color = Fore.BLACK elif status == "WARNING": + color = orange_color + elif status == "INFO": color = Fore.YELLOW else: raise Exception("Invalid Report Status. Must be PASS, FAIL, ERROR or WARNING") @@ -289,3 +294,111 @@ def send_to_s3_bucket( except Exception as error: logger.critical(f"{error.__class__.__name__} -- {error}") sys.exit() + + +def display_summary_table( + findings: list, + audit_info: AWS_Audit_Info, + output_filename: str, + output_directory: str, +): + try: + if findings: + current = { + "Service": "", + "Provider": "", + "Critical": 0, + "High": 0, + "Medium": 0, + "Low": 0, + } + findings_table = { + "Provider": [], + "Service": [], + "Status": [], + "Critical": [], + "High": [], + "Medium": [], + "Low": [], + } + pass_count = fail_count = 0 + for finding in findings: + # If new service and not first, add previous row + if ( + current["Service"] != finding.check_metadata.ServiceName + and current["Service"] + ): + + add_service_to_table(findings_table, current) + + current["Critical"] = current["High"] = current["Medium"] = current[ + "Low" + ] = 0 + + current["Service"] = finding.check_metadata.ServiceName + current["Provider"] = finding.check_metadata.Provider + + if finding.status == "PASS": + pass_count += 1 + elif finding.status == "FAIL": + fail_count += 1 + if finding.check_metadata.Severity == "critical": + current["Critical"] += 1 + elif finding.check_metadata.Severity == "high": + current["High"] += 1 + elif finding.check_metadata.Severity == "medium": + current["Medium"] += 1 + elif finding.check_metadata.Severity == "low": + current["Low"] += 1 + + # Add final service + + add_service_to_table(findings_table, current) + + print("\nOverview Results:") + overview_table = [ + [ + f"{Fore.RED}{round(fail_count/len(findings)*100, 2)}% ({fail_count}) Failed{Style.RESET_ALL}", + f"{Fore.GREEN}{round(pass_count/len(findings)*100, 2)}% ({pass_count}) Passed{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + print( + f"\nAccount {Fore.YELLOW}{audit_info.audited_account}{Style.RESET_ALL} Scan Results (severity columns are for fails only):" + ) + print(tabulate(findings_table, headers="keys", tablefmt="rounded_grid")) + print( + f"{Style.BRIGHT}* You only see here those services that contains resources.{Style.RESET_ALL}" + ) + print("\nDetailed results are in:") + print(f" - CSV: {output_directory}/{output_filename}.csv") + print(f" - JSON: {output_directory}/{output_filename}.json\n") + + except Exception as error: + logger.critical( + f"{error.__class__.__name__}:{error.__traceback__.tb_lineno} -- {error}" + ) + sys.exit() + + +def add_service_to_table(findings_table, current): + if ( + current["Critical"] > 0 + or current["High"] > 0 + or current["Medium"] > 0 + or current["Low"] > 0 + ): + current["Status"] = f"{Fore.RED}FAIL{Style.RESET_ALL}" + else: + current["Status"] = f"{Fore.GREEN}PASS{Style.RESET_ALL}" + findings_table["Provider"].append(current["Provider"]) + findings_table["Service"].append(current["Service"]) + findings_table["Status"].append(current["Status"]) + findings_table["Critical"].append( + f"{Fore.LIGHTRED_EX}{current['Critical']}{Style.RESET_ALL}" + ) + findings_table["High"].append(f"{Fore.RED}{current['High']}{Style.RESET_ALL}") + findings_table["Medium"].append( + f"{Fore.YELLOW}{current['Medium']}{Style.RESET_ALL}" + ) + findings_table["Low"].append(f"{Fore.BLUE}{current['Low']}{Style.RESET_ALL}") diff --git a/lib/outputs/outputs_test.py b/lib/outputs/outputs_test.py index 3bae0191..4fbf2109 100644 --- a/lib/outputs/outputs_test.py +++ b/lib/outputs/outputs_test.py @@ -13,6 +13,7 @@ from config.config import ( prowler_version, timestamp_iso, timestamp_utc, + orange_color, ) from lib.check.models import Check_Report, load_check_metadata from lib.outputs.models import ( @@ -109,7 +110,7 @@ class Test_Outputs: def test_set_report_color(self): test_status = ["PASS", "FAIL", "ERROR", "WARNING"] - test_colors = [Fore.GREEN, Fore.RED, Fore.BLACK, Fore.YELLOW] + test_colors = [Fore.GREEN, Fore.RED, Fore.BLACK, orange_color] for status in test_status: assert set_report_color(status) in test_colors diff --git a/providers/aws/services/account/account_maintain_current_contact_details/account_maintain_current_contact_details.py b/providers/aws/services/account/account_maintain_current_contact_details/account_maintain_current_contact_details.py index b3a2bb76..f35f02b1 100644 --- a/providers/aws/services/account/account_maintain_current_contact_details/account_maintain_current_contact_details.py +++ b/providers/aws/services/account/account_maintain_current_contact_details/account_maintain_current_contact_details.py @@ -1,13 +1,14 @@ -from colorama import Fore, Style - -from lib.check.models import Check +from lib.check.models import Check, Check_Report +from providers.aws.services.account.account_client import account_client # This check has no findings since it is manual class account_maintain_current_contact_details(Check): def execute(self): - print( - f"\t{Fore.YELLOW}INFO{Style.RESET_ALL} Manual check: Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Contact Information." - ) - return [] + report = Check_Report(self.metadata) + report.region = account_client.region + report.resource_id = account_client.audited_account + report.status = "INFO" + report.status_extended = "Manual check: Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Contact Information." + return [report] diff --git a/providers/aws/services/account/account_security_contact_information_is_registered/account_security_contact_information_is_registered.py b/providers/aws/services/account/account_security_contact_information_is_registered/account_security_contact_information_is_registered.py index 0ee53411..e6e1eeeb 100644 --- a/providers/aws/services/account/account_security_contact_information_is_registered/account_security_contact_information_is_registered.py +++ b/providers/aws/services/account/account_security_contact_information_is_registered/account_security_contact_information_is_registered.py @@ -1,13 +1,14 @@ -from colorama import Fore, Style - -from lib.check.models import Check +from lib.check.models import Check, Check_Report +from providers.aws.services.account.account_client import account_client # This check has no findings since it is manual class account_security_contact_information_is_registered(Check): def execute(self): - print( - f"\t{Fore.YELLOW}INFO{Style.RESET_ALL} Manual check: Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Alternate Contacts -> Security Section." - ) - return [] + report = Check_Report(self.metadata) + report.region = account_client.region + report.resource_id = account_client.audited_account + report.status = "INFO" + report.status_extended = "Manual check: Login to the AWS Console. Choose your account name on the top right of the window -> My Account -> Alternate Contacts -> Security Section." + return [report] diff --git a/providers/aws/services/account/account_security_questions_are_registered_in_the_aws_account/account_security_questions_are_registered_in_the_aws_account.py b/providers/aws/services/account/account_security_questions_are_registered_in_the_aws_account/account_security_questions_are_registered_in_the_aws_account.py index 34efe87f..d068618b 100644 --- a/providers/aws/services/account/account_security_questions_are_registered_in_the_aws_account/account_security_questions_are_registered_in_the_aws_account.py +++ b/providers/aws/services/account/account_security_questions_are_registered_in_the_aws_account/account_security_questions_are_registered_in_the_aws_account.py @@ -1,13 +1,14 @@ -from colorama import Fore, Style - -from lib.check.models import Check +from lib.check.models import Check, Check_Report +from providers.aws.services.account.account_client import account_client # This check has no findings since it is manual class account_security_questions_are_registered_in_the_aws_account(Check): def execute(self): - print( - f"\t{Fore.YELLOW}INFO{Style.RESET_ALL} Manual check: Login to the AWS Console as root. Choose your account name on the top right of the window -> My Account -> Configure Security Challenge Questions." - ) - return [] + report = Check_Report(self.metadata) + report.region = account_client.region + report.resource_id = account_client.audited_account + report.status = "INFO" + report.status_extended = "Manual check: Login to the AWS Console as root. Choose your account name on the top right of the window -> My Account -> Configure Security Challenge Questions." + return [report] diff --git a/providers/aws/services/account/account_service.py b/providers/aws/services/account/account_service.py index 051e51ed..8c6c3d9f 100644 --- a/providers/aws/services/account/account_service.py +++ b/providers/aws/services/account/account_service.py @@ -1,9 +1,13 @@ ################## Account +from providers.aws.aws_provider import get_region_global_service + + class Account: def __init__(self, audit_info): self.service = "account" self.session = audit_info.audit_session self.audited_account = audit_info.audited_account + self.region = get_region_global_service(audit_info) def __get_session__(self): return self.session diff --git a/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json index 7ac845da..292588d2 100644 --- a/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json +++ b/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_read_enabled/cloudtrail_s3_dataevents_read_enabled.metadata.json @@ -2,8 +2,10 @@ "Provider": "aws", "CheckID": "cloudtrail_s3_dataevents_read_enabled", "CheckTitle": "Check if S3 buckets have Object-level logging for read events is enabled in CloudTrail.", - "CheckType": ["Logging and Monitoring"], - "ServiceName": "s3", + "CheckType": [ + "Logging and Monitoring" + ], + "ServiceName": "cloudtrail", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", "Severity": "low", diff --git a/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json b/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json index 153fd931..5f13d155 100644 --- a/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json +++ b/providers/aws/services/cloudtrail/cloudtrail_s3_dataevents_write_enabled/cloudtrail_s3_dataevents_write_enabled.metadata.json @@ -2,8 +2,10 @@ "Provider": "aws", "CheckID": "cloudtrail_s3_dataevents_write_enabled", "CheckTitle": "Check if S3 buckets have Object-level logging for write events is enabled in CloudTrail.", - "CheckType": ["Logging and Monitoring"], - "ServiceName": "s3", + "CheckType": [ + "Logging and Monitoring" + ], + "ServiceName": "cloudtrail", "SubServiceName": "", "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", "Severity": "low", diff --git a/providers/aws/services/ec2/lib/security_groups.py b/providers/aws/services/ec2/lib/security_groups.py index 57b92000..171419a0 100644 --- a/providers/aws/services/ec2/lib/security_groups.py +++ b/providers/aws/services/ec2/lib/security_groups.py @@ -35,14 +35,13 @@ def check_security_group( @param any_address: If True, only 0.0.0.0/0 will be public and do not search for public addresses. (Default: False) """ - # Check for all traffic ingress rules regardless of the protocol if ingress_rule["IpProtocol"] == "-1": for ip_ingress_rule in ingress_rule["IpRanges"]: if _is_cidr_public(ip_ingress_rule["CidrIp"], any_address): return True for ip_ingress_rule in ingress_rule["Ipv6Ranges"]: - if _is_cidr_public(ip_ingress_rule["CidrIp"], any_address): + if _is_cidr_public(ip_ingress_rule["CidrIpv6"], any_address): return True # Check for specific ports in ingress rules @@ -76,7 +75,7 @@ def check_security_group( # IPv6 for ip_ingress_rule in ingress_rule["Ipv6Ranges"]: - if _is_cidr_public(ip_ingress_rule["CidrIp"]): + if _is_cidr_public(ip_ingress_rule["CidrIpv6"]): # If there are input ports to check if ports: for port in ports: diff --git a/prowler b/prowler index 696a16f1..ca219eab 100755 --- a/prowler +++ b/prowler @@ -17,17 +17,16 @@ from lib.check.check import ( exclude_checks_to_run, exclude_groups_to_run, exclude_services_to_run, - import_check, + execute_checks, list_groups, list_services, print_checks, print_services, - run_check, set_output_options, ) from lib.check.checks_loader import load_checks_to_execute from lib.logger import logger, set_logging_config -from lib.outputs.outputs import close_json, send_to_s3_bucket +from lib.outputs.outputs import close_json, display_summary_table, send_to_s3_bucket from providers.aws.aws_provider import provider_set_session from providers.aws.lib.allowlist.allowlist import parse_allowlist_file from providers.aws.lib.security_hub.security_hub import ( @@ -137,6 +136,7 @@ if __name__ == "__main__": "--output-modes", nargs="+", help="Output mode, by default csv", + default=["csv", "json"], choices=["csv", "json", "json-asff"], ) parser.add_argument( @@ -194,6 +194,11 @@ if __name__ == "__main__": default=None, help="Path for allowlist yaml file, by default is 'providers/aws/allowlist.yaml'. See default yaml for reference and format.", ) + parser.add_argument( + "--verbose", + action="store_true", + help="Display detailed information about findings.", + ) # Parse Arguments args = parser.parse_args() @@ -227,7 +232,7 @@ if __name__ == "__main__": sys.exit() if args.no_banner: - print_banner() + print_banner(args) if args.list_groups: list_groups(provider) @@ -321,30 +326,14 @@ if __name__ == "__main__": args.security_hub, output_filename, allowlist_file, + args.verbose, ) # Execute checks if len(checks_to_execute): - for check_name in checks_to_execute: - # Recover service from check name - service = check_name.split("_")[0] - try: - # Import check module - check_module_path = ( - f"providers.{provider}.services.{service}.{check_name}.{check_name}" - ) - lib = import_check(check_module_path) - # Recover functions from check - check_to_execute = getattr(lib, check_name) - c = check_to_execute() - # Run check - run_check(c, audit_info, audit_output_options) - - # If check does not exists in the provider or is from another provider - except ModuleNotFoundError: - logger.error( - f"Check '{check_name}' was not found for the {provider.upper()} provider" - ) + findings = execute_checks( + checks_to_execute, provider, audit_info, audit_output_options + ) else: logger.error( "There are no checks to execute. Please, check your input arguments" @@ -374,3 +363,11 @@ if __name__ == "__main__": # Resolve previous fails of Security Hub if args.security_hub: resolve_security_hub_previous_findings(output_directory, audit_info) + + # Display summary table + display_summary_table( + findings, + audit_info, + output_filename, + output_directory, + )