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:
2
Pipfile
2
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"
|
||||
|
||||
91
Pipfile.lock
generated
91
Pipfile.lock
generated
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
45
prowler
45
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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user