mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
8a1d7bcd6b
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com> Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
506 lines
19 KiB
Python
506 lines
19 KiB
Python
import argparse
|
|
import sys
|
|
from argparse import RawTextHelpFormatter
|
|
|
|
from dashboard.lib.arguments.arguments import init_dashboard_parser
|
|
from prowler.config.config import (
|
|
available_compliance_frameworks,
|
|
available_output_formats,
|
|
check_current_version,
|
|
default_config_file_path,
|
|
default_fixer_config_file_path,
|
|
default_output_directory,
|
|
)
|
|
from prowler.lib.check.models import Severity
|
|
from prowler.lib.cli.redact import warn_sensitive_argument_values
|
|
from prowler.lib.outputs.common import Status
|
|
from prowler.providers.common.arguments import (
|
|
PROVIDER_ALIASES,
|
|
enforce_invoked_provider_loaded,
|
|
init_providers_parser,
|
|
validate_asff_usage,
|
|
validate_provider_arguments,
|
|
validate_sarif_usage,
|
|
)
|
|
from prowler.providers.common.provider import Provider
|
|
|
|
|
|
class ProwlerArgumentParser:
|
|
# Set the default parser
|
|
def __init__(self):
|
|
# Discover any providers not in the hardcoded list below
|
|
# TODO - First step to support current providers and the new external provider implementation
|
|
known_providers = {
|
|
"aws",
|
|
"azure",
|
|
"gcp",
|
|
"kubernetes",
|
|
"m365",
|
|
"github",
|
|
"googleworkspace",
|
|
"cloudflare",
|
|
"oraclecloud",
|
|
"openstack",
|
|
"alibabacloud",
|
|
"iac",
|
|
"llm",
|
|
"image",
|
|
"nhn",
|
|
"mongodbatlas",
|
|
"vercel",
|
|
"okta",
|
|
"scaleway",
|
|
"stackit",
|
|
"linode",
|
|
}
|
|
all_providers = set(Provider.get_available_providers())
|
|
new_providers = sorted(all_providers - known_providers)
|
|
|
|
# Build extra strings for dynamically discovered providers
|
|
extra_providers_csv = ""
|
|
extra_providers_text = ""
|
|
if new_providers:
|
|
providers_help = Provider.get_providers_help_text()
|
|
extra_providers_csv = "," + ",".join(new_providers)
|
|
extra_lines = []
|
|
for name in new_providers:
|
|
help_text = providers_help.get(name, "")
|
|
if help_text:
|
|
extra_lines.append(f" {name:<20}{help_text}")
|
|
if extra_lines:
|
|
extra_providers_text = "\n" + "\n".join(extra_lines)
|
|
|
|
# CLI Arguments
|
|
self.parser = argparse.ArgumentParser(
|
|
prog="prowler",
|
|
formatter_class=RawTextHelpFormatter,
|
|
usage=f"prowler [-h] [--version] {{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,linode,dashboard,iac,image,llm{extra_providers_csv}}} ...",
|
|
epilog=f"""
|
|
Available Cloud Providers:
|
|
{{aws,azure,gcp,kubernetes,m365,github,googleworkspace,okta,iac,llm,image,nhn,mongodbatlas,oraclecloud,alibabacloud,cloudflare,openstack,scaleway,stackit,vercel,linode{extra_providers_csv}}}
|
|
aws AWS Provider
|
|
azure Azure Provider
|
|
gcp GCP Provider
|
|
kubernetes Kubernetes Provider
|
|
m365 Microsoft 365 Provider
|
|
github GitHub Provider
|
|
googleworkspace Google Workspace Provider
|
|
okta Okta Provider
|
|
cloudflare Cloudflare Provider
|
|
oraclecloud Oracle Cloud Infrastructure Provider
|
|
openstack OpenStack Provider
|
|
stackit StackIT Provider
|
|
alibabacloud Alibaba Cloud Provider
|
|
iac IaC Provider
|
|
llm LLM Provider (Beta)
|
|
image Container Image Provider
|
|
nhn NHN Provider (Unofficial)
|
|
mongodbatlas MongoDB Atlas Provider
|
|
scaleway Scaleway Provider
|
|
vercel Vercel Provider
|
|
linode Linode Provider{extra_providers_text}
|
|
|
|
|
|
Available components:
|
|
dashboard Local dashboard
|
|
|
|
To see the different available options on a specific component, run:
|
|
prowler {{provider|dashboard}} -h|--help
|
|
|
|
Detailed documentation at https://docs.prowler.com
|
|
""",
|
|
)
|
|
# Default
|
|
self.parser.add_argument(
|
|
"--version",
|
|
"-v",
|
|
action="store_true",
|
|
help="show Prowler version",
|
|
)
|
|
# Common arguments parser
|
|
self.common_providers_parser = argparse.ArgumentParser(add_help=False)
|
|
|
|
# Providers Parser
|
|
self.subparsers = self.parser.add_subparsers(
|
|
title="Available Cloud Providers", dest="provider", help=argparse.SUPPRESS
|
|
)
|
|
|
|
self.__init_outputs_parser__()
|
|
self.__init_logging_parser__()
|
|
self.__init_checks_parser__()
|
|
self.__init_exclude_checks_parser__()
|
|
self.__init_list_checks_parser__()
|
|
self.__init_mutelist_parser__()
|
|
self.__init_config_parser__()
|
|
self.__init_custom_checks_metadata_parser__()
|
|
self.__init_third_party_integrations_parser__()
|
|
|
|
# Init Providers Arguments
|
|
init_providers_parser(self)
|
|
|
|
# Dashboard Parser
|
|
init_dashboard_parser(self)
|
|
|
|
def parse(self, args=None) -> argparse.Namespace:
|
|
"""
|
|
parse is a wrapper to call parse_args() and do some validation
|
|
"""
|
|
# We can override sys.argv
|
|
if args:
|
|
sys.argv = args
|
|
|
|
if len(sys.argv) == 2 and sys.argv[1] in ("-v", "--version"):
|
|
print(check_current_version())
|
|
sys.exit(0)
|
|
|
|
# Set AWS as the default provider if no provider is supplied
|
|
if len(sys.argv) == 1:
|
|
sys.argv = self.__set_default_provider__(sys.argv)
|
|
|
|
# Help and Version flags cannot set a default provider
|
|
if (
|
|
len(sys.argv) >= 2
|
|
and (sys.argv[1] not in ("-h", "--help"))
|
|
and (sys.argv[1] not in ("-v", "--version"))
|
|
):
|
|
# Since the provider is always the second argument, we are checking if
|
|
# a flag is supplied. Use startswith("-") instead of "in" to avoid
|
|
# matching external provider names that contain hyphens
|
|
# (e.g. "local-acme-snowflake").
|
|
if sys.argv[1].startswith("-"):
|
|
sys.argv = self.__set_default_provider__(sys.argv)
|
|
|
|
# Provider aliases mapping (single source: arguments.PROVIDER_ALIASES)
|
|
elif sys.argv[1] in PROVIDER_ALIASES:
|
|
sys.argv[1] = PROVIDER_ALIASES[sys.argv[1]]
|
|
|
|
# Selective fail-loud here (post argv-normalisation, pre parse_args)
|
|
# so the invoked-provider check stays correct under parse(args=...).
|
|
enforce_invoked_provider_loaded(self)
|
|
|
|
# Warn about sensitive flags passed with explicit values
|
|
# Snapshot argv before parse_args() which may exit on errors
|
|
warn_sensitive_argument_values(list(sys.argv[1:]))
|
|
|
|
# Parse arguments
|
|
args = self.parser.parse_args()
|
|
|
|
# A provider is always required
|
|
if not args.provider:
|
|
self.parser.error(
|
|
"A provider/component is required to see its specific help options."
|
|
)
|
|
|
|
# Only Logging Configuration
|
|
if args.provider != "dashboard" and (args.only_logs or args.list_checks_json):
|
|
args.no_banner = True
|
|
|
|
# Extra validation for provider arguments
|
|
valid, message = validate_provider_arguments(args)
|
|
if not valid:
|
|
self.parser.error(f"{args.provider}: {message}")
|
|
|
|
asff_is_valid, asff_error = validate_asff_usage(
|
|
args.provider, getattr(args, "output_formats", None)
|
|
)
|
|
if not asff_is_valid:
|
|
self.parser.error(asff_error)
|
|
|
|
sarif_is_valid, sarif_error = validate_sarif_usage(
|
|
args.provider, getattr(args, "output_formats", None)
|
|
)
|
|
if not sarif_is_valid:
|
|
self.parser.error(sarif_error)
|
|
|
|
return args
|
|
|
|
def __set_default_provider__(self, args: list) -> list:
|
|
default_args = [args[0]]
|
|
provider = "aws"
|
|
default_args.append(provider)
|
|
default_args.extend(args[1:])
|
|
# Save the arguments with the default provider included
|
|
return default_args
|
|
|
|
def __init_outputs_parser__(self):
|
|
# Outputs
|
|
common_outputs_parser = self.common_providers_parser.add_argument_group(
|
|
"Outputs"
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--status",
|
|
nargs="+",
|
|
help=f"Filter by the status of the findings {[status.value for status in Status]}",
|
|
choices=[status.value for status in Status],
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--output-formats",
|
|
"--output-modes",
|
|
"-M",
|
|
nargs="+",
|
|
help="Output modes, by default csv and json-oscf are saved. When using AWS Security Hub integration, json-asff output is also saved.",
|
|
default=["csv", "json-ocsf", "html"],
|
|
choices=available_output_formats,
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--output-filename",
|
|
"-F",
|
|
nargs="?",
|
|
help="Custom output report name without the file extension, if not specified will use default output/prowler-output-ACCOUNT_NUM-OUTPUT_DATE.format",
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--output-directory",
|
|
"-o",
|
|
nargs="?",
|
|
help="Custom output directory, by default the folder where Prowler is stored",
|
|
default=default_output_directory,
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--verbose",
|
|
action="store_true",
|
|
help="Runs showing all checks executed and results",
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--ignore-exit-code-3",
|
|
"-z",
|
|
action="store_true",
|
|
help="Failed checks do not trigger exit code 3",
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--no-banner", "-b", action="store_true", help="Hide Prowler banner"
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--no-color",
|
|
action="store_true",
|
|
help="Disable color codes in output",
|
|
)
|
|
|
|
common_outputs_parser.add_argument(
|
|
"--unix-timestamp",
|
|
action="store_true",
|
|
default=False,
|
|
help="Set the output timestamp format as unix timestamps instead of iso format timestamps (default mode).",
|
|
)
|
|
common_outputs_parser.add_argument(
|
|
"--push-to-cloud",
|
|
action="store_true",
|
|
help=(
|
|
"Send findings in OCSF format to Prowler Cloud. "
|
|
"Requires PROWLER_CLOUD_API_KEY environment variable. "
|
|
"For the IaC provider, --provider-uid is also required. "
|
|
"More details here: https://goto.prowler.com/import-findings"
|
|
),
|
|
)
|
|
|
|
def __init_logging_parser__(self):
|
|
# Logging Options
|
|
# Both options can be combined to only report to file some log level
|
|
common_logging_parser = self.common_providers_parser.add_argument_group(
|
|
"Logging"
|
|
)
|
|
common_logging_parser.add_argument(
|
|
"--log-level",
|
|
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
default="CRITICAL",
|
|
help="Select Log Level",
|
|
)
|
|
common_logging_parser.add_argument(
|
|
"--log-file",
|
|
nargs="?",
|
|
help="Set log file name",
|
|
)
|
|
common_logging_parser.add_argument(
|
|
"--only-logs",
|
|
action="store_true",
|
|
help="Print only Prowler logs by the stdout. This option sets --no-banner.",
|
|
)
|
|
|
|
def __init_exclude_checks_parser__(self):
|
|
# Exclude checks options
|
|
exclude_checks_parser = self.common_providers_parser.add_argument_group(
|
|
"Exclude checks/services to run"
|
|
)
|
|
exclude_checks_parser.add_argument(
|
|
"--excluded-check",
|
|
"--excluded-checks",
|
|
"-e",
|
|
nargs="+",
|
|
help="Checks to exclude",
|
|
)
|
|
exclude_checks_parser.add_argument(
|
|
"--excluded-checks-file",
|
|
nargs="?",
|
|
help="JSON file containing the checks to be excluded. See config/checklist_example.json",
|
|
)
|
|
exclude_checks_parser.add_argument(
|
|
"--excluded-service",
|
|
"--excluded-services",
|
|
nargs="+",
|
|
help="Services to exclude",
|
|
)
|
|
|
|
def __init_checks_parser__(self):
|
|
# Set checks to execute
|
|
common_checks_parser = self.common_providers_parser.add_argument_group(
|
|
"Specify checks/services to run"
|
|
)
|
|
# The following arguments needs to be set exclusivelly
|
|
group = common_checks_parser.add_mutually_exclusive_group()
|
|
group.add_argument(
|
|
"--check",
|
|
"--checks",
|
|
"-c",
|
|
nargs="+",
|
|
help="List of checks to be executed.",
|
|
)
|
|
group.add_argument(
|
|
"--checks-file",
|
|
"-C",
|
|
nargs="?",
|
|
help="JSON file containing the checks to be executed. See config/checklist_example.json",
|
|
)
|
|
group.add_argument(
|
|
"--service",
|
|
"--services",
|
|
"-s",
|
|
nargs="+",
|
|
help="List of services to be executed.",
|
|
)
|
|
common_checks_parser.add_argument(
|
|
"--severity",
|
|
"--severities",
|
|
nargs="+",
|
|
help=f"Severities to be executed {[severity.value for severity in Severity]}",
|
|
choices=[severity.value for severity in Severity],
|
|
)
|
|
group.add_argument(
|
|
"--compliance",
|
|
nargs="+",
|
|
help="Compliance Framework to check against for. The format should be the following: framework_version_provider (e.g.: cis_3.0_aws)",
|
|
choices=available_compliance_frameworks,
|
|
)
|
|
group.add_argument(
|
|
"--category",
|
|
"--categories",
|
|
nargs="+",
|
|
help="List of categories to be executed.",
|
|
default=[],
|
|
# TODO: Pending validate choices
|
|
)
|
|
group.add_argument(
|
|
"--resource-group",
|
|
"--resource-groups",
|
|
nargs="+",
|
|
help="List of resource groups to be executed.",
|
|
default=[],
|
|
)
|
|
common_checks_parser.add_argument(
|
|
"--checks-folder",
|
|
"-x",
|
|
nargs="?",
|
|
help="Specify external directory with custom checks (each check must have a folder with the required files, see more in https://docs.prowler.com/user-guide/cli/tutorials/misc#custom-checks-in-prowler).",
|
|
)
|
|
|
|
def __init_list_checks_parser__(self):
|
|
# List checks options
|
|
list_checks_parser = self.common_providers_parser.add_argument_group(
|
|
"List checks/services/categories/resource-groups/compliance-framework checks"
|
|
)
|
|
list_group = list_checks_parser.add_mutually_exclusive_group()
|
|
list_group.add_argument(
|
|
"--list-checks", "-l", action="store_true", help="List checks"
|
|
)
|
|
list_group.add_argument(
|
|
"--list-checks-json",
|
|
action="store_true",
|
|
help="Output a list of checks in json format to use with --checks-file option",
|
|
)
|
|
list_group.add_argument(
|
|
"--list-services",
|
|
action="store_true",
|
|
help="List covered services by given provider",
|
|
)
|
|
list_group.add_argument(
|
|
"--list-compliance",
|
|
"--list-compliances",
|
|
action="store_true",
|
|
help="List all available compliance frameworks",
|
|
)
|
|
list_group.add_argument(
|
|
"--list-compliance-requirements",
|
|
nargs="+",
|
|
help="List requirements and checks per compliance framework",
|
|
choices=available_compliance_frameworks,
|
|
)
|
|
list_group.add_argument(
|
|
"--list-categories",
|
|
action="store_true",
|
|
help="List the available check's categories",
|
|
)
|
|
list_group.add_argument(
|
|
"--list-resource-groups",
|
|
action="store_true",
|
|
help="List the available check's resource groups",
|
|
)
|
|
list_group.add_argument(
|
|
"--list-fixer",
|
|
"--list-fixers",
|
|
"--list-remediations",
|
|
action="store_true",
|
|
help="List fixers available for the provider",
|
|
)
|
|
|
|
def __init_mutelist_parser__(self):
|
|
mutelist_subparser = self.common_providers_parser.add_argument_group("Mutelist")
|
|
mutelist_subparser.add_argument(
|
|
"--mutelist-file",
|
|
"-w",
|
|
nargs="?",
|
|
help="Path for mutelist YAML file. See example prowler/config/<provider>_mutelist.yaml for reference and format. For AWS provider, it also accepts AWS DynamoDB Table, Lambda ARNs or S3 URIs, see more in https://docs.prowler.com/user-guide/cli/tutorials/mutelist",
|
|
)
|
|
|
|
def __init_config_parser__(self):
|
|
config_parser = self.common_providers_parser.add_argument_group("Configuration")
|
|
config_parser.add_argument(
|
|
"--config-file",
|
|
nargs="?",
|
|
default=default_config_file_path,
|
|
help="Set configuration file path",
|
|
)
|
|
config_parser.add_argument(
|
|
"--fixer-config",
|
|
nargs="?",
|
|
default=default_fixer_config_file_path,
|
|
help="Set configuration fixer file path",
|
|
)
|
|
|
|
def __init_custom_checks_metadata_parser__(self):
|
|
# CustomChecksMetadata
|
|
custom_checks_metadata_subparser = (
|
|
self.common_providers_parser.add_argument_group("Custom Checks Metadata")
|
|
)
|
|
custom_checks_metadata_subparser.add_argument(
|
|
"--custom-checks-metadata-file",
|
|
nargs="?",
|
|
default=None,
|
|
help="Path for the custom checks metadata YAML file. See example prowler/config/custom_checks_metadata_example.yaml for reference and format. See more in https://docs.prowler.com/user-guide/cli/tutorials/custom-checks-metadata/",
|
|
)
|
|
|
|
def __init_third_party_integrations_parser__(self):
|
|
third_party_subparser = self.common_providers_parser.add_argument_group(
|
|
"3rd Party Integrations"
|
|
)
|
|
third_party_subparser.add_argument(
|
|
"--shodan",
|
|
"-N",
|
|
nargs="?",
|
|
default=None,
|
|
metavar="SHODAN_API_KEY",
|
|
help="Check if any public IPs in your Cloud environments are exposed in Shodan. We recommend to use the SHODAN_API_KEY environment variable to provide the API key.",
|
|
)
|
|
third_party_subparser.add_argument(
|
|
"--slack",
|
|
action="store_true",
|
|
help="Send a summary of the execution with a Slack APP in your channel. Environment variables SLACK_API_TOKEN and SLACK_CHANNEL_NAME are required (see more in https://docs.prowler.com/user-guide/cli/tutorials/integrations#configuration-of-the-integration-with-slack/).",
|
|
)
|