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.outputs.common import Status from prowler.providers.common.arguments import ( init_providers_parser, validate_provider_arguments, ) class ProwlerArgumentParser: # Set the default parser def __init__(self): # CLI Arguments self.parser = argparse.ArgumentParser( prog="prowler", formatter_class=RawTextHelpFormatter, usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,nhn,dashboard} ...", epilog=""" Available Cloud Providers: {aws,azure,gcp,kubernetes,m365,nhn} aws AWS Provider azure Azure Provider gcp GCP Provider kubernetes Kubernetes Provider github GitHub Provider m365 Microsoft 365 Provider nhn NHN Provider (Unofficial) 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, starting by "-", is supplied if "-" in sys.argv[1]: sys.argv = self.__set_default_provider__(sys.argv) # Provider aliases mapping # Microsoft 365 elif sys.argv[1] == "microsoft365": sys.argv[1] = "m365" # 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}") 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).", ) 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-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 ) 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.cloud/en/latest/tutorials/misc/#custom-checks).", ) def __init_list_checks_parser__(self): # List checks options list_checks_parser = self.common_providers_parser.add_argument_group( "List checks/services/categories/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-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/_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.cloud/en/latest/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.cloud/en/latest/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.", ) 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.cloud/en/latest/tutorials/integrations/#slack).", )