mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
feat(display): add progress bar and summary table (#1512)
Co-authored-by: sergargar <sergio@verica.io>
This commit is contained in:
@@ -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}
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +16,7 @@ class Output_From_Options:
|
||||
security_hub_enabled: bool
|
||||
output_filename: str
|
||||
allowlist_file: str
|
||||
verbose: str
|
||||
|
||||
|
||||
# Testing Pending
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user