Files
prowler/prowler/lib/cli/parser.py
T
varunmamillapalli 8a1d7bcd6b feat(linode): add provider with administration compute and networking services (#11633)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
2026-06-22 11:19:20 +02:00

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/).",
)