mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-12 15:55:09 +00:00
feat(compliance): Loader and Execute (#1465)
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import functools
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from pkgutil import walk_packages
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
@@ -6,7 +9,8 @@ from typing import Any
|
||||
from alive_progress import alive_bar
|
||||
from colorama import Fore, Style
|
||||
|
||||
from config.config import groups_file, orange_color
|
||||
from config.config import compliance_specification_dir, groups_file, orange_color
|
||||
from lib.check.compliance_models import load_compliance_framework
|
||||
from lib.check.models import Check, Output_From_Options, load_check_metadata
|
||||
from lib.logger import logger
|
||||
from lib.outputs.outputs import report
|
||||
@@ -31,6 +35,29 @@ def bulk_load_checks_metadata(provider: str) -> dict:
|
||||
return bulk_check_metadata
|
||||
|
||||
|
||||
# Bulk load all compliance frameworks specification
|
||||
def bulk_load_compliance_frameworks(provider: str) -> dict:
|
||||
"""Bulk load all compliance frameworks specification into a dict"""
|
||||
bulk_compliance_frameworks = {}
|
||||
compliance_specification_dir_path = f"{compliance_specification_dir}/{provider}"
|
||||
try:
|
||||
for filename in os.listdir(compliance_specification_dir_path):
|
||||
file_path = os.path.join(compliance_specification_dir_path, filename)
|
||||
# Check if it is a file
|
||||
if os.path.isfile(file_path):
|
||||
# Open Compliance file in JSON
|
||||
# cis_v1.4_aws.json --> cis_v1.4_aws
|
||||
compliance_framework_name = filename.split(".json")[0]
|
||||
# Store the compliance info
|
||||
bulk_compliance_frameworks[
|
||||
compliance_framework_name
|
||||
] = load_compliance_framework(file_path)
|
||||
except Exception as e:
|
||||
logger.error(f"{e.__class__.__name__} -- {e}")
|
||||
|
||||
return bulk_compliance_frameworks
|
||||
|
||||
|
||||
# Exclude checks to run
|
||||
def exclude_checks_to_run(checks_to_execute: set, excluded_checks: list) -> set:
|
||||
for check in excluded_checks:
|
||||
@@ -101,16 +128,43 @@ def print_services(service_list: set):
|
||||
print(f"- {service}")
|
||||
|
||||
|
||||
def print_checks(provider: str, check_list: set, bulk_checks_metadata: dict):
|
||||
def print_compliance_frameworks(bulk_compliance_frameworks: dict):
|
||||
print(
|
||||
f"There are {Fore.YELLOW}{len(bulk_compliance_frameworks.keys())}{Style.RESET_ALL} available Compliance Frameworks: \n"
|
||||
)
|
||||
for framework in bulk_compliance_frameworks.keys():
|
||||
print(f"\t- {Fore.YELLOW}{framework}{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def print_compliance_requirements(bulk_compliance_frameworks: dict):
|
||||
if bulk_compliance_frameworks and "ens_rd2022_aws" in bulk_compliance_frameworks:
|
||||
print("Listing ENS RD2022 AWS Compliance Requirements:\n")
|
||||
for compliance in bulk_compliance_frameworks.values():
|
||||
for requirement in compliance.Requirements:
|
||||
checks = ""
|
||||
for check in requirement.Checks:
|
||||
checks += f" {Fore.YELLOW}\t\t{check}\n{Style.RESET_ALL}"
|
||||
print(
|
||||
f"Requirement Id: {Fore.MAGENTA}{requirement.Id}{Style.RESET_ALL}\n\t- Description: {requirement.Description}\n\t- Checks:\n{checks}"
|
||||
)
|
||||
|
||||
|
||||
def print_checks(
|
||||
provider: str,
|
||||
check_list: set,
|
||||
bulk_checks_metadata: dict,
|
||||
):
|
||||
for check in check_list:
|
||||
try:
|
||||
print(
|
||||
f"[{bulk_checks_metadata[check].CheckID}] {bulk_checks_metadata[check].CheckTitle} - {Fore.MAGENTA}{bulk_checks_metadata[check].ServiceName} {Fore.YELLOW}[{bulk_checks_metadata[check].Severity}]{Style.RESET_ALL}"
|
||||
)
|
||||
except KeyError as error:
|
||||
logger.error(
|
||||
logger.critical(
|
||||
f"Check {error} was not found for the {provider.upper()} provider"
|
||||
)
|
||||
sys.exit()
|
||||
|
||||
print(
|
||||
f"\nThere are {Fore.YELLOW}{len(check_list)}{Style.RESET_ALL} available checks.\n"
|
||||
)
|
||||
@@ -150,21 +204,51 @@ def load_checks_to_execute_from_groups(
|
||||
return checks_to_execute
|
||||
|
||||
|
||||
# Parse checks from compliance frameworks specification
|
||||
def parse_checks_from_compliance_framework(
|
||||
compliance_frameworks: list, bulk_compliance_frameworks: dict
|
||||
) -> list:
|
||||
"""Parse checks from compliance frameworks specification"""
|
||||
checks_to_execute = set()
|
||||
try:
|
||||
for framework in compliance_frameworks:
|
||||
# compliance_framework_json["Requirements"][*]["Checks"]
|
||||
compliance_framework_checks_list = [
|
||||
requirement.Checks
|
||||
for requirement in bulk_compliance_frameworks[framework].Requirements
|
||||
]
|
||||
# Reduce nested list into a list
|
||||
# Pythonic functional magic
|
||||
compliance_framework_checks = functools.reduce(
|
||||
lambda x, y: x + y, compliance_framework_checks_list
|
||||
)
|
||||
# Then union this list of checks with the initial one
|
||||
checks_to_execute = checks_to_execute.union(compliance_framework_checks)
|
||||
except Exception as e:
|
||||
logger.error(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}] -- {e}")
|
||||
|
||||
return checks_to_execute
|
||||
|
||||
|
||||
# Recover all checks from the selected provider and service
|
||||
def recover_checks_from_provider(provider: str, service: str = None) -> list:
|
||||
checks = []
|
||||
modules = list_modules(provider, service)
|
||||
for module_name in modules:
|
||||
# Format: "providers.{provider}.services.{service}.{check_name}.{check_name}"
|
||||
check_name = module_name.name
|
||||
# We need to exclude common shared libraries in services
|
||||
if (
|
||||
check_name.count(".") == 5
|
||||
and "lib" not in check_name
|
||||
and "test" not in check_name
|
||||
):
|
||||
checks.append(check_name)
|
||||
return checks
|
||||
try:
|
||||
checks = []
|
||||
modules = list_modules(provider, service)
|
||||
for module_name in modules:
|
||||
# Format: "providers.{provider}.services.{service}.{check_name}.{check_name}"
|
||||
check_name = module_name.name
|
||||
# We need to exclude common shared libraries in services
|
||||
if (
|
||||
check_name.count(".") == 5
|
||||
and "lib" not in check_name
|
||||
and "test" not in check_name
|
||||
):
|
||||
checks.append(check_name)
|
||||
return checks
|
||||
except Exception as e:
|
||||
logger.critical(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}")
|
||||
sys.exit()
|
||||
|
||||
|
||||
# List all available modules in the selected provider and service
|
||||
@@ -184,6 +268,7 @@ def import_check(check_path: str) -> ModuleType:
|
||||
return lib
|
||||
|
||||
|
||||
# Sets the Output_From_Options to be used in the output modes
|
||||
def set_output_options(
|
||||
quiet: bool,
|
||||
output_modes: list,
|
||||
@@ -191,8 +276,10 @@ def set_output_options(
|
||||
security_hub_enabled: bool,
|
||||
output_filename: str,
|
||||
allowlist_file: str,
|
||||
bulk_checks_metadata: dict,
|
||||
verbose: bool,
|
||||
):
|
||||
"""Sets the Output_From_Options to be used in the output modes"""
|
||||
global output_options
|
||||
output_options = Output_From_Options(
|
||||
is_quiet=quiet,
|
||||
@@ -201,6 +288,7 @@ def set_output_options(
|
||||
security_hub_enabled=security_hub_enabled,
|
||||
output_filename=output_filename,
|
||||
allowlist_file=allowlist_file,
|
||||
bulk_checks_metadata=bulk_checks_metadata,
|
||||
verbose=verbose,
|
||||
# set input options here
|
||||
)
|
||||
@@ -211,15 +299,15 @@ 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}"
|
||||
f"\nCheck ID: {check.CheckID} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW} [{check.Severity}]{Style.RESET_ALL}"
|
||||
)
|
||||
logger.debug(f"Executing check: {check.checkID}")
|
||||
logger.debug(f"Executing check: {check.CheckID}")
|
||||
try:
|
||||
findings = check.execute()
|
||||
except Exception as error:
|
||||
print(f"Something went wrong in {check.checkID}, please use --log-level ERROR")
|
||||
print(f"Something went wrong in {check.CheckID}, please use --log-level ERROR")
|
||||
logger.error(
|
||||
f"{check.checkID} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
f"{check.CheckID} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
finally:
|
||||
return findings
|
||||
@@ -264,13 +352,14 @@ def execute_checks(
|
||||
|
||||
# If check does not exists in the provider or is from another provider
|
||||
except ModuleNotFoundError:
|
||||
logger.error(
|
||||
logger.critical(
|
||||
f"Check '{check_name}' was not found for the {provider.upper()} provider"
|
||||
)
|
||||
bar.title = f"-> {Fore.RED}Scan was aborted!{Style.RESET_ALL}"
|
||||
sys.exit()
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
bar.title = f"-> {Fore.GREEN}Scan is completed!"
|
||||
print(Style.RESET_ALL)
|
||||
bar.title = f"-> {Fore.GREEN}Scan is completed!{Style.RESET_ALL}"
|
||||
return all_findings
|
||||
|
||||
Reference in New Issue
Block a user