From 7ce54f0d4f1083ef9ea9d731cba05103a2430599 Mon Sep 17 00:00:00 2001 From: Nicolas Rebagliati Date: Fri, 16 Jul 2021 14:34:57 -0300 Subject: [PATCH 1/4] Ask for executive report template if not provided --- .../ask_for_executive_report_template.md | 1 + docs/docs/commands.md | 4 +- faraday_cli/shell/modules/executive_report.py | 119 +++++++++++------- 3 files changed, 80 insertions(+), 44 deletions(-) create mode 100644 CHANGELOG/current/ask_for_executive_report_template.md diff --git a/CHANGELOG/current/ask_for_executive_report_template.md b/CHANGELOG/current/ask_for_executive_report_template.md new file mode 100644 index 0000000..525fd45 --- /dev/null +++ b/CHANGELOG/current/ask_for_executive_report_template.md @@ -0,0 +1 @@ +Ask for executive report template if not provided diff --git a/docs/docs/commands.md b/docs/docs/commands.md index 3a3110d..3700299 100644 --- a/docs/docs/commands.md +++ b/docs/docs/commands.md @@ -679,7 +679,7 @@ $ faraday-cli executive_report list-templates -p Generate an executive report with a given template. ``` -$ faraday-cli executive_report create -t \'"generic_default.html (generic) (PDF)"\' --title title --summary summary --enterprise company -o /tmp/test.pdf --ignore-info +$ faraday-cli executive_report create -t \'"generic_default.html (generic) (PDF)"\' --title title --summary summary --enterprise company -d /tmp/test.pdf --ignore-info Report created: /tmp/test.pdf ``` @@ -695,7 +695,7 @@ Report created: /tmp/test.pdf | `--confirmed` | Confirmed vulnerabilities | | `--severity [SEVERITY [SEVERITY ...]]` | Filter by severity informational/critical/high/medium/low/unclassified | | `--ignore-info` | Ignore informational/unclassified vulnerabilities | -| `-o/--output OUTPUT` | Report output | +| `-d/--destination DESTINATION` | Report destination | ## help diff --git a/faraday_cli/shell/modules/executive_report.py b/faraday_cli/shell/modules/executive_report.py index d885a77..783c058 100644 --- a/faraday_cli/shell/modules/executive_report.py +++ b/faraday_cli/shell/modules/executive_report.py @@ -5,6 +5,7 @@ from pathlib import Path import urllib.parse +import click import cmd2 from tabulate import tabulate @@ -68,7 +69,7 @@ def list_executive_reports_templates(self, args): "-w", "--workspace-name", type=str, help="Workspace name" ) report_parser.add_argument( - "-t", "--template", type=str, help="Template", required=True + "-t", "--template", type=str, help="Template to use", required=False ) report_parser.add_argument( "--title", type=str, help="Report title", default="" @@ -95,7 +96,7 @@ def list_executive_reports_templates(self, args): help=f"Ignore {'/'.join(IGNORE_SEVERITIES)} vulnerabilities", ) report_parser.add_argument( - "-o", "--output", type=str, help="Report output" + "-d", "--destination", type=str, help="Report destination" ) @cmd2.as_subcommand_to( @@ -161,49 +162,83 @@ def get_report(report_data, _output): templates_data = self._cmd.api_client.get_executive_report_templates( workspace_name ) - templates = { - template[1]: template[0] for template in templates_data["items"] - } report_name = "_".join( filter(None, [workspace_name, args.title, args.enterprise]) ) - if args.template not in templates: - self._cmd.perror(f"Invalid template: {args.template}") + if not args.template: + data = [] + template_index = 0 + templates_choices = {} + for _template in sorted( + templates_data["items"], key=lambda x: x[1] + ): + template_index += 1 + templates_choices[str(template_index)] = _template + data.append( + { + "TEMPLATE": template_index, + "NAME": _template[1], + "GROUPED": _template[0], + } + ) + self._cmd.poutput( + tabulate( + data, + headers="keys", + tablefmt="simple", + ) + ) + selected_template = click.prompt( + "Select your template:", + type=click.Choice(templates_choices.keys()), + ) + template = templates_choices[selected_template][1] + grouped = templates_choices[selected_template][0] else: - report_data = { - "conclusions": "", - "confirmed": args.confirmed, - "enterprise": args.enterprise, - "grouped": templates[args.template], - "name": report_name, - "objectives": "", - "recommendations": "", - "scope": "", - "summary": args.summary, - "tags": [], - "template_name": args.template, - "title": args.title, - "vuln_count": 0, + templates = { + template[1]: template[0] + for template in templates_data["items"] } - if args.severity and args.ignore_info: - self._cmd.perror("Use either --ignore-info or --severity") + if args.template not in templates: + self._cmd.perror(f"Invalid template: {args.template}") return - query_filter = FaradayFilter() - selected_severities = set(map(lambda x: x.lower(), args.severity)) - if selected_severities: - for severity in selected_severities: - if severity not in SEVERITIES: - self._cmd.perror(f"Invalid severity: {severity}") - return - else: - query_filter.require_severity(severity) - if args.ignore_info: - for severity in IGNORE_SEVERITIES: - query_filter.ignore_severity(severity) - if args.confirmed: - query_filter.filter_confirmed() - report_data["filter"] = urllib.parse.quote( - json.dumps(query_filter.get_filter()) - ) - report_file = get_report(report_data, args.output) - self._cmd.poutput(f"Report generated: {report_file}") + else: + grouped = templates[args.template] + template = args.template + report_data = { + "conclusions": "", + "confirmed": args.confirmed, + "enterprise": args.enterprise, + "grouped": grouped, + "name": report_name, + "objectives": "", + "recommendations": "", + "scope": "", + "summary": args.summary, + "tags": [], + "template_name": template, + "title": args.title, + "vuln_count": 0, + } + if args.severity and args.ignore_info: + self._cmd.perror("Use either --ignore-info or --severity") + return + query_filter = FaradayFilter() + selected_severities = set(map(lambda x: x.lower(), args.severity)) + if selected_severities: + for severity in selected_severities: + if severity not in SEVERITIES: + self._cmd.perror(f"Invalid severity: {severity}") + return + else: + query_filter.require_severity(severity) + if args.ignore_info: + for severity in IGNORE_SEVERITIES: + query_filter.ignore_severity(severity) + if args.confirmed: + query_filter.filter_confirmed() + report_data["filter"] = urllib.parse.quote( + json.dumps(query_filter.get_filter()) + ) + report_file = get_report(report_data, args.destination) + self._cmd.poutput(f"Report generated: {report_file}") From b72ce495a71722dab6e9a17dce9f345f73a7818e Mon Sep 17 00:00:00 2001 From: Nicolas Rebagliati Date: Mon, 9 Aug 2021 16:30:00 +0000 Subject: [PATCH 2/4] Tkt 46 create ws parameter --- .../current/add_create_workspace_parameter.md | 1 + docs/docs/commands.md | 4 ++- faraday_cli/api_client/faraday_api.py | 2 +- faraday_cli/shell/modules/reports.py | 26 ++++++++++++++++--- faraday_cli/shell/modules/stats.py | 2 +- faraday_cli/shell/modules/tools.py | 26 ++++++++++++++++--- faraday_cli/shell/modules/workspace.py | 2 +- faraday_cli/shell/shell.py | 2 +- 8 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 CHANGELOG/current/add_create_workspace_parameter.md diff --git a/CHANGELOG/current/add_create_workspace_parameter.md b/CHANGELOG/current/add_create_workspace_parameter.md new file mode 100644 index 0000000..fe93b41 --- /dev/null +++ b/CHANGELOG/current/add_create_workspace_parameter.md @@ -0,0 +1 @@ +add --create-workspace parameter for tool command diff --git a/docs/docs/commands.md b/docs/docs/commands.md index 3a3110d..0cf6889 100644 --- a/docs/docs/commands.md +++ b/docs/docs/commands.md @@ -411,6 +411,7 @@ Different stats about the vulnerabilities in Faraday. | Syntax | Description | |:----- |------: | | `-w WORKSPACE_NAME` | Workspace name | +| `--create-workspace` | if -w is used and the workspace dont exists, it will create it | | `--ignore-info` | Ignore informational/unclassified vulnerabilities | | `--severity [SEVERITY [SEVERITY ...]]` | Filter by severity informational/critical/high/medium/low/unclassified | | `--confirmed` | Confirmed vulnerabilities | @@ -443,13 +444,14 @@ $ faraday-cli tool report $HOME/Downloads/openvas-report.xml | Syntax | Description | |:----- |------: | | `-w WORKSPACE_NAME` | Workspace name | +| `--create-workspace` | if -w is used and the workspace dont exists, it will create it | | `--plugin-id PLUGIN_ID` | Plugin ID (force detection) | | `-j/--json-output` | Show output in json (dont send it to faraday) | | `--tag-vuln TAG_VULN` | Tag to add to vulnerabilities | | `--tag-host TAG_HOST` | Tag to add to hosts | | `--tag-service TAG_SERVICE` | Tag to add to services | -### process_tool +### run tool Execute a tool and upload the information into faraday. diff --git a/faraday_cli/api_client/faraday_api.py b/faraday_cli/api_client/faraday_api.py index 6e22b36..370f796 100644 --- a/faraday_cli/api_client/faraday_api.py +++ b/faraday_cli/api_client/faraday_api.py @@ -406,7 +406,7 @@ def delete_workspace(self, workspace_name: str): return response @handle_errors - def is_workspace_valid(self, workspace_name): + def is_workspace_available(self, workspace_name): workspaces = self.get_workspaces() available_workspaces = [ ws for ws in map(lambda x: x["name"], workspaces) diff --git a/faraday_cli/shell/modules/reports.py b/faraday_cli/shell/modules/reports.py index b507869..06e5461 100644 --- a/faraday_cli/shell/modules/reports.py +++ b/faraday_cli/shell/modules/reports.py @@ -17,6 +17,11 @@ def __init__(self): report_parser.add_argument( "-w", "--workspace-name", type=str, help="Workspace" ) + report_parser.add_argument( + "--create-workspace", + action="store_true", + help="Create the workspace it not exists", + ) report_parser.add_argument( "--plugin-id", type=str, @@ -67,9 +72,24 @@ def process_report(self, args: argparse.Namespace): return else: workspace_name = args.workspace_name - if not self._cmd.api_client.is_workspace_valid(workspace_name): - self._cmd.perror(f"Invalid workspace: {workspace_name}") - return + if not self._cmd.api_client.is_workspace_available(workspace_name): + if not args.create_workspace: + self._cmd.perror(f"Invalid workspace: {workspace_name}") + return + else: + try: + self._cmd.api_client.create_workspace(workspace_name) + self._cmd.poutput( + cmd2.style( + f"Workspace {workspace_name} created", + fg="green", + ) + ) + except Exception as e: + self._cmd.perror(f"Error creating workspace: {e}") + return + else: + destination_workspace = workspace_name else: destination_workspace = workspace_name if args.plugin_id: diff --git a/faraday_cli/shell/modules/stats.py b/faraday_cli/shell/modules/stats.py index 6e50184..aa2120f 100644 --- a/faraday_cli/shell/modules/stats.py +++ b/faraday_cli/shell/modules/stats.py @@ -151,7 +151,7 @@ def graph_stats(gather_data_func, filter_to_apply): self._cmd.perror(f"No data in workspace {workspace_name}") if workspace_name: - if not self._cmd.api_client.is_workspace_valid(workspace_name): + if not self._cmd.api_client.is_workspace_available(workspace_name): self._cmd.perror(f"Invalid workspace: {workspace_name}") return else: diff --git a/faraday_cli/shell/modules/tools.py b/faraday_cli/shell/modules/tools.py index 6a15af2..b3a2662 100644 --- a/faraday_cli/shell/modules/tools.py +++ b/faraday_cli/shell/modules/tools.py @@ -15,6 +15,11 @@ def __init__(self): tool_parser.add_argument( "-w", "--workspace-name", type=str, help="Workspace" ) + tool_parser.add_argument( + "--create-workspace", + action="store_true", + help="Create the workspace it not exists", + ) tool_parser.add_argument( "--plugin-id", type=str, @@ -61,9 +66,24 @@ def process_tool(self, args): return else: workspace_name = args.workspace_name - if not self._cmd.api_client.is_workspace_valid(workspace_name): - self._cmd.perror(f"Invalid workspace: {workspace_name}") - return + if not self._cmd.api_client.is_workspace_available(workspace_name): + if not args.create_workspace: + self._cmd.perror(f"Invalid workspace: {workspace_name}") + return + else: + try: + self._cmd.api_client.create_workspace(workspace_name) + self._cmd.poutput( + cmd2.style( + f"Workspace {workspace_name} created", + fg="green", + ) + ) + except Exception as e: + self._cmd.perror(f"Error creating workspace: {e}") + return + else: + destination_workspace = workspace_name else: destination_workspace = workspace_name diff --git a/faraday_cli/shell/modules/workspace.py b/faraday_cli/shell/modules/workspace.py index 7d658b9..90ee729 100644 --- a/faraday_cli/shell/modules/workspace.py +++ b/faraday_cli/shell/modules/workspace.py @@ -28,7 +28,7 @@ def __init__(self): ) def select_ws(self, args: argparse.Namespace): """Select active Workspace""" - if self._cmd.api_client.is_workspace_valid(args.workspace_name): + if self._cmd.api_client.is_workspace_available(args.workspace_name): active_config.workspace = args.workspace_name active_config.save() self._cmd.poutput( diff --git a/faraday_cli/shell/shell.py b/faraday_cli/shell/shell.py index 85f344b..314912e 100644 --- a/faraday_cli/shell/shell.py +++ b/faraday_cli/shell/shell.py @@ -81,7 +81,7 @@ def __init__(self, *args, **kwargs): settings_to_hide = ["debug"] for setting_name in settings_to_hide: self.remove_settable(setting_name) - intro = [style(logo, fg="red")] + intro = [style(f"{logo}\nv:{__version__}", fg="cyan")] if active_config.faraday_url and active_config.token: intro.append( style(f"Server: {active_config.faraday_url}", fg="green") From c31f1587c5f3834d5da202c42c09ffb666c96d42 Mon Sep 17 00:00:00 2001 From: Nicolas Rebagliati Date: Mon, 9 Aug 2021 16:30:05 +0000 Subject: [PATCH 3/4] add for executor parameters if not provided --- .gitlab-ci.yml | 2 +- .pre-commit-config.yaml | 3 + .../current/ask_for_executor_parameters.md | 1 + docs/docs/commands.md | 30 +++++++++- faraday_cli/api_client/faraday_api.py | 4 +- faraday_cli/shell/modules/agent.py | 56 +++++++++++++++++-- 6 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG/current/ask_for_executor_parameters.md diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 664d656..8d6fdaf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,7 @@ flake8: stage: .pre script: - pip install .[dev] - - flake8 . + - flake8 --max-line-length=120 . black: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e38b5e..c90ed98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,3 +20,6 @@ repos: hooks: - id: flake8 additional_dependencies: [flake8-typing-imports==1.9.0] + args: # arguments to configure flake8 + # making isort line length compatible with black + - "--max-line-length=120" diff --git a/CHANGELOG/current/ask_for_executor_parameters.md b/CHANGELOG/current/ask_for_executor_parameters.md new file mode 100644 index 0000000..bde8983 --- /dev/null +++ b/CHANGELOG/current/ask_for_executor_parameters.md @@ -0,0 +1 @@ +Add for executor parameters if not provided diff --git a/docs/docs/commands.md b/docs/docs/commands.md index 3a3110d..5f8e08c 100644 --- a/docs/docs/commands.md +++ b/docs/docs/commands.md @@ -619,12 +619,40 @@ Run an executor. ``` $ echo '{"target": "www.google.com"}' | faraday-cli agent run -a 1 -e nmap --stdin ``` + If no ```-p``` or ```--stdin``` argument is provided, then the executor parameters will be asked one by one. + + You can store the parameters in env variables and will be auto loaded. + + Example: + ``` + For the TARGET parameter of the NMAP executor save the variable FARADAY_CLI_EXECUTOR_NMAP_TARGET + ``` + + You can store the parameters in a file and use it as a 'profile' to repeat scans + ``` + $ cat scan_test.com_nmap.vars + FARADAY_CLI_EXECUTOR_NMAP_TARGET=test.com + FARADAY_CLI_EXECUTOR_NMAP_OPTION_PN=true + FARADAY_CLI_EXECUTOR_NMAP_OPTION_SC=false + FARADAY_CLI_EXECUTOR_NMAP_OPTION_SV=true + FARADAY_CLI_EXECUTOR_NMAP_PORT_LIST= + FARADAY_CLI_EXECUTOR_NMAP_TOP_PORTS= + FARADAY_CLI_EXECUTOR_NMAP_HOST_TIMEOUT= + FARADAY_CLI_EXECUTOR_NMAP_SCRIPT_TIMEOUT= + + $ source scan_test.com_nmap.vars && faraday-cli agent run -a 2 -e nmap -w test + Running executor: unnamed_agent/nmap + Parameters: {"TARGET": "test.com", "OPTION_PN": "true", "OPTION_SC": "false", "OPTION_SV": "true"} + Generated Command: 14 + ``` !!! warning If you pass the executor parameters as an argument it needs to be escaped like this (only in command mode, not in shell mode). ``` $ faraday-cli agent run -a 1 -e nmap -p \''{"target": "www.google.com"}'\' - Run executor: agent/nmap [{'command_id': 5}] + Running executor: unnamed_agent/nmap + Parameters: {"TARGET": "www.google.com"} + Generated Command: 13 ``` *Required Arguments:* diff --git a/faraday_cli/api_client/faraday_api.py b/faraday_cli/api_client/faraday_api.py index 6e22b36..8df7093 100644 --- a/faraday_cli/api_client/faraday_api.py +++ b/faraday_cli/api_client/faraday_api.py @@ -77,7 +77,9 @@ def hanlde(self, *args, **kwargs): e.response.headers["content-type"] == "application/json" ): - raise RequestError(e.response.body["message"]) + raise RequestError( + e.response.body.get("message", e.response.body) + ) else: raise RequestError(e) except Exception as e: diff --git a/faraday_cli/shell/modules/agent.py b/faraday_cli/shell/modules/agent.py index 8f94eaf..5efce93 100644 --- a/faraday_cli/shell/modules/agent.py +++ b/faraday_cli/shell/modules/agent.py @@ -1,9 +1,11 @@ import json import argparse import sys +import os from collections import OrderedDict import cmd2 +import click from tabulate import tabulate from simple_rest_client.exceptions import NotFoundError @@ -189,12 +191,14 @@ def get_agent(self, args: argparse.Namespace): ) def run_executor(self, args): """Run executor""" + ask_for_parameters = False if args.stdin: executor_params = sys.stdin.read() else: if not args.executor_params: - self._cmd.perror("Missing executor params") - return + ask_for_parameters = True + # self._cmd.perror("Missing executor params") + # return else: executor_params = args.executor_params if not args.workspace_name: @@ -227,6 +231,38 @@ def run_executor(self, args): f"Invalid executor name [{args.executor_name}]" ) return + if ask_for_parameters: + executor_params = {} + types_mapping = { + "boolean": click.BOOL, + "integer": click.INT, + } + for parameter, parameter_data in executor[ + "parameters_metadata" + ].items(): + value = os.getenv( + f"FARADAY_CLI_EXECUTOR_{executor['name'].upper()}_{parameter}", + None, + ) + if value is None: + if parameter_data["mandatory"]: + value = click.prompt( + f"{parameter} ({parameter_data['type']})", + type=types_mapping.get( + parameter_data["type"], click.STRING + ), + show_default=False, + ) + else: + value = click.prompt( + f"{parameter} ({parameter_data['type']})", + default="", + show_default=False, + ) + if type(value) == str and value == "": + continue + executor_params[parameter] = str(value) + executor_params = json.dumps(executor_params) executor_parameters_schema = { "type": "object", "properties": { @@ -236,7 +272,7 @@ def run_executor(self, args): "required": [ i[0] for i in filter( - lambda x: x[1] is True, + lambda x: x[1]["mandatory"] is True, executor["parameters_metadata"].items(), ) ], @@ -248,6 +284,16 @@ def run_executor(self, args): except InvalidJsonSchema as e: self._cmd.perror(e) else: + run_message = ( + f"Running executor: {agent['name']}/{executor['name']}" + f"\nParameters: {executor_params}" + ) + self._cmd.poutput( + cmd2.style( + run_message, + fg="green", + ) + ) try: response = self._cmd.api_client.run_executor( workspace_name, @@ -256,11 +302,11 @@ def run_executor(self, args): json.loads(executor_params), ) except Exception as e: - print(e) + self._cmd.perror(str(e)) else: self._cmd.poutput( cmd2.style( - f"Run executor: {agent['name']}/{executor['name']} [{response}]", # noqa: E501 + f"Generated Command: {response['command_id']}", # noqa: E501 fg="green", ) ) From 272e12bbde8693acf095f03a9e602fc3b97dee5a Mon Sep 17 00:00:00 2001 From: Nicolas Rebagliati Date: Mon, 9 Aug 2021 13:31:43 -0300 Subject: [PATCH 4/4] ready for release 2.0.2 --- .../{current => 2.0.2}/add_create_workspace_parameter.md | 0 .../ask_for_executive_report_template.md | 0 .../{current => 2.0.2}/ask_for_executor_parameters.md | 0 CHANGELOG/2.0.2/date.md | 1 + .../{current => 2.0.2}/fix_bug_invalid_executor_name.md | 0 CHANGELOG/{current => 2.0.2}/update_readme.md | 0 RELEASE.md | 8 ++++++++ 7 files changed, 9 insertions(+) rename CHANGELOG/{current => 2.0.2}/add_create_workspace_parameter.md (100%) rename CHANGELOG/{current => 2.0.2}/ask_for_executive_report_template.md (100%) rename CHANGELOG/{current => 2.0.2}/ask_for_executor_parameters.md (100%) create mode 100644 CHANGELOG/2.0.2/date.md rename CHANGELOG/{current => 2.0.2}/fix_bug_invalid_executor_name.md (100%) rename CHANGELOG/{current => 2.0.2}/update_readme.md (100%) diff --git a/CHANGELOG/current/add_create_workspace_parameter.md b/CHANGELOG/2.0.2/add_create_workspace_parameter.md similarity index 100% rename from CHANGELOG/current/add_create_workspace_parameter.md rename to CHANGELOG/2.0.2/add_create_workspace_parameter.md diff --git a/CHANGELOG/current/ask_for_executive_report_template.md b/CHANGELOG/2.0.2/ask_for_executive_report_template.md similarity index 100% rename from CHANGELOG/current/ask_for_executive_report_template.md rename to CHANGELOG/2.0.2/ask_for_executive_report_template.md diff --git a/CHANGELOG/current/ask_for_executor_parameters.md b/CHANGELOG/2.0.2/ask_for_executor_parameters.md similarity index 100% rename from CHANGELOG/current/ask_for_executor_parameters.md rename to CHANGELOG/2.0.2/ask_for_executor_parameters.md diff --git a/CHANGELOG/2.0.2/date.md b/CHANGELOG/2.0.2/date.md new file mode 100644 index 0000000..808a322 --- /dev/null +++ b/CHANGELOG/2.0.2/date.md @@ -0,0 +1 @@ +Aug 9th, 2021 diff --git a/CHANGELOG/current/fix_bug_invalid_executor_name.md b/CHANGELOG/2.0.2/fix_bug_invalid_executor_name.md similarity index 100% rename from CHANGELOG/current/fix_bug_invalid_executor_name.md rename to CHANGELOG/2.0.2/fix_bug_invalid_executor_name.md diff --git a/CHANGELOG/current/update_readme.md b/CHANGELOG/2.0.2/update_readme.md similarity index 100% rename from CHANGELOG/current/update_readme.md rename to CHANGELOG/2.0.2/update_readme.md diff --git a/RELEASE.md b/RELEASE.md index 65e9a2c..dddba49 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,11 @@ +2.0.2 [Aug 9th, 2021]: +--- + * add --create-workspace parameter for tool command + * Ask for executive report template if not provided + * Add for executor parameters if not provided + * [FIX] Bug using an invalid executor name + * Update readme to fix some examples + 2.0.1 [Jun 29th, 2021]: --- * [FIX] Show help if no subcommand is provided