mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com>
953 lines
40 KiB
Python
953 lines
40 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import sys
|
|
from os import environ
|
|
|
|
from colorama import Fore, Style
|
|
from colorama import init as colorama_init
|
|
|
|
from prowler.config.config import (
|
|
csv_file_suffix,
|
|
get_available_compliance_frameworks,
|
|
html_file_suffix,
|
|
json_asff_file_suffix,
|
|
json_ocsf_file_suffix,
|
|
)
|
|
from prowler.lib.banner import print_banner
|
|
from prowler.lib.check.check import (
|
|
exclude_checks_to_run,
|
|
exclude_services_to_run,
|
|
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,
|
|
)
|
|
from prowler.lib.check.checks_loader import load_checks_to_execute
|
|
from prowler.lib.check.compliance import update_checks_metadata_with_compliance
|
|
from prowler.lib.check.compliance_models import Compliance
|
|
from prowler.lib.check.custom_checks_metadata import (
|
|
parse_custom_checks_metadata_file,
|
|
update_checks_metadata,
|
|
)
|
|
from prowler.lib.check.models import CheckMetadata
|
|
from prowler.lib.cli.parser import ProwlerArgumentParser
|
|
from prowler.lib.logger import logger, set_logging_config
|
|
from prowler.lib.outputs.asff.asff import ASFF
|
|
from prowler.lib.outputs.compliance.aws_well_architected.aws_well_architected import (
|
|
AWSWellArchitected,
|
|
)
|
|
from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS
|
|
from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS
|
|
from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
|
|
from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS
|
|
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
|
|
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
|
|
from prowler.lib.outputs.compliance.compliance import display_compliance_table
|
|
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
|
|
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
|
|
from prowler.lib.outputs.compliance.ens.ens_gcp import GCPENS
|
|
from prowler.lib.outputs.compliance.generic.generic import GenericCompliance
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_aws import AWSISO27001
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_azure import AzureISO27001
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_gcp import GCPISO27001
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_kubernetes import (
|
|
KubernetesISO27001,
|
|
)
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_m365 import M365ISO27001
|
|
from prowler.lib.outputs.compliance.iso27001.iso27001_nhn import NHNISO27001
|
|
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP
|
|
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack
|
|
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import (
|
|
AzureMitreAttack,
|
|
)
|
|
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_gcp import GCPMitreAttack
|
|
from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_aws import (
|
|
ProwlerThreatScoreAWS,
|
|
)
|
|
from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_azure import (
|
|
ProwlerThreatScoreAzure,
|
|
)
|
|
from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_gcp import (
|
|
ProwlerThreatScoreGCP,
|
|
)
|
|
from prowler.lib.outputs.compliance.prowler_threatscore.prowler_threatscore_m365 import (
|
|
ProwlerThreatScoreM365,
|
|
)
|
|
from prowler.lib.outputs.csv.csv import CSV
|
|
from prowler.lib.outputs.finding import Finding
|
|
from prowler.lib.outputs.html.html import HTML
|
|
from prowler.lib.outputs.ocsf.ocsf import OCSF
|
|
from prowler.lib.outputs.outputs import extract_findings_statistics
|
|
from prowler.lib.outputs.slack.slack import Slack
|
|
from prowler.lib.outputs.summary_table import display_summary_table
|
|
from prowler.providers.aws.lib.s3.s3 import S3
|
|
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub
|
|
from prowler.providers.aws.models import AWSOutputOptions
|
|
from prowler.providers.azure.models import AzureOutputOptions
|
|
from prowler.providers.common.provider import Provider
|
|
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
|
|
from prowler.providers.gcp.models import GCPOutputOptions
|
|
from prowler.providers.github.models import GithubOutputOptions
|
|
from prowler.providers.iac.models import IACOutputOptions
|
|
from prowler.providers.kubernetes.models import KubernetesOutputOptions
|
|
from prowler.providers.m365.models import M365OutputOptions
|
|
from prowler.providers.nhn.models import NHNOutputOptions
|
|
|
|
|
|
def prowler():
|
|
# Parse Arguments
|
|
# Refactor(CLI)
|
|
parser = ProwlerArgumentParser()
|
|
args = parser.parse()
|
|
|
|
# Save Arguments
|
|
provider = args.provider
|
|
if provider == "dashboard":
|
|
from dashboard import DASHBOARD_ARGS
|
|
from dashboard.__main__ import dashboard
|
|
|
|
sys.exit(dashboard.run(**DASHBOARD_ARGS))
|
|
|
|
checks = args.check
|
|
excluded_checks = args.excluded_check
|
|
excluded_services = args.excluded_service
|
|
services = args.service
|
|
categories = args.category
|
|
checks_file = args.checks_file
|
|
checks_folder = args.checks_folder
|
|
severities = args.severity
|
|
compliance_framework = args.compliance
|
|
custom_checks_metadata_file = args.custom_checks_metadata_file
|
|
default_execution = (
|
|
not checks
|
|
and not services
|
|
and not categories
|
|
and not excluded_checks
|
|
and not excluded_services
|
|
and not severities
|
|
and not checks_file
|
|
and not checks_folder
|
|
)
|
|
|
|
if args.no_color:
|
|
colorama_init(strip=True)
|
|
|
|
if not args.no_banner:
|
|
legend = args.verbose or getattr(args, "fixer", None)
|
|
print_banner(legend)
|
|
|
|
# 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 default_execution:
|
|
args.output_formats.extend(get_available_compliance_frameworks(provider))
|
|
|
|
# Set Logger configuration
|
|
set_logging_config(args.log_level, args.log_file, args.only_logs)
|
|
|
|
if args.list_services:
|
|
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 = CheckMetadata.get_bulk(provider)
|
|
|
|
if args.list_categories:
|
|
print_categories(list_categories(bulk_checks_metadata))
|
|
sys.exit()
|
|
|
|
bulk_compliance_frameworks = {}
|
|
# Load compliance frameworks
|
|
logger.debug("Loading compliance frameworks from .json files")
|
|
|
|
# Skip compliance frameworks for IAC provider
|
|
if provider != "iac":
|
|
bulk_compliance_frameworks = Compliance.get_bulk(provider)
|
|
# Complete checks metadata with the compliance framework specification
|
|
bulk_checks_metadata = update_checks_metadata_with_compliance(
|
|
bulk_compliance_frameworks, bulk_checks_metadata
|
|
)
|
|
|
|
# Update checks metadata if the --custom-checks-metadata-file is present
|
|
custom_checks_metadata = None
|
|
if custom_checks_metadata_file:
|
|
custom_checks_metadata = parse_custom_checks_metadata_file(
|
|
provider, custom_checks_metadata_file
|
|
)
|
|
bulk_checks_metadata = update_checks_metadata(
|
|
bulk_checks_metadata, custom_checks_metadata
|
|
)
|
|
|
|
if args.list_compliance:
|
|
print_compliance_frameworks(bulk_compliance_frameworks)
|
|
sys.exit()
|
|
if args.list_compliance_requirements:
|
|
print_compliance_requirements(
|
|
bulk_compliance_frameworks, args.list_compliance_requirements
|
|
)
|
|
sys.exit()
|
|
|
|
# Load checks to execute
|
|
checks_to_execute = load_checks_to_execute(
|
|
bulk_checks_metadata=bulk_checks_metadata,
|
|
bulk_compliance_frameworks=bulk_compliance_frameworks,
|
|
checks_file=checks_file,
|
|
check_list=checks,
|
|
service_list=services,
|
|
severities=severities,
|
|
compliance_frameworks=compliance_framework,
|
|
categories=categories,
|
|
provider=provider,
|
|
)
|
|
|
|
# if --list-checks-json, dump a json file and exit
|
|
if args.list_checks_json:
|
|
print(list_checks_json(provider, sorted(checks_to_execute)))
|
|
sys.exit()
|
|
|
|
# If -l/--list-checks passed as argument, print checks to execute and quit
|
|
if args.list_checks:
|
|
print_checks(provider, sorted(checks_to_execute), bulk_checks_metadata)
|
|
sys.exit()
|
|
|
|
# Provider to scan
|
|
Provider.init_global_provider(args)
|
|
global_provider = Provider.get_global_provider()
|
|
|
|
# Print Provider Credentials
|
|
if not args.only_logs:
|
|
global_provider.print_credentials()
|
|
|
|
# Skip service and check loading for IAC provider
|
|
if provider != "iac":
|
|
# Import custom checks from folder
|
|
if checks_folder:
|
|
custom_checks = parse_checks_from_folder(global_provider, checks_folder)
|
|
# Workaround to be able to execute custom checks alongside all checks if nothing is explicitly set
|
|
if (
|
|
not checks_file
|
|
and not checks
|
|
and not services
|
|
and not severities
|
|
and not compliance_framework
|
|
and not categories
|
|
):
|
|
checks_to_execute.update(custom_checks)
|
|
|
|
# Exclude checks if -e/--excluded-checks
|
|
if excluded_checks:
|
|
checks_to_execute = exclude_checks_to_run(
|
|
checks_to_execute, excluded_checks
|
|
)
|
|
|
|
# Exclude services if --excluded-services
|
|
if excluded_services:
|
|
checks_to_execute = exclude_services_to_run(
|
|
checks_to_execute, excluded_services, provider
|
|
)
|
|
|
|
# Once the provider is set and we have the eventual checks based on the resource identifier,
|
|
# it is time to check what Prowler's checks are going to be executed
|
|
checks_from_resources = (
|
|
global_provider.get_checks_to_execute_by_audit_resources()
|
|
)
|
|
# Intersect checks from resources with checks to execute so we only run the checks that apply to the resources with the specified ARNs or tags
|
|
if getattr(args, "resource_arn", None) or getattr(args, "resource_tag", None):
|
|
checks_to_execute = checks_to_execute.intersection(checks_from_resources)
|
|
|
|
# Sort final check list
|
|
checks_to_execute = sorted(checks_to_execute)
|
|
|
|
# Setup Output Options
|
|
if provider == "aws":
|
|
output_options = AWSOutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "azure":
|
|
output_options = AzureOutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "gcp":
|
|
output_options = GCPOutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "kubernetes":
|
|
output_options = KubernetesOutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "github":
|
|
output_options = GithubOutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "m365":
|
|
output_options = M365OutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "nhn":
|
|
output_options = NHNOutputOptions(
|
|
args, bulk_checks_metadata, global_provider.identity
|
|
)
|
|
elif provider == "iac":
|
|
output_options = IACOutputOptions(args, bulk_checks_metadata)
|
|
|
|
# Run the quick inventory for the provider if available
|
|
if hasattr(args, "quick_inventory") and args.quick_inventory:
|
|
run_provider_quick_inventory(global_provider, args)
|
|
sys.exit()
|
|
|
|
# Execute checks
|
|
findings = []
|
|
|
|
if provider == "iac":
|
|
# For IAC provider, run the scan directly
|
|
findings = global_provider.run()
|
|
elif len(checks_to_execute):
|
|
findings = execute_checks(
|
|
checks_to_execute,
|
|
global_provider,
|
|
custom_checks_metadata,
|
|
args.config_file,
|
|
output_options,
|
|
)
|
|
else:
|
|
logger.error(
|
|
"There are no checks to execute. Please, check your input arguments"
|
|
)
|
|
|
|
# Prowler Fixer
|
|
if 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):
|
|
fixed_findings = run_fixer(findings)
|
|
if not fixed_findings:
|
|
print(
|
|
f"{Style.BRIGHT}{Fore.RED}\nThere were findings to fix, but the fixer failed or it is not implemented for those findings yet. {Style.RESET_ALL}\n"
|
|
)
|
|
else:
|
|
print(
|
|
f"{Style.BRIGHT}{Fore.GREEN}\n{fixed_findings} findings fixed!{Style.RESET_ALL}\n"
|
|
)
|
|
else:
|
|
print(f"{Style.BRIGHT}{Fore.GREEN}\nNo findings to fix!{Style.RESET_ALL}\n")
|
|
sys.exit()
|
|
|
|
# Outputs
|
|
# TODO: this part is needed since the checks generates a Check_Report_XXX and the output uses Finding
|
|
# This will be refactored for the outputs generate directly the Finding
|
|
finding_outputs = []
|
|
for finding in findings:
|
|
try:
|
|
finding_outputs.append(
|
|
Finding.generate_output(global_provider, finding, output_options)
|
|
)
|
|
except Exception:
|
|
continue
|
|
|
|
# Extract findings stats
|
|
stats = extract_findings_statistics(finding_outputs)
|
|
|
|
if args.slack:
|
|
# TODO: this should be also in a config file
|
|
if "SLACK_API_TOKEN" in environ and (
|
|
"SLACK_CHANNEL_NAME" in environ or "SLACK_CHANNEL_ID" in environ
|
|
):
|
|
token = environ["SLACK_API_TOKEN"]
|
|
channel = (
|
|
environ["SLACK_CHANNEL_NAME"]
|
|
if "SLACK_CHANNEL_NAME" in environ
|
|
else environ["SLACK_CHANNEL_ID"]
|
|
)
|
|
prowler_args = " ".join(sys.argv[1:])
|
|
slack = Slack(token, channel, global_provider)
|
|
_ = slack.send(stats, prowler_args)
|
|
else:
|
|
# Refactor(CLI)
|
|
logger.critical(
|
|
"Slack integration needs SLACK_API_TOKEN and SLACK_CHANNEL_NAME environment variables (see more in https://docs.prowler.cloud/en/latest/tutorials/integrations/#slack)."
|
|
)
|
|
sys.exit(1)
|
|
|
|
generated_outputs = {"regular": [], "compliance": []}
|
|
|
|
if args.output_formats:
|
|
for mode in args.output_formats:
|
|
filename = (
|
|
f"{output_options.output_directory}/{output_options.output_filename}"
|
|
)
|
|
if mode == "csv":
|
|
csv_output = CSV(
|
|
findings=finding_outputs,
|
|
file_path=f"{filename}{csv_file_suffix}",
|
|
)
|
|
generated_outputs["regular"].append(csv_output)
|
|
# Write CSV Finding Object to file
|
|
csv_output.batch_write_data_to_file()
|
|
|
|
if mode == "json-asff":
|
|
asff_output = ASFF(
|
|
findings=finding_outputs,
|
|
file_path=f"{filename}{json_asff_file_suffix}",
|
|
)
|
|
generated_outputs["regular"].append(asff_output)
|
|
# Write ASFF Finding Object to file
|
|
asff_output.batch_write_data_to_file()
|
|
|
|
if mode == "json-ocsf":
|
|
json_output = OCSF(
|
|
findings=finding_outputs,
|
|
file_path=f"{filename}{json_ocsf_file_suffix}",
|
|
)
|
|
generated_outputs["regular"].append(json_output)
|
|
json_output.batch_write_data_to_file()
|
|
if mode == "html":
|
|
html_output = HTML(
|
|
findings=finding_outputs,
|
|
file_path=f"{filename}{html_file_suffix}",
|
|
)
|
|
generated_outputs["regular"].append(html_output)
|
|
html_output.batch_write_data_to_file(
|
|
provider=global_provider, stats=stats
|
|
)
|
|
|
|
# Compliance Frameworks
|
|
input_compliance_frameworks = set(output_options.output_modes).intersection(
|
|
get_available_compliance_frameworks(provider)
|
|
)
|
|
if provider == "aws":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("cis_"):
|
|
# Generate CIS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
cis = AWSCIS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(cis)
|
|
cis.batch_write_data_to_file()
|
|
elif compliance_name == "mitre_attack_aws":
|
|
# Generate MITRE ATT&CK Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
mitre_attack = AWSMitreAttack(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(mitre_attack)
|
|
mitre_attack.batch_write_data_to_file()
|
|
elif compliance_name.startswith("ens_"):
|
|
# Generate ENS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
ens = AWSENS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(ens)
|
|
ens.batch_write_data_to_file()
|
|
elif compliance_name.startswith("aws_well_architected_framework"):
|
|
# Generate AWS Well-Architected Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
aws_well_architected = AWSWellArchitected(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(aws_well_architected)
|
|
aws_well_architected.batch_write_data_to_file()
|
|
elif compliance_name.startswith("iso27001_"):
|
|
# Generate ISO27001 Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
iso27001 = AWSISO27001(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(iso27001)
|
|
iso27001.batch_write_data_to_file()
|
|
elif compliance_name.startswith("kisa"):
|
|
# Generate KISA-ISMS-P Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
kisa_ismsp = AWSKISAISMSP(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(kisa_ismsp)
|
|
kisa_ismsp.batch_write_data_to_file()
|
|
elif compliance_name == "prowler_threatscore_aws":
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
prowler_threatscore = ProwlerThreatScoreAWS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(prowler_threatscore)
|
|
prowler_threatscore.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
elif provider == "azure":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("cis_"):
|
|
# Generate CIS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
cis = AzureCIS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(cis)
|
|
cis.batch_write_data_to_file()
|
|
elif compliance_name == "mitre_attack_azure":
|
|
# Generate MITRE ATT&CK Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
mitre_attack = AzureMitreAttack(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(mitre_attack)
|
|
mitre_attack.batch_write_data_to_file()
|
|
elif compliance_name.startswith("ens_"):
|
|
# Generate ENS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
ens = AzureENS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(ens)
|
|
ens.batch_write_data_to_file()
|
|
elif compliance_name.startswith("iso27001_"):
|
|
# Generate ISO27001 Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
iso27001 = AzureISO27001(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(iso27001)
|
|
iso27001.batch_write_data_to_file()
|
|
elif compliance_name == "prowler_threatscore_azure":
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
prowler_threatscore = ProwlerThreatScoreAzure(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(prowler_threatscore)
|
|
prowler_threatscore.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
elif provider == "gcp":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("cis_"):
|
|
# Generate CIS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
cis = GCPCIS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(cis)
|
|
cis.batch_write_data_to_file()
|
|
elif compliance_name == "mitre_attack_gcp":
|
|
# Generate MITRE ATT&CK Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
mitre_attack = GCPMitreAttack(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(mitre_attack)
|
|
mitre_attack.batch_write_data_to_file()
|
|
elif compliance_name.startswith("ens_"):
|
|
# Generate ENS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
ens = GCPENS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(ens)
|
|
ens.batch_write_data_to_file()
|
|
elif compliance_name.startswith("iso27001_"):
|
|
# Generate ISO27001 Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
iso27001 = GCPISO27001(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(iso27001)
|
|
iso27001.batch_write_data_to_file()
|
|
elif compliance_name == "prowler_threatscore_gcp":
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
prowler_threatscore = ProwlerThreatScoreGCP(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(prowler_threatscore)
|
|
prowler_threatscore.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
elif provider == "kubernetes":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("cis_"):
|
|
# Generate CIS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
cis = KubernetesCIS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(cis)
|
|
cis.batch_write_data_to_file()
|
|
elif compliance_name.startswith("iso27001_"):
|
|
# Generate ISO27001 Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
iso27001 = KubernetesISO27001(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(iso27001)
|
|
iso27001.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
elif provider == "m365":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("cis_"):
|
|
# Generate CIS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
cis = M365CIS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(cis)
|
|
cis.batch_write_data_to_file()
|
|
elif compliance_name == "prowler_threatscore_m365":
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
prowler_threatscore = ProwlerThreatScoreM365(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(prowler_threatscore)
|
|
prowler_threatscore.batch_write_data_to_file()
|
|
elif compliance_name.startswith("iso27001_"):
|
|
# Generate ISO27001 Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
iso27001 = M365ISO27001(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(iso27001)
|
|
iso27001.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
elif provider == "nhn":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("iso27001_"):
|
|
# Generate ISO27001 Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
iso27001 = NHNISO27001(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(iso27001)
|
|
iso27001.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
elif provider == "github":
|
|
for compliance_name in input_compliance_frameworks:
|
|
if compliance_name.startswith("cis_"):
|
|
# Generate CIS Finding Object
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
cis = GithubCIS(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(cis)
|
|
cis.batch_write_data_to_file()
|
|
else:
|
|
filename = (
|
|
f"{output_options.output_directory}/compliance/"
|
|
f"{output_options.output_filename}_{compliance_name}.csv"
|
|
)
|
|
generic_compliance = GenericCompliance(
|
|
findings=finding_outputs,
|
|
compliance=bulk_compliance_frameworks[compliance_name],
|
|
create_file_descriptor=True,
|
|
file_path=filename,
|
|
)
|
|
generated_outputs["compliance"].append(generic_compliance)
|
|
generic_compliance.batch_write_data_to_file()
|
|
|
|
# AWS Security Hub Integration
|
|
if provider == "aws":
|
|
# Send output to S3 if needed (-B / -D) for all the output formats
|
|
if args.output_bucket or args.output_bucket_no_assume:
|
|
output_bucket = args.output_bucket
|
|
bucket_session = global_provider.session.current_session
|
|
# Check if -D was input
|
|
if args.output_bucket_no_assume:
|
|
output_bucket = args.output_bucket_no_assume
|
|
bucket_session = global_provider.session.original_session
|
|
s3 = S3(
|
|
session=bucket_session,
|
|
bucket_name=output_bucket,
|
|
output_directory=args.output_directory,
|
|
)
|
|
s3.send_to_bucket(generated_outputs)
|
|
if args.security_hub:
|
|
print(
|
|
f"{Style.BRIGHT}\nSending findings to AWS Security Hub, please wait...{Style.RESET_ALL}"
|
|
)
|
|
|
|
security_hub_regions = (
|
|
global_provider.get_available_aws_service_regions(
|
|
"securityhub",
|
|
global_provider.identity.partition,
|
|
global_provider.identity.audited_regions,
|
|
)
|
|
if not global_provider.identity.audited_regions
|
|
else global_provider.identity.audited_regions
|
|
)
|
|
|
|
security_hub = SecurityHub(
|
|
aws_account_id=global_provider.identity.account,
|
|
aws_partition=global_provider.identity.partition,
|
|
aws_session=global_provider.session.current_session,
|
|
findings=asff_output.data,
|
|
send_only_fails=output_options.send_sh_only_fails,
|
|
aws_security_hub_available_regions=security_hub_regions,
|
|
)
|
|
# Send the findings to Security Hub
|
|
findings_sent_to_security_hub = security_hub.batch_send_to_security_hub()
|
|
print(
|
|
f"{Style.BRIGHT}{Fore.GREEN}\n{findings_sent_to_security_hub} findings sent to AWS Security Hub!{Style.RESET_ALL}"
|
|
)
|
|
|
|
# Resolve previous fails of Security Hub
|
|
if not args.skip_sh_update:
|
|
print(
|
|
f"{Style.BRIGHT}\nArchiving previous findings in AWS Security Hub, please wait...{Style.RESET_ALL}"
|
|
)
|
|
findings_archived_in_security_hub = (
|
|
security_hub.archive_previous_findings()
|
|
)
|
|
print(
|
|
f"{Style.BRIGHT}{Fore.GREEN}\n{findings_archived_in_security_hub} findings archived in AWS Security Hub!{Style.RESET_ALL}"
|
|
)
|
|
|
|
# Display summary table
|
|
if not args.only_logs:
|
|
display_summary_table(
|
|
findings,
|
|
global_provider,
|
|
output_options,
|
|
)
|
|
# Only display compliance table if there are findings (not all MANUAL) and it is a default execution
|
|
if (
|
|
findings and not all(finding.status == "MANUAL" for finding in findings)
|
|
) and default_execution:
|
|
compliance_overview = False
|
|
if not compliance_framework:
|
|
compliance_framework = get_available_compliance_frameworks(provider)
|
|
if (
|
|
compliance_framework
|
|
): # If there are compliance frameworks, print compliance overview
|
|
compliance_overview = True
|
|
for compliance in sorted(compliance_framework):
|
|
# Display compliance table
|
|
display_compliance_table(
|
|
findings,
|
|
bulk_checks_metadata,
|
|
compliance,
|
|
output_options.output_filename,
|
|
output_options.output_directory,
|
|
compliance_overview,
|
|
)
|
|
if compliance_overview:
|
|
print(
|
|
f"\nDetailed compliance results are in {Fore.YELLOW}{output_options.output_directory}/compliance/{Style.RESET_ALL}\n"
|
|
)
|
|
|
|
# If custom checks were passed, remove the modules
|
|
if checks_folder:
|
|
remove_custom_checks_module(checks_folder, provider)
|
|
|
|
# If there are failed findings exit code 3, except if -z is input
|
|
if (
|
|
not args.ignore_exit_code_3
|
|
and stats["total_fail"] > 0
|
|
and not stats["all_fails_are_muted"]
|
|
):
|
|
sys.exit(3)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
prowler()
|