diff --git a/samples/README.md b/samples/README.md index ebf383d0..7278add3 100644 --- a/samples/README.md +++ b/samples/README.md @@ -95,6 +95,13 @@ The following samples are categorized by CrowdStrike product, and further catego | [Asset Management (Discover)](#asset-management-samples) | List discovered hosts
Spyglass | | [Vulnerability Management (Spotlight)](#vulnerability-management-samples) | Find vulnerable hosts by CVE ID
CISA DHS Known Exploited Vulnerabilities
Spotlight Quick Report | + + +### [Fusion and Foundry](#fusion-and-foundry-apis) +| Topic | Samples | +| :-- | :-- | +| [Workflows](#workflows-samples) | Workflow Manager (terminal)
Workflows Manager (GUI) | + ### [Threat Intelligence](#threat-intelligence-apis) @@ -1794,6 +1801,84 @@ This sample demonstrates the following CrowdStrike Spotlight Vulnerability API a + + + +
+

Fusion and Foundry

+ + +
+

Workflows

(click to expand)
+The samples in this section focus on the CrowdStrike Falcon Workflows API service collection. +
+ +- [Workflow Manager (terminal version)](#workflow-manager-terminal-version) +- [Workflow Manager (gui version)](#workflow-manager-gui-version) + +#### Workflow Manager (terminal version) + +This sample demonstrates how to leverage the Workflows API to provide the following functionality: + - List all workflows + - Execute a workflow + - List all executions for a workflow + - Print the results of a workflow execution + - Import a workflow + - Export a workflow + +[![Falcon Fusion Workflows](https://img.shields.io/badge/Service%20Class-Falcon_Fusion_SOAR_Workflows_Manager_[terminal_version]-silver?style=for-the-badge&labelColor=C30A16&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAOCAYAAAAi2ky3AAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TpaIVBzuIOGSoDmJBVEQ3rUIRKoRaoVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXNzUnSREu9LCi1ifPB4H+e9c7jvXkColZhmtY0Cmm6bqURczGRXxNAruhAEMI1hmVnGrCQl4bu+7hHg512MZ/m/+3N1qzmLAQGReIYZpk28Tjy5aRuc94kjrCirxOfEIyYVSPzIdcXjN84FlwWeGTHTqTniCLFYaGGlhVnR1IgniKOqplO+kPFY5bzFWStVWKNO/sNwTl9e4jrtASSwgEVIEKGggg2UYCNGp06KhRTdx338/a5fIpdCrg0wcsyjDA2y6wefwe/eWvnxMS8pHAfaXxznYxAI7QL1quN8HztO/QQIPgNXetNfrgFTn6RXm1r0COjZBi6um5qyB1zuAH1PhmzKrsTnL+TzwPsZjSkL9N4Cnate3xr3OH0A0tSr5A1wcAgMFSh7zeffHa19+/dNo38/hq9yr+iELI0AAAAGYktHRAAAAAAAAPlDu38AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQflDAsTByz7Va2cAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAYBJREFUKM+lkjFIlVEYht/zn3sFkYYUyUnIRcemhCtCU6JQOLiIU+QeJEQg6BBIm0s4RBCBLjq5OEvgJC1uOniJhivesLx17/97/vO9b4NK4g25157hfHCGB773/cA0HZIEAKiMj+LWiOxljG/i96pnCFP58XHnrWX2+9cj0dYl9Yu2FE9/9rXrcAAgs2eSyiBfOe/XRD503h/CuffOubQVUXL+Jh9BllzBbyJJBgDclVkO4Kukd8zzkXJbeUljIldFTstsmSHM6S81ma2KfPKlFdkGAMY4wzx/bbXapMy21My+YizdKNq5mDzLkrxafSxySFKjSWX2oTmjKzz4vN0r2lOFcL/Q3V0/mX95ILMXTTGYVfaut/aP2+oCMAvnZgCcsF5fcR0dg65YHAdwB+QApADvu0AuOe/ftlJAD7Nsgmm6yBjDtfWORJZlNtFyo/lR5Z7MyheKA5ktSur7sTAHazSG27pehjAiaVfkN8b4XFIJ/wOzbOx07VNRUuHy7w98CzCcGPyWywAAAABJRU5ErkJggg==)](workflows##workflow-manager-terminal-version) + +##### Workflows API operations discussed +This sample demonstrates the following CrowdStrike Workflows API operations: + +| Operation | Description | +| :--- | :--- | +| [WorkflowDefinitionsCombined](https://falconpy.io/Service-Collections/Workflows.html#workflowdefinitionscombined) | Search workflow definitions based on the provided filter. | +| [WorkflowDefinitionsExport](https://falconpy.io/Service-Collections/Workflows.html#workflowdefinitionsexport) | Export a workflow definition for the given definition ID. | +| [WorkflowDefinitionsImport](https://falconpy.io/Service-Collections/Workflows.html#workflowdefinitionsimport) | Import a workflow definition from a file. | +| [WorkflowExecute](https://falconpy.io/Service-Collections/Workflows.html#workflowexecute) | Execute an on-demand workflow. The response will contain the execution ID. | +| [WorkflowExecutionsCombined](https://falconpy.io/Service-Collections/Workflows.html#workflowexecutionscombined) | Search workflow executions based on the provided filter. | +| [WorkflowExecutionsResults](https://falconpy.io/Service-Collections/Workflows.html#workflowexecutionsresults) | Get execution result of a given execution. | + +--- + +#### Workflow Manager (GUI version) + +Like the sample above, this sample demonstrates how to leverage the Workflows API to provide the following functionality: + - List all workflows + - Execute a workflow + - List all executions for a workflow + - Print the results of a workflow execution + - Import a workflow + - Export a workflow + +Additional functionality provided by this sample include: + - Full GUI interface + - Activity logging (to a local file) + - Exporting list results to CSV + +[![Falcon Fusion Workflows](https://img.shields.io/badge/Service%20Class-Falcon_Fusion_SOAR_Workflows_Manager_[GUI_version]-silver?style=for-the-badge&labelColor=C30A16&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAOCAYAAAAi2ky3AAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TpaIVBzuIOGSoDmJBVEQ3rUIRKoRaoVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXNzUnSREu9LCi1ifPB4H+e9c7jvXkColZhmtY0Cmm6bqURczGRXxNAruhAEMI1hmVnGrCQl4bu+7hHg512MZ/m/+3N1qzmLAQGReIYZpk28Tjy5aRuc94kjrCirxOfEIyYVSPzIdcXjN84FlwWeGTHTqTniCLFYaGGlhVnR1IgniKOqplO+kPFY5bzFWStVWKNO/sNwTl9e4jrtASSwgEVIEKGggg2UYCNGp06KhRTdx338/a5fIpdCrg0wcsyjDA2y6wefwe/eWvnxMS8pHAfaXxznYxAI7QL1quN8HztO/QQIPgNXetNfrgFTn6RXm1r0COjZBi6um5qyB1zuAH1PhmzKrsTnL+TzwPsZjSkL9N4Cnate3xr3OH0A0tSr5A1wcAgMFSh7zeffHa19+/dNo38/hq9yr+iELI0AAAAGYktHRAAAAAAAAPlDu38AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQflDAsTByz7Va2cAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAYBJREFUKM+lkjFIlVEYht/zn3sFkYYUyUnIRcemhCtCU6JQOLiIU+QeJEQg6BBIm0s4RBCBLjq5OEvgJC1uOniJhivesLx17/97/vO9b4NK4g25157hfHCGB773/cA0HZIEAKiMj+LWiOxljG/i96pnCFP58XHnrWX2+9cj0dYl9Yu2FE9/9rXrcAAgs2eSyiBfOe/XRD503h/CuffOubQVUXL+Jh9BllzBbyJJBgDclVkO4Kukd8zzkXJbeUljIldFTstsmSHM6S81ma2KfPKlFdkGAMY4wzx/bbXapMy21My+YizdKNq5mDzLkrxafSxySFKjSWX2oTmjKzz4vN0r2lOFcL/Q3V0/mX95ILMXTTGYVfaut/aP2+oCMAvnZgCcsF5fcR0dg65YHAdwB+QApADvu0AuOe/ftlJAD7Nsgmm6yBjDtfWORJZlNtFyo/lR5Z7MyheKA5ktSur7sTAHazSG27pehjAiaVfkN8b4XFIJ/wOzbOx07VNRUuHy7w98CzCcGPyWywAAAABJRU5ErkJggg==)](workflows/#workflow-manager-gui-version) + +##### Workflows API operations discussed +This sample demonstrates the following CrowdStrike Workflows API operations: + +| Operation | Description | +| :--- | :--- | +| [WorkflowDefinitionsCombined](https://falconpy.io/Service-Collections/Workflows.html#workflowdefinitionscombined) | Search workflow definitions based on the provided filter. | +| [WorkflowDefinitionsExport](https://falconpy.io/Service-Collections/Workflows.html#workflowdefinitionsexport) | Export a workflow definition for the given definition ID. | +| [WorkflowDefinitionsImport](https://falconpy.io/Service-Collections/Workflows.html#workflowdefinitionsimport) | Import a workflow definition from a file. | +| [WorkflowExecute](https://falconpy.io/Service-Collections/Workflows.html#workflowexecute) | Execute an on-demand workflow. The response will contain the execution ID. | +| [WorkflowExecutionsCombined](https://falconpy.io/Service-Collections/Workflows.html#workflowexecutionscombined) | Search workflow executions based on the provided filter. | +| [WorkflowExecutionsResults](https://falconpy.io/Service-Collections/Workflows.html#workflowexecutionsresults) | Get execution result of a given execution. | + +
+ +[Back to top](#falconpy-sample-library) | [How to authenticate](#authentication-for-these-examples) | [Table of Contents](#fusion-and-foundry-toc) + +--- + +
+ diff --git a/samples/workflows/README.md b/samples/workflows/README.md new file mode 100644 index 00000000..afa48e1b --- /dev/null +++ b/samples/workflows/README.md @@ -0,0 +1,794 @@ +![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png) +[![CrowdStrike Subreddit](https://img.shields.io/badge/-r%2Fcrowdstrike-white?logo=reddit&labelColor=gray&link=https%3A%2F%2Freddit.com%2Fr%2Fcrowdstrike)](https://reddit.com/r/crowdstrike) + +# Workflows examples +The examples within this folder focus on leveraging CrowdStrike's Falcon Falcon Fusion SOAR API. + +- [Workflow Manager (terminal)](#workflow-manager-terminal-version) +- [Workflow Manager (gui)](#workflow-manager-gui-version) + +## Workflow Manager (Terminal version) +This sample demonstrates how to leverage the Workflows API to provide the following functionality: + +- List all workflows +- Execute a workflow +- List all executions for a workflow +- Print the results of a workflow execution +- Import a workflow +- Export a workflow + +### Running the program +In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes: + +| Service Collection | Scope | +| :---- | :---- | +| Workflows | __READ__, __WRITE__ | + +#### Required packages +In order to run this sample, you will need to have the [`tabulate`](https://pypi.org/project/tabulate/) and [`termcolor`](https://pypi.org/project/termcolor/) packages installed. + +### Execution syntax +This sample leverages simple command-line arguments to implement functionality. + +#### Basic usage +Execute the default example. This will default to listing all workflows discovered in tabular format. + +```shell +python3 workflow_manager.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET +``` + +> [!TIP] +> This sample supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning you can execute any of the command lines shown below without providing credentials if you have the values `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` defined in your environment. + +```shell +python3 workflow_manager.py +``` + +Change the CrowdStrike region with the `-b` argument. + +```shell +python3 workflow_manager.py -b usgov1 +``` + +Execute a workflow using a custom payload. + +```shell +python3 workflow_manager.py -e -i $WORKFLOW_DEFINITION_ID -p {'key': 'value'} +``` + +List all executions of a workflow using the `-le` argument. + +```shell +python3 workflow_manager.py -le -i $WORKFLOW_DEFINITION_ID +``` + +Retrieve the results of an execution with the `-g` argument. + +```shell +python3 workflow_manager.py -g -i $WORKFLOW_EXECUTION_ID +``` + +Export a workflow to a local YAML file using the `-ex` argument. + +```shell +python3 workflow_manager.py -ex $EXPORT_FILENAME -i $WORKFLOW_DEFINITION_ID +``` + +> [!NOTE] +> Exporting to an existing file will overwrite it's contents. + +Import a workflow from a local YAML file using the `-im` argument. + +```shell +python3 workflow_manager.py -im $IMPORT_FILENAME +``` + +> [!NOTE] +> If the workflow name defined within your workflow YAML file exists within your tenant, an error will be thrown. +> Use the `-n` argument to import this file as a new workflow with a new name. + +```shell +python3 workflow_manager.py -im $IMPORT_FILENAME -n $WORKFLOW_NEW_NAME +``` + +> [!TIP] +> You can validate this workflow import without saving by providing the `-v` argument with the above command. + +```shell +python3 workflow_manager.py -im $IMPORT_FILENAME -n $WORKFLOW_NEW_NAME -v +``` + +API debugging can be enabled using the `-d` argument. + +```shell +python3 workflow_manager.py -d +``` + +Adjust the output table format using the `-t` argument. + +```shell +python3 workflow_manager.py -l -t fancy_grid +``` + +Swap to JSON output with the `-j` argument. + +```shell +python3 workflow_manager.py -l -j +``` + +#### Command-line help +Command-line help is available via the `-h` argument. + +```shell +usage: workflow_manager.py [-h] [-d] [-i ID] [-p PAYLOAD] [-e] [-g] [-l] [-le] [-ex EXPORT_WORKFLOW] [-im IMPORT_WORKFLOW] [-n WORKFLOW_NAME] [-v] [-j] [-t TABLE_FORMAT] [-k CLIENT_ID] + [-s CLIENT_SECRET] [-b BASE_URL] + +Falcon Fusion SOAR workflow manager. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + + ██ ██ ██ ████ ██ +░██ ░██ ░██ ░██░ ░██ +░██ █ ░██ ██████ ██████░██ ██ ██████ ░██ ██████ ███ ██ +░██ ███ ░██ ██░░░░██░░██░░█░██ ██ ░░░██░ ░██ ██░░░░██░░██ █ ░██ +░██ ██░██░██░██ ░██ ░██ ░ ░████ ░██ ░██░██ ░██ ░██ ███░██ +░████ ░░████░██ ░██ ░██ ░██░██ ░██ ░██░██ ░██ ░████░████ +░██░ ░░░██░░██████ ░███ ░██░░██ ░██ ███░░██████ ███░ ░░░██ +░░ ░░ ░░░░░░ ░░░ ░░ ░░ ░░ ░░░ ░░░░░░ ░░░ ░░░ + ████ ████ +░██░██ ██░██ █████ +░██░░██ ██ ░██ ██████ ███████ ██████ ██░░░██ █████ ██████ +░██ ░░███ ░██ ░░░░░░██ ░░██░░░██ ░░░░░░██ ░██ ░██ ██░░░██░░██░░█ +░██ ░░█ ░██ ███████ ░██ ░██ ███████ ░░██████░███████ ░██ ░ +░██ ░ ░██ ██░░░░██ ░██ ░██ ██░░░░██ ░░░░░██░██░░░░ ░██ +░██ ░██░░████████ ███ ░██░░████████ █████ ░░██████░███ +░░ ░░ ░░░░░░░░ ░░░ ░░ ░░░░░░░░ ░░░░░ ░░░░░░ ░░░ + +This sample demonstrates how to leverage the Workflows API to provide +the following functionality: + - List all workflows (-l or --list-workflows) + - Execute a workflow (-e or --execute) + - List all executions for a workflow (-le or --list-executions) + - Print the results of a workflow execution (-g or --get_result) + - Import a workflow (-im {FILENAME} or --import-workflow {FILENAME}) + - Export a workflow (-ex {FILENAME} or --export-workflow {FILENAME}) + +Creation date: 11.06.2024 - jlangdev@CrowdStrike +Modification date: 11.08.2024 - jshcodes@CrowdStrike + +This sample requires the following packages: +- crowdstrike-falconpy >= 1.4.1 +- tabulate +- termcolor + +options: + -h, --help show this help message and exit + -d, --debug Activate API debugging + +workflow arguments: + -i, --id ID Workflow definition or execution ID + -p, --payload PAYLOAD + Workflow execution payload + +command arguments: + -e, --execute Execute the workflow specified + -g, --get-result Retrieve a workflow execution result + -l, --list-workflows List all workflows + -le, --list-executions + List the executions for the workflow specified + -ex, --export-workflow EXPORT_WORKFLOW + Export a workflow to a local file. + Provide a filename for this argument. Example: 'exported.yml' + -im, --import-workflow IMPORT_WORKFLOW + Import a workflow from a local file. + Provide a filename for this argument. Example: 'to_import.yml' + -n, --workflow-name WORKFLOW_NAME + Name for the imported workflow + -v, --validate-only Validate the workflow only, do not save upon import + +formatting arguments: + -j, --json Display execution results in JSON format + -t, --table-format TABLE_FORMAT + Tabular display format + +authentication arguments (environment authentication supported): + -k, --falcon-client-id CLIENT_ID + CrowdStrike Falcon API ID + -s, --falcon-client-secret CLIENT_SECRET + CrowdStrike Falcon API secret + -b, --base-url BASE_URL + CrowdStrike Region (US1, US2, EU1, USGOV1, USGOV2) + Full URL is also supported. +``` + +### Example source code +The source code for this example can be found [here](workflow_manager.py). + +--- +--- +--- + + + +## Workflow Manager (GUI version) + +[Running the program](#running-the-program-1) || [Authentication](#authentication) || [Basic Usage](#basic-usage-1) || +[Advanced Usage](#advanced-usage) || [Command-line help](#command-line-help-1) || [Source Code](#example-source-code-1) + +Like the sample above, this sample demonstrates how to leverage the Workflows API to provide the following functionality: + +List workflows command + +- List all workflows +- Execute a workflow +- List all executions for a workflow +- Print the results of a workflow execution +- Import a workflow +- Export a workflow + +Additional functionality provided by this sample include: + +- Full GUI interface +- Activity logging (to a local file) +- Exporting list results to CSV + +### Running the program +In order to run this demonstration, you you will need access to CrowdStrike API keys with the following scopes: + +| Service Collection | Scope | +| :---- | :---- | +| Workflows | __READ__, __WRITE__ | + + +This application can be started using a simple command line without arguments. + +```shell +python3 workflow_manager_gui.py +``` + +#### Required packages +In order to run this sample, you will need to have the [`Gooey`](https://pypi.org/project/Gooey/), [`requests`](https://pypi.org/project/requests/) and [`tabulate`](https://pypi.org/project/tabulate/) packages installed. + +[Return to Summary](#workflow-manager-gui-toc) + +--- +--- + +### Authentication + +Environment Configuration + +API credentials can be specified on the __Environment__ tab. + +These will be pre-populated if provided by the command line or environment. + +> [!TIP] +> These values can be provided on the command line using the `-k` and `-s` arguments or the `--client_id` and `--client_secret` arguments. +> This sample also supports [Environment Authentication](https://falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), meaning these values will be pre-populated for you from the variables `FALCON_CLIENT_ID` and `FALCON_CLIENT_SECRET` if they are present in the execution environment. + +[Return to Summary](#workflow-manager-gui-toc) + +--- +--- + +### Basic Usage +The GUI workflow manager application supports all of the same command functionality provided by the [terminal version](#workflow-manager-terminal-version). + +- [Listing workflows](#listing-workflows) +- [Executing a workflow](#executing-a-workflow) +- [Retrieving workflow execution IDs](#retrieving-all-executions-for-a-workflow) +- [Getting an execution result](#getting-the-results-of-a-workflow-execution) +- [Exporting workflows](#exporting-a-workflow-to-a-yaml-file) +- [Importing workflows](#importing-a-workflow-from-a-yaml-file) + +#### Listing workflows + +List workflows command + +To retrieve a list of all workflows within the tenant, select __*list_workflows*__ on the __Command__ tab. + +Listing all workflows within the tenant requires no additional parameters (beyond authentication). + +> [!NOTE] +> This is the default command when no command is specified. + +




+ +Listing workflows + +Results will be shown in a console window. + +> [!TIP] +> Review advanced configuration options below for more detail regarding +> [table formatting](#formatting-output) and [outputing results to CSV](#exporting-list-results-to-csv). + +




+ +--- + +#### Executing a workflow + +Execute workflow command + +To execute a workflow, first select the `execute` action on the __Command__ tab. + +








+ +Workflow ID no dropdown + +Provide the workflow definition ID of the workflow to execute in the __*id*__ field on the __Workflow__ tab. + +







+ +Workflow ID dropdown + +If authentication credentials are provided via the command line or detected within the environment, the application will attempt +to display a dropdown of all available workflows. + +> [!TIP] +> Bypass this behavior by providing the `-sk` or `--skip_preflight` command line argument when starting the application. + +



+ +Workflow ID custom values + +The dropdown is editable and will accept custom values. + +








+ +Workflow ID and Payload + +Once your ID has been specified, provide the necessary execution payload for the workflow in the `payload` field. + +








+ +Workflow execution + +Clicking the __*Start*__ button will execute the options specified and display the results to the console. + +






+ +--- + +#### Retrieving all executions for a workflow + +List executions command + +Select the `list_executions` option on the __Command__ tab to begin. + +








+ +Workflow ID + +Provide the desired workflow definition ID in the __*id*__ field on the __Workflow__ tab. + +








+ +List executions + +Clicking the __*Start*__ button will execute the search. + +Results are displayed to the console. + +





+ +--- + +#### Getting the results of a workflow execution + +Get result command + +To retrieve results for a specific execution, first select the `get_result` option on the __Command__ tab. + +







+ +Execution ID + +Provide the Workflow execution ID in the __*execution_id*__ field on the __Workflow__ tab. + +







+ +JSON formatting + +To return results in indented JSON format, select the __*json*__ option on the __Environment__ tab. + +







+ +Get Result + +Clicking the __*Start*__ button will execute the request using the specified options and display the results to the console. + +> [!NOTE] +> Device IDs were redacted for this screen shot but are typically shown by the application. + +



+ +--- + +#### Exporting a workflow to a YAML file + +Export command + +Select the __*workflow_export*__ option on the __Command__ tab. + +








+ +Workflow ID + +Provide the desired workflow definition ID in the __*id*__ field on the __Workflow__ tab. + +








+ +Export configuration + +Use the __*export_workflow*__ field on the __Export__ tab to specify the save file for the export. + +







+ +Export file + +This file will be saved in YAML format. + + +








+ +Export file dialog + +The __*Browse*__ button may be used to specify this value. + +






+ +Export workflow + +Clicking the __*Start*__ button will export the selected workflow to the specified file. + +If this file already exists, it will be overwritten. + +




+ +--- + +#### Importing a workflow from a YAML file + +Import command + +Select the __*workflow_import*__ option on the __Command__ tab. + + +









+ +Import configuration + +On the __Import__ tab, provide the location of the workflow template YAML file in the __*import_workflow*__ field. + +







+ +Import file dialog + +The __*Browse*__ button can be used to search and select the desired workflow template to import. + +







+ +New workflow name + +If the workflow name defined within the YAML file already exists in your tenant, an error will be thrown. + +You can specify a new name for this workflow using the __*workflow_name*__ field. + +




+ +Validate only + +Use the __*validate*__ checkbox to specify that this workflow will be validated for successful import, but no action will be taken. + +







+ +Import workflow + +Clicking the __*Start*__ button will begin the import as specified. + +Results will be displayed to the console upon completion. + +





+ +[Return to Summary](#workflow-manager-gui-toc) + +--- +--- + +### Advanced Usage +Environment configuration + +There are several advanced options that can be specified on the __Environment__ tab or via the command line. + +- [Formatting](#formatting-output) +- [CSV list exports](#exporting-list-results-to-csv) +- [Prefilling configuration options](#providing-configuration-via-the-command-line) +- [Font size](#adjusting-the-console-display-font-size) +- [Autostarting](#auto-starting-execution-via-the-command-line) +- [Debugging](#debugging-api-activity) +- [Logging results](#logging-results) + +#### Formatting output + +JSON formatting + +Specify the __*json*__ option to output results in formatted JSON. + +








+ +Table formatting + +Different table formats may be selected using the __*table_format*__ dropdown. + +








+ +Compress output + +Output can compressed in the console to display when running multiple executions with the __*compress_output*__ option. + +






+ +--- + +#### Exporting list results to CSV + +CSV export + +Select the _csv_ option in the __*table_format*__ dropdown field to export list results to CSV for the `list_workflows` and `list_executions` commands. + +Results will be saved to _**`workflows.csv`**_ or _**`workflow_executions.csv`**_ depending on the operation selected. + +Execution results are still displayed to the console using the _simple_ table format when the __*Start*__ button is pressed. + +

+ +--- + +#### Providing configuration via the command line +Configuration options may be specified on the command line when starting the application. These values will be pre-populated on the +configuration form. Command line provided configuration options take precedence over values specified as defaults or detected within +the running environment. + +##### Specifying the list executions command option and a workflow definition ID + +```shell +python3 workflow_manager_gui.py -le -i $WORKFLOW_DEFINITION_ID +``` + +##### Importing a workflow using the command line + +```shell +python3 workflow_manager_gui.py -im -iw $PATH_AND_FILENAME -n $NEW_WORKFLOW_NAME +``` + +--- + +#### Adjusting the console display font size + +The font point size for the console display can be adjusted using a positional command line argument. This value should be an integer. + +```shell +python3 workflow_manager_gui.py 10 +``` + +> [!NOTE] +> Positional command line arguments may be mixed with named arguments. + +```shell +python3 workflow_manager_gui.py 14 -i $WORKFLOW_DEFINITION_ID -p {'HostNames': ['example-hostname']} -e +``` + +--- + +#### Auto-starting execution via the command line + +Execution can be triggered at runtime by providing the `go` positional argument. + +```shell +python3 workflow_manager_gui.py go -ex -ew $PATH_AND_FILENAME -i $WORKFLOW_DEFINITION_ID +``` + +> [!NOTE] +> The font size and auto-execution positional arguments can be mixed together along with named arguments. +> When using font size and auto-execution together, the font size should be specified first. + +```shell +python3 workflow_manager_gui.py 11 go -g -ei $EXECUTION_ID +``` + +--- + +#### Debugging API activity + +API debugging + +Select the __*debug*__ option to enable API debugging. + +This will show detailed information regarding interactions performed with the CrowdStrike Falcon API, +listing endpoints used, payloads provided, and responses received. + +



+ +--- + +#### Logging results + +Log file + +To keep a seperate log file of all results produced by the application, use the __*logfile*__ field. + +






+ +Log file selection + +The __*Browse*__ button can be used to select the log file. + +




+ +Log file overwrite + +> [!WARNING] +> If this file exists, you may be prompted to replace the existing file. +> +> This is a known issue. Log files will not be overwritten. Instead, results will be appended to the bottom of the +> file regardless of the operating system message. + +




+ +[Return to Summary](#workflow-manager-gui-toc) + +--- +--- + +#### Command-line help +Command-line help is available via the `-h` argument. + +```shell +usage: workflow_manager_gui.py [-h] (-l | -e | -le | -g | -ex | -im) [-i ID] [-ei EXECUTION_ID] [-p PAYLOAD] + [-n WORKFLOW_NAME] [-v] [-iw IMPORT_WORKFLOW] [-ew EXPORT_WORKFLOW] [-k CLIENT_ID] + [-s CLIENT_SECRET] [-b {auto,us1,us2,eu1,usgov1,usgov2}] [-lf LOGFILE] [-d] [-o] [-sk] + (-j | + -t {plain,simple,github,grid,simple_grid,rounded_grid,heavy_grid,mixed_grid, + double_grid,fancy_grid,outline,simple_outline,rounded_outline,heavy_outline, + mixed_outline,double_outline,fancy_outline,pipe,csv,orgtbl,asciidoc,jira,presto, + pretty,psql,rst,mediawiki,moinmoin,youtrack,html,unsafehtml,latex,latex_raw, + latex_booktabs,latex_longtable,textile,tsv}) + +Falcon Fusion SOAR workflow manager. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy 1.4.1+ +`-------' `-------' + + ██ ██ ██ ████ ██ +░██ ░██ ░██ ░██░ ░██ +░██ █ ░██ ██████ ██████░██ ██ ██████ ░██ ██████ ███ ██ +░██ ███ ░██ ██░░░░██░░██░░█░██ ██ ░░░██░ ░██ ██░░░░██░░██ █ ░██ +░██ ██░██░██░██ ░██ ░██ ░ ░████ ░██ ░██░██ ░██ ░██ ███░██ +░████ ░░████░██ ░██ ░██ ░██░██ ░██ ░██░██ ░██ ░████░████ +░██░ ░░░██░░██████ ░███ ░██░░██ ░██ ███░░██████ ███░ ░░░██ +░░ ░░ ░░░░░░ ░░░ ░░ ░░ ░░ ░░░ ░░░░░░ ░░░ ░░░ + ████ ████ +░██░██ ██░██ █████ +░██░░██ ██ ░██ ██████ ███████ ██████ ██░░░██ █████ ██████ +░██ ░░███ ░██ ░░░░░░██ ░░██░░░██ ░░░░░░██ ░██ ░██ ██░░░██░░██░░█ +░██ ░░█ ░██ ███████ ░██ ░██ ███████ ░░██████░███████ ░██ ░ +░██ ░ ░██ ██░░░░██ ░██ ░██ ██░░░░██ ░░░░░██░██░░░░ ░██ +░██ ░██░░████████ ███ ░██░░████████ █████ ░░██████░███ +░░ ░░ ░░░░░░░░ ░░░ ░░ ░░░░░░░░ ░░░░░ ░░░░░░ ░░░ + +This sample demonstrates how to leverage the Workflows API to provide +the following functionality: + - List all workflows + - Results can be exported to CSV + - Execute a workflow + - List all executions for a workflow + - Results can be exported to CSV + - Print the results of a workflow execution + - Import a workflow + - Export a workflow + - Optional logging of results to a file + +This version leverages the Gooey project to implement a simple GUI, command line +arguments are supported but not required to specify execution configuration. + +Creation date: 11.06.2024 - Initial version, jlangdev@CrowdStrike +Modification date: 11.08.2024 - Refactoring, jshcodes@CrowdStrike +Modification date: 11.10.2024 - Add graphical interface, jshcodes@CrowdStrike + +This sample requires the following packages: +- crowdstrike-falconpy >= 1.4.1 +- gooey +- requests +- tabulate + +options: + -h, --help show this help message and exit + +Command: + Workflow command to perform + + -l, --list_workflows List all workflows + -e, --execute Execute the workflow specified on the Workflow tab + -le, --list_executions + List the executions for the workflow specified + -g, --get_result Retrieve a workflow execution result + -ex, --workflow_export + Export a workflow + -im, --workflow_import + Import a workflow + +Workflow: + Workflow or execution ID and workflow payload + + -i, --id ID Workflow definition ID + -ei, --execution_id EXECUTION_ID + Workflow execution ID + -p, --payload PAYLOAD + Workflow execution payload + +Import: + Import a workflow from a file + + -n, --workflow_name WORKFLOW_NAME + Name for the imported workflow + -v, --validate_only Validate the workflow only, do not save upon import + -iw, --import_workflow IMPORT_WORKFLOW + Location of the YAML workflow file to import + +Export: + Export a workflow to a file + + -ew, --export_workflow EXPORT_WORKFLOW + Location to save the exported workflow (YAML format) + Use the Workflow tab to specify the desired workflow ID + +Environment: + Authentication and program execution options + + -k, --client_id CLIENT_ID + CrowdStrike Falcon API ID + (pre-filled from environment or command line) + -s, --client_secret CLIENT_SECRET + CrowdStrike Falcon API secret + (pre-filled from environment or command line) + -b, --base_url {auto,us1,us2,eu1,usgov1,usgov2} + CrowdStrike Region + ('auto' not implemented for usgov1 or usgov2) + -lf, --logfile LOGFILE + Log output results to a local file as well as the console + -d, --debug Activate API debugging + -o, --compress_output + Compress display output + -sk, --skip_preflight + Skip preflight API lookups + -j, --json Display execution results in JSON format + -t, --table_format {plain,simple,github,grid,simple_grid,rounded_grid,heavy_grid,mixed_grid,double_grid,fancy_grid,outline,simple_outline,rounded_outline,heavy_outline,mixed_outline,double_outline,fancy_outline,pipe,csv,orgtbl,asciidoc,jira,presto,pretty,psql,rst,mediawiki,moinmoin,youtrack,html,unsafehtml,latex,latex_raw,latex_booktabs,latex_longtable,textile,tsv} + Tabular display format + Selecting CSV format will output to a file and display a table to the console using simple format +``` + +[Return to Summary](#workflow-manager-gui-toc) + +--- +--- + +### Example source code +The source code for this example can be found [here](workflow_manager_gui.py). + +[Return to Summary](#workflow-manager-gui-toc) + +--- +--- +--- \ No newline at end of file diff --git a/samples/workflows/asset/command-execute.png b/samples/workflows/asset/command-execute.png new file mode 100644 index 00000000..6568b8aa Binary files /dev/null and b/samples/workflows/asset/command-execute.png differ diff --git a/samples/workflows/asset/command-get-result.png b/samples/workflows/asset/command-get-result.png new file mode 100644 index 00000000..ebcd71ec Binary files /dev/null and b/samples/workflows/asset/command-get-result.png differ diff --git a/samples/workflows/asset/command-list-executions.png b/samples/workflows/asset/command-list-executions.png new file mode 100644 index 00000000..90451c77 Binary files /dev/null and b/samples/workflows/asset/command-list-executions.png differ diff --git a/samples/workflows/asset/command-list-workflows.png b/samples/workflows/asset/command-list-workflows.png new file mode 100644 index 00000000..ad40109c Binary files /dev/null and b/samples/workflows/asset/command-list-workflows.png differ diff --git a/samples/workflows/asset/command-workflow-export.png b/samples/workflows/asset/command-workflow-export.png new file mode 100644 index 00000000..c5131f18 Binary files /dev/null and b/samples/workflows/asset/command-workflow-export.png differ diff --git a/samples/workflows/asset/command-workflow-import.png b/samples/workflows/asset/command-workflow-import.png new file mode 100644 index 00000000..0dca24ee Binary files /dev/null and b/samples/workflows/asset/command-workflow-import.png differ diff --git a/samples/workflows/asset/config_icon.png b/samples/workflows/asset/config_icon.png new file mode 100644 index 00000000..72e2dff1 Binary files /dev/null and b/samples/workflows/asset/config_icon.png differ diff --git a/samples/workflows/asset/environment-configuration-api-debugging.png b/samples/workflows/asset/environment-configuration-api-debugging.png new file mode 100644 index 00000000..44b7c432 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-api-debugging.png differ diff --git a/samples/workflows/asset/environment-configuration-compress-output.png b/samples/workflows/asset/environment-configuration-compress-output.png new file mode 100644 index 00000000..a374ef5d Binary files /dev/null and b/samples/workflows/asset/environment-configuration-compress-output.png differ diff --git a/samples/workflows/asset/environment-configuration-csv-output.png b/samples/workflows/asset/environment-configuration-csv-output.png new file mode 100644 index 00000000..54dfa1e9 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-csv-output.png differ diff --git a/samples/workflows/asset/environment-configuration-json-formatting.png b/samples/workflows/asset/environment-configuration-json-formatting.png new file mode 100644 index 00000000..a9886da9 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-json-formatting.png differ diff --git a/samples/workflows/asset/environment-configuration-log-file-dialog.png b/samples/workflows/asset/environment-configuration-log-file-dialog.png new file mode 100644 index 00000000..65637a86 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-log-file-dialog.png differ diff --git a/samples/workflows/asset/environment-configuration-log-file-overwrite-dialog.png b/samples/workflows/asset/environment-configuration-log-file-overwrite-dialog.png new file mode 100644 index 00000000..c37b9311 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-log-file-overwrite-dialog.png differ diff --git a/samples/workflows/asset/environment-configuration-log-file.png b/samples/workflows/asset/environment-configuration-log-file.png new file mode 100644 index 00000000..d4b44c25 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-log-file.png differ diff --git a/samples/workflows/asset/environment-configuration-table-formatting.png b/samples/workflows/asset/environment-configuration-table-formatting.png new file mode 100644 index 00000000..8f1a61e9 Binary files /dev/null and b/samples/workflows/asset/environment-configuration-table-formatting.png differ diff --git a/samples/workflows/asset/environment-configuration.png b/samples/workflows/asset/environment-configuration.png new file mode 100644 index 00000000..6b3fceac Binary files /dev/null and b/samples/workflows/asset/environment-configuration.png differ diff --git a/samples/workflows/asset/execution-example-execute-workflow.png b/samples/workflows/asset/execution-example-execute-workflow.png new file mode 100644 index 00000000..fdfa27cb Binary files /dev/null and b/samples/workflows/asset/execution-example-execute-workflow.png differ diff --git a/samples/workflows/asset/execution-example-export-workflow.png b/samples/workflows/asset/execution-example-export-workflow.png new file mode 100644 index 00000000..03e6f2e0 Binary files /dev/null and b/samples/workflows/asset/execution-example-export-workflow.png differ diff --git a/samples/workflows/asset/execution-example-get-result.png b/samples/workflows/asset/execution-example-get-result.png new file mode 100644 index 00000000..f7d33acf Binary files /dev/null and b/samples/workflows/asset/execution-example-get-result.png differ diff --git a/samples/workflows/asset/execution-example-import-workflow.png b/samples/workflows/asset/execution-example-import-workflow.png new file mode 100644 index 00000000..a95c708e Binary files /dev/null and b/samples/workflows/asset/execution-example-import-workflow.png differ diff --git a/samples/workflows/asset/execution-example-list-executions.png b/samples/workflows/asset/execution-example-list-executions.png new file mode 100644 index 00000000..61a601d4 Binary files /dev/null and b/samples/workflows/asset/execution-example-list-executions.png differ diff --git a/samples/workflows/asset/execution-example-list-workflows.png b/samples/workflows/asset/execution-example-list-workflows.png new file mode 100644 index 00000000..5de75420 Binary files /dev/null and b/samples/workflows/asset/execution-example-list-workflows.png differ diff --git a/samples/workflows/asset/execution-example-workflow-disabled.png b/samples/workflows/asset/execution-example-workflow-disabled.png new file mode 100644 index 00000000..3f4f79f5 Binary files /dev/null and b/samples/workflows/asset/execution-example-workflow-disabled.png differ diff --git a/samples/workflows/asset/exit-program.png b/samples/workflows/asset/exit-program.png new file mode 100644 index 00000000..2a6b8bd5 Binary files /dev/null and b/samples/workflows/asset/exit-program.png differ diff --git a/samples/workflows/asset/export-configuration-export-file.png b/samples/workflows/asset/export-configuration-export-file.png new file mode 100644 index 00000000..2475d5fc Binary files /dev/null and b/samples/workflows/asset/export-configuration-export-file.png differ diff --git a/samples/workflows/asset/export-configuration-save-file-dialog.png b/samples/workflows/asset/export-configuration-save-file-dialog.png new file mode 100644 index 00000000..5465c4a2 Binary files /dev/null and b/samples/workflows/asset/export-configuration-save-file-dialog.png differ diff --git a/samples/workflows/asset/export-configuration.png b/samples/workflows/asset/export-configuration.png new file mode 100644 index 00000000..4c8a8753 Binary files /dev/null and b/samples/workflows/asset/export-configuration.png differ diff --git a/samples/workflows/asset/import-configuration-import-file-dialog.png b/samples/workflows/asset/import-configuration-import-file-dialog.png new file mode 100644 index 00000000..7c1f8fdf Binary files /dev/null and b/samples/workflows/asset/import-configuration-import-file-dialog.png differ diff --git a/samples/workflows/asset/import-configuration-validate-only.png b/samples/workflows/asset/import-configuration-validate-only.png new file mode 100644 index 00000000..f6a67859 Binary files /dev/null and b/samples/workflows/asset/import-configuration-validate-only.png differ diff --git a/samples/workflows/asset/import-configuration-workflow-name.png b/samples/workflows/asset/import-configuration-workflow-name.png new file mode 100644 index 00000000..c346f158 Binary files /dev/null and b/samples/workflows/asset/import-configuration-workflow-name.png differ diff --git a/samples/workflows/asset/import-configuration.png b/samples/workflows/asset/import-configuration.png new file mode 100644 index 00000000..b2f7864e Binary files /dev/null and b/samples/workflows/asset/import-configuration.png differ diff --git a/samples/workflows/asset/program_icon.png b/samples/workflows/asset/program_icon.png new file mode 100644 index 00000000..28a27e21 Binary files /dev/null and b/samples/workflows/asset/program_icon.png differ diff --git a/samples/workflows/asset/running_icon.png b/samples/workflows/asset/running_icon.png new file mode 100644 index 00000000..3495bd51 Binary files /dev/null and b/samples/workflows/asset/running_icon.png differ diff --git a/samples/workflows/asset/workflow-configuration-execution-id.png b/samples/workflows/asset/workflow-configuration-execution-id.png new file mode 100644 index 00000000..30cfd0fd Binary files /dev/null and b/samples/workflows/asset/workflow-configuration-execution-id.png differ diff --git a/samples/workflows/asset/workflow-configuration-id-and-payload.png b/samples/workflows/asset/workflow-configuration-id-and-payload.png new file mode 100644 index 00000000..61073172 Binary files /dev/null and b/samples/workflows/asset/workflow-configuration-id-and-payload.png differ diff --git a/samples/workflows/asset/workflow-configuration-no-dropdown.png b/samples/workflows/asset/workflow-configuration-no-dropdown.png new file mode 100644 index 00000000..37a95b23 Binary files /dev/null and b/samples/workflows/asset/workflow-configuration-no-dropdown.png differ diff --git a/samples/workflows/asset/workflow-configuration-select-option.png b/samples/workflows/asset/workflow-configuration-select-option.png new file mode 100644 index 00000000..9c57ac01 Binary files /dev/null and b/samples/workflows/asset/workflow-configuration-select-option.png differ diff --git a/samples/workflows/asset/workflow-configuration-select-workflow-id.png b/samples/workflows/asset/workflow-configuration-select-workflow-id.png new file mode 100644 index 00000000..bb8513a1 Binary files /dev/null and b/samples/workflows/asset/workflow-configuration-select-workflow-id.png differ diff --git a/samples/workflows/asset/workflow-configuration-workflow-id-custom-value.png b/samples/workflows/asset/workflow-configuration-workflow-id-custom-value.png new file mode 100644 index 00000000..4e917726 Binary files /dev/null and b/samples/workflows/asset/workflow-configuration-workflow-id-custom-value.png differ diff --git a/samples/workflows/asset/workflow-configuration.png b/samples/workflows/asset/workflow-configuration.png new file mode 100644 index 00000000..12c0b274 Binary files /dev/null and b/samples/workflows/asset/workflow-configuration.png differ diff --git a/samples/workflows/workflow_manager.py b/samples/workflows/workflow_manager.py new file mode 100644 index 00000000..9554f494 --- /dev/null +++ b/samples/workflows/workflow_manager.py @@ -0,0 +1,404 @@ +"""Falcon Fusion SOAR workflow manager. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy +`-------' `-------' + + ██ ██ ██ ████ ██ +░██ ░██ ░██ ░██░ ░██ +░██ █ ░██ ██████ ██████░██ ██ ██████ ░██ ██████ ███ ██ +░██ ███ ░██ ██░░░░██░░██░░█░██ ██ ░░░██░ ░██ ██░░░░██░░██ █ ░██ +░██ ██░██░██░██ ░██ ░██ ░ ░████ ░██ ░██░██ ░██ ░██ ███░██ +░████ ░░████░██ ░██ ░██ ░██░██ ░██ ░██░██ ░██ ░████░████ +░██░ ░░░██░░██████ ░███ ░██░░██ ░██ ███░░██████ ███░ ░░░██ +░░ ░░ ░░░░░░ ░░░ ░░ ░░ ░░ ░░░ ░░░░░░ ░░░ ░░░ + ████ ████ +░██░██ ██░██ █████ +░██░░██ ██ ░██ ██████ ███████ ██████ ██░░░██ █████ ██████ +░██ ░░███ ░██ ░░░░░░██ ░░██░░░██ ░░░░░░██ ░██ ░██ ██░░░██░░██░░█ +░██ ░░█ ░██ ███████ ░██ ░██ ███████ ░░██████░███████ ░██ ░ +░██ ░ ░██ ██░░░░██ ░██ ░██ ██░░░░██ ░░░░░██░██░░░░ ░██ +░██ ░██░░████████ ███ ░██░░████████ █████ ░░██████░███ +░░ ░░ ░░░░░░░░ ░░░ ░░ ░░░░░░░░ ░░░░░ ░░░░░░ ░░░ + +This sample demonstrates how to leverage the Workflows API to provide +the following functionality: + - List all workflows (-l or --list-workflows) + - Execute a workflow (-e or --execute) + - List all executions for a workflow (-le or --list-executions) + - Print the results of a workflow execution (-g or --get_result) + - Import a workflow (-im {FILENAME} or --import-workflow {FILENAME}) + - Export a workflow (-ex {FILENAME} or --export-workflow {FILENAME}) + +Creation date: 11.06.2024 - jlangdev@CrowdStrike +Modification date: 11.08.2024 - jshcodes@CrowdStrike + +This sample requires the following packages: +- crowdstrike-falconpy >= 1.4.1 +- tabulate +- termcolor +""" +import logging +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace +from json import dumps, loads +from os import getenv +from tabulate import tabulate +from termcolor import colored +from falconpy import Workflows, Result, APIError + + +def consume_arguments() -> Namespace: + """Consume the provided command line.""" + parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) + parser.add_argument("-d", "--debug", + help="Activate API debugging", + action="store_true", + required=False + ) + wflow = parser.add_argument_group("workflow arguments") + wflow.add_argument("-i", "--id", + help="Workflow definition or execution ID", + required=False + ) + wflow.add_argument("-p", "--payload", + help="Workflow execution payload", + required=False, + default="{}" + ) + cmds = parser.add_argument_group("command arguments") + cmds.add_argument("-e", "--execute", + help="Execute the workflow specified", + required=False, + action="store_true" + ) + cmds.add_argument("-g", "--get-result", + dest="get_result", + help="Retrieve a workflow execution result", + required=False, + action="store_true" + ) + cmds.add_argument("-l", "--list-workflows", + dest="list_workflows", + help="List all workflows", + required=False, + action="store_true" + ) + cmds.add_argument("-le", "--list-executions", + dest="list_executions", + help="List the executions for the workflow specified", + required=False, + action="store_true" + ) + cmds.add_argument("-ex", "--export-workflow", + dest="export_workflow", + help="Export a workflow to a local file.\n" + "Provide a filename for this argument. Example: 'exported.yml'", + required=False + ) + cmds.add_argument("-im", "--import-workflow", + dest="import_workflow", + help="Import a workflow from a local file.\n" + "Provide a filename for this argument. Example: 'to_import.yml'", + required=False + ) + cmds.add_argument("-n", "--workflow-name", + help="Name for the imported workflow", + required=False + ) + cmds.add_argument("-v", "--validate-only", + dest="validate", + help="Validate the workflow only, do not save upon import", + required=False, + default=False, + action="store_true" + ) + frmt = parser.add_argument_group("formatting arguments") + frmt.add_argument("-j", "--json", + help="Display execution results in JSON format", + required=False, + action="store_true" + ) + frmt.add_argument("-t", "--table-format", + dest="table_format", + help="Tabular display format", + required=False, + default="simple" + ) + auth = parser.add_argument_group("authentication arguments " + "(environment authentication supported)" + ) + auth.add_argument("-k", "--falcon-client-id", + dest="client_id", + help="CrowdStrike Falcon API ID", + required=False, + default=getenv("FALCON_CLIENT_ID") + ) + auth.add_argument("-s", "--falcon-client-secret", + dest="client_secret", + help="CrowdStrike Falcon API secret", + required=False, + default=getenv("FALCON_CLIENT_SECRET") + ) + auth.add_argument("-b", "--base-url", + dest="base_url", + help="CrowdStrike Region (US1, US2, EU1, USGOV1, USGOV2) \n" + "Full URL is also supported.", + required=False, + default="auto" + ) + + return parser.parse_args() + + +def get_workflows(sdk: Workflows): + """Print a list of workflows within the tenant to the screen.""" + workflow_list = [] + pop_keys = ["trigger", "actions", "description", "conditions", "loops"] + for workflow in sdk.search_definitions().data: + pop_list = [k for k in pop_keys if k in workflow] + workflow["type"] = "Unknown" + if "conditions" in workflow: + workflow["type"] = "Event" + if "trigger" in workflow: + if "schedule" in workflow["trigger"]: + workflow["type"] = "Scheduled" + if "event" not in workflow["trigger"]: + workflow["type"] = "On demand" + for key in pop_list: + workflow.pop(key) + workflow_list.append(workflow) + + return workflow_list + + +def get_executions(sdk: Workflows, definition_id: str): + """Retrieve all executions for the specified workflow.""" + execution_list = [] + pop_keys = ["definition_id", "definition_version", "ancestor_executions", + "retryable", "trigger", "activities", "loops" + ] + if definition_id in [w["id"] for w in get_workflows(sdk)]: + for execution in sdk.search_executions(filter=f"definition_id:'{definition_id}'").data: + pop_list = [k for k in pop_keys if k in execution] + for key in pop_list: + execution.pop(key) + execution_list.append(execution) + else: + print("Invalid workflow ID") + + return execution_list + + +def display_executions(sdk: Workflows, definition_id: str, tformat: str, use_json: bool): + """Display the execution list to the terminal.""" + exec_list = get_executions(sdk, definition_id=definition_id) + if exec_list: + if use_json: + print(dumps(exec_list, indent=4)) + else: + display_keys = {"execution_id": "ID", + "status": "Status", + "start_timestamp": "Start", + "end_timestamp": "End" + } + print(colored(EXECUTION_HEADER, "red", attrs=["bold"])) + print(tabulate(exec_list, headers=display_keys, tablefmt=tformat)) + else: + print("No executions found.") + + +def display_workflows(sdk: Workflows, tformat: str, use_json: bool): + """Display the workflow list to the terminal.""" + workflow_list = get_workflows(sdk) + if workflow_list: + if use_json: + print(dumps(workflow_list, indent=4)) + else: + display_keys = {"id": "ID", + "name": "Name", + "enabled": "Enabled", + "type": "Type", + "last_modified_timestamp": "Last modified", + "version": "Version" + } + print(colored(WORKFLOW_HEADER, "red", attrs=["bold"])) + print(tabulate(workflow_list, + headers=display_keys, + tablefmt=tformat + )) + else: + print("No workflows found.") + + +def execute_workflow(sdk: Workflows, definition_id: str, payload: str): + """Execute the specified workflow.""" + print(f"Attempting to execute workflow {definition_id}") + if definition_id in [w["id"] for w in get_workflows(sdk)]: + try: + result: Result = sdk.execute(definition_id=definition_id, body=loads(payload)) + if result.status_code == 200: + print(f"Workflow execution {result.data} triggered successfully") + else: + for err in result.errors: + print(f"[{err['code']}] {err['message']}") + except APIError as failure: + print(failure) + else: + print("Invalid workflow ID.") + + +def display_execution_results(sdk: Workflows, definition_id: str, use_json: bool): + """Display the results for the specified execution.""" + print(colored(RESULT_HEADER, "red", attrs=["bold"])) + try: + for result in sdk.execution_results(ids=definition_id).data: + for activity in result.get("activities"): + output = activity.get("result") + if use_json: + output = dumps(output, indent=4) + print(output) + except APIError as err: + print(err) + + +def export_workflow(sdk: Workflows, definition_id: str, filename: str): + """Export the specified workflow to a YAML file.""" + print(colored(EXPORT_HEADER, "red", attrs=["bold"])) + print(f"Exporting workflow {definition_id} to {filename}...\n") + try: + with open(filename, "wb") as export_file: + export_file.write(sdk.export_definition(id=definition_id).data) + except APIError as err: + print(err) + + +def import_workflow(sdk: Workflows, name: str, filename: str, validate_only: bool): + """Import the specified YAML file as a workflow.""" + print(colored(IMPORT_HEADER, "red", attrs=["bold"])) + stub = "Validat" if validate_only else "Import" + nam = "a new workflow" if not name else name + print(f"{stub}ing {filename} as {nam}...\n") + try: + response: Result = sdk.import_definition(name=name, + data_file=filename, + validate_only=validate_only + ) + if response.status_code == 200: + if validate_only: + print("Workflow import passes validation.") + else: + print(f"Workflow imported successfully [{response.data[0].get('id')}].") + except APIError as err: + print(err) + + +def main(): + """Execute the main routine.""" + cmd_line = consume_arguments() + _debug = False + if cmd_line.debug: + # Activate debug logging + logging.basicConfig(level=logging.DEBUG) + _debug = True + # Check for a specified action, if not present, set the default to list all workflows + if not max(cmd_line.execute, + cmd_line.list_workflows, + cmd_line.list_executions, + cmd_line.get_result, + bool(cmd_line.export_workflow), + bool(cmd_line.import_workflow) + ): + cmd_line.list_workflows = True + # Open the Service Collection as a context manager so we log out automatically + with Workflows(debug=_debug, + pythonic=True, + client_id=cmd_line.client_id, + client_secret=cmd_line.client_secret, + base_url=cmd_line.base_url + ) as workflows: + if not workflows.token_valid: + # Invalid login, show an error and then exit + fail_msg = colored("Invalid CrowdStrike API credentials or region specified.", + "red", + attrs=["bold"] + ) + print(LOGIN_FAIL.format(fail_msg)) + return + + if cmd_line.execute: + execute_workflow(workflows, cmd_line.id, cmd_line.payload) + + if cmd_line.list_executions: + display_executions(workflows, cmd_line.id, cmd_line.table_format, cmd_line.json) + + if cmd_line.list_workflows: + display_workflows(workflows, cmd_line.table_format, cmd_line.json) + + if cmd_line.get_result: + display_execution_results(workflows, cmd_line.id, cmd_line.json) + + if cmd_line.export_workflow: + export_workflow(workflows, cmd_line.id, cmd_line.export_workflow) + + if cmd_line.import_workflow: + import_workflow(workflows, + cmd_line.workflow_name, + cmd_line.import_workflow, + cmd_line.validate + ) + + +WORKFLOW_HEADER = r""" +_ _ _ ____ ____ _ _ ____ _ ____ _ _ _ ____ +| | | | | |__/ |_/ |___ | | | | | | [__ +|_|_| |__| | \ | \_ | |___ |__| |_|_| ___] +""" + +EXECUTION_HEADER = r""" +_ _ _ ____ ____ _ _ ____ _ ____ _ _ _ ____ _ _ ____ ____ _ _ ___ _ ____ _ _ ____ +| | | | | |__/ |_/ |___ | | | | | | |___ \/ |___ | | | | | | | |\ | [__ +|_|_| |__| | \ | \_ | |___ |__| |_|_| |___ _/\_ |___ |___ |__| | | |__| | \| ___] +""" + +RESULT_HEADER = r""" +____ _ _ ____ ____ _ _ ___ _ ____ _ _ ____ ____ ____ _ _ _ ___ ____ +|___ \/ |___ | | | | | | | |\ | |__/ |___ [__ | | | | [__ +|___ _/\_ |___ |___ |__| | | |__| | \| | \ |___ ___] |__| |___ | ___] +""" + +IMPORT_HEADER = r""" +_ _ _ ___ ____ ____ ___ +| |\/| |__] | | |__/ | +| | | | |__| | \ | +""" + +EXPORT_HEADER = r""" +____ _ _ ___ ____ ____ ___ +|___ \/ |__] | | |__/ | +|___ _/\_ | |__| | \ | +""" + +LOGIN_FAIL = r""" + + _\\/|(/_ + >_ _ / + (_)-(_) + ) o ( + \ = / {} + |W| + | | + __| |__ + / \ u / \ + | `-' | + |__| |__| + |||ADM||| + ||| ||| + ||| ||| +""" + +if __name__ == "__main__": + main() diff --git a/samples/workflows/workflow_manager_gui.py b/samples/workflows/workflow_manager_gui.py new file mode 100644 index 00000000..7c000dd1 --- /dev/null +++ b/samples/workflows/workflow_manager_gui.py @@ -0,0 +1,798 @@ +"""Falcon Fusion SOAR workflow manager. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | CROWDSTRIKE FALCON |::.. . | FalconPy 1.4.1+ +`-------' `-------' + + ██ ██ ██ ████ ██ +░██ ░██ ░██ ░██░ ░██ +░██ █ ░██ ██████ ██████░██ ██ ██████ ░██ ██████ ███ ██ +░██ ███ ░██ ██░░░░██░░██░░█░██ ██ ░░░██░ ░██ ██░░░░██░░██ █ ░██ +░██ ██░██░██░██ ░██ ░██ ░ ░████ ░██ ░██░██ ░██ ░██ ███░██ +░████ ░░████░██ ░██ ░██ ░██░██ ░██ ░██░██ ░██ ░████░████ +░██░ ░░░██░░██████ ░███ ░██░░██ ░██ ███░░██████ ███░ ░░░██ +░░ ░░ ░░░░░░ ░░░ ░░ ░░ ░░ ░░░ ░░░░░░ ░░░ ░░░ + ████ ████ +░██░██ ██░██ █████ +░██░░██ ██ ░██ ██████ ███████ ██████ ██░░░██ █████ ██████ +░██ ░░███ ░██ ░░░░░░██ ░░██░░░██ ░░░░░░██ ░██ ░██ ██░░░██░░██░░█ +░██ ░░█ ░██ ███████ ░██ ░██ ███████ ░░██████░███████ ░██ ░ +░██ ░ ░██ ██░░░░██ ░██ ░██ ██░░░░██ ░░░░░██░██░░░░ ░██ +░██ ░██░░████████ ███ ░██░░████████ █████ ░░██████░███ +░░ ░░ ░░░░░░░░ ░░░ ░░ ░░░░░░░░ ░░░░░ ░░░░░░ ░░░ + +This sample demonstrates how to leverage the Workflows API to provide +the following functionality: + - List all workflows + - Results can be exported to CSV + - Execute a workflow + - List all executions for a workflow + - Results can be exported to CSV + - Print the results of a workflow execution + - Import a workflow + - Export a workflow + - Optional logging of results to a file + +This version leverages the Gooey project to implement a simple GUI, command line +arguments are supported but not required to specify execution configuration. + +Creation date: 11.06.2024 - Initial version, jlangdev@CrowdStrike +Modification date: 11.08.2024 - Refactoring, jshcodes@CrowdStrike +Modification date: 11.10.2024 - Add graphical interface, jshcodes@CrowdStrike + +This sample requires the following packages: +- crowdstrike-falconpy >= 1.4.1 +- gooey +- requests +- tabulate +""" +import csv +import os +import sys +import logging +from argparse import RawTextHelpFormatter, Namespace, ArgumentError +from json import dumps, dump, loads +from os import getenv +from typing import List, Union +import requests +from tabulate import tabulate +from gooey import Gooey, GooeyParser +from falconpy import Workflows, Result, APIError + + +MENU = [ + { + "type": "AboutDialog", + "menuTitle": "About", + "name": "Falcon Fusion SOAR Workflow Manager", + "description": "\n\n\nA graphical application for managing Falcon Fusion SOAR workflows\n" + "\n Built using the CrowdStrike FalconPy project", + "version": "0.1", + "copyright": "2024", + "website": "https://github.com/crowdstrike/falconpy" + }, { + "type": "Link", + "menuTitle": "Falcon Fusion SOAR API documentation", + "url": "https://www.falconpy.io/Service-Collections/Workflows.html" + }, { + "type": "Link", + "menuTitle": "Falcon Fusion SOAR console documentation", + "url": "https://falcon.crowdstrike.com/documentation/page/dc4f8c45/workflows-falcon-fusion" + }, { + "type": "Link", + "menuTitle": "CrowdStrike Developer Center", + "url": "https://developer.crowdstrike.com" + }, { + "type": "Link", + "menuTitle": "More FalconPy code samples", + "url": "https://github.com/crowdstrike/falconpy/tree/main/samples#falconpy-sample-library" + } +] + + +def get_font_size() -> int: + """Return the specified font point size for terminal output. + + Specify this value as the first argument (integer) when executing the application. + """ + returned = None + try: + if "-" not in sys.argv[1]: + returned = int(sys.argv[1]) + # Pop it out of the list so it doesn't + # interfere with argument parsing + sys.argv.pop(1) + except (IndexError, ValueError): + pass + + return returned + + +def check_for_autostart() -> bool: + """Check to see if the command line specifies automatic execution.""" + returned = False + try: + if "-" not in sys.argv[1]: + if sys.argv[1].lower() in ["run", "go", "exec", "force"]: + returned = True + sys.argv.pop(1) + except (IndexError, ValueError): + pass + + return returned + + +def get_image_dir(): + """Identify the current program image directory.""" + returned = os.path.dirname(os.path.abspath(__file__)) + if os.path.exists(f"{returned}/asset/program_icon.png"): + returned = f"{returned}/asset" + + return returned + + +def gui_image(img: str, local_img: str): + """Retrieve GUI program images from GitHub before launching the interface.""" + curpath = os.path.dirname(os.path.abspath(__file__)) + loc_array = ["https://raw.githubusercontent.com/CrowdStrike/", + "falconpy/refs/heads/main/samples/workflows/asset/" + ] + loc = "".join(loc_array) + if not max(os.path.exists(f"{curpath}/{local_img}"), + os.path.exists(f"{curpath}/asset/{local_img}") + ): + try: + result = requests.get(f"{loc}{img}", timeout=5) + if result.status_code == 200: + img_data = result.content + with open(f"{curpath}/{local_img}", "wb") as gui_icon: + gui_icon.write(img_data) + except (requests.exceptions.Timeout, PermissionError, OSError): + pass + + +def prefill_check(what: str = "ID") -> str: + """Detect if environment authentication is in use and inform the user.""" + returned = f"CrowdStrike Falcon API {what}" + if getenv(f"FALCON_CLIENT_{what.upper()}"): + returned = f"{returned}\n(pre-filled from environment or command line)" + + return returned + + +def attempt_to_prefill_workflow_ids() -> Union[List[str], None]: + """Attempt to lookup workflow names and IDs before the GUI loads. + + This functionality only works when environment authentication is in use, or valid + credentials are provided on the command line using the -k and -s arguments. + """ + returned: List[str] = [] + tmp_id = getenv("FALCON_CLIENT_ID") + tmp_sec = getenv("FALCON_CLIENT_SECRET") + tmp_base = "auto" + tmp_debug = False + cnt = 0 + for arg in sys.argv: + if arg in ["-k", "--client_id"]: + tmp_id = sys.argv[cnt+1] + if arg in ["-s", "--client_secret"]: + tmp_sec = sys.argv[cnt+1] + if arg in ["-b", "--base_url"]: + tmp_base = sys.argv[cnt+1] + if arg in ["-d", "--debug"]: + tmp_debug = True + if arg in ["-g", "--get_result", "--skip_preflight", "-sk", "-h"]: + tmp_id = None + returned = None + break + cnt += 1 + if min(bool(tmp_id), bool(tmp_sec)): + with Workflows(client_id=tmp_id, + client_secret=tmp_sec, + base_url=tmp_base, + debug=tmp_debug, + pythonic=True + ) as flows: + try: + for flow in flows.search_definitions().data: + returned.append(f"{flow.get('id')} ({flow.get('name')})") + except APIError: + pass + + return returned + + +def check_action() -> int: + """Check to see if a default action was specified on the command line.""" + returned = 0 + arglist = { + "-l": 0, + "--list_workflows": 0, + "-e": 1, + "--execute": 1, + "-le": 2, + "--list-executions": 2, + "-g": 3, + "--get_result": 3 + } + try: + for arg in sys.argv: + returned = arglist.get(arg, returned) + except (IndexError, ValueError): + pass + return returned + + +def check_for_json_format() -> int: + """Check to see if JSON formatted output was specified.""" + returned = 1 + try: + if "-j" in sys.argv: + returned = 0 + except (IndexError, ValueError): + pass + + return returned + + +def return_true() -> bool: + """Return true.""" + return True + + +@Gooey(advanced=True, + program_name="Falcon Fusion SOAR Workflow Manager", + program_description="List, execute, review and manage Fusion workflows.", + default_size=(1100, 610), + terminal_font_family='Courier New', + terminal_font_size=get_font_size(), + auto_start=check_for_autostart(), + show_stop_warning=False, + show_success_modal=False, + show_restart_button=False, + use_cmd_args=True, + tabbed_groups=True, + image_dir=get_image_dir(), + menu=[{"name": "Help", "items": MENU}] + ) +def consume_arguments() -> Namespace: # pylint: disable=R0915 + """Consume the provided command line.""" + parser = GooeyParser(description=__doc__, + formatter_class=RawTextHelpFormatter, + exit_on_error=False + ) + cmds = parser.add_argument_group("Command", description="Workflow command to perform") + excl = cmds.add_mutually_exclusive_group("Action", + gooey_options={"initial_selection": check_action()} + ) + excl.add_argument("-l", "--list_workflows", + help="List all workflows", + required=False, + action="store_true" + ) + excl.add_argument("-e", "--execute", + help="Execute the workflow specified on the Workflow tab", + required=False, + action="store_true" + ) + excl.add_argument("-le", "--list_executions", + help="List the executions for the workflow specified", + required=False, + action="store_true" + ) + excl.add_argument("-g", "--get_result", + help="Retrieve a workflow execution result", + required=False, + action="store_true" + ) + excl.add_argument("-ex", "--workflow_export", + help="Export a workflow", + required=False, + action="store_true" + ) + excl.add_argument("-im", "--workflow_import", + help="Import a workflow", + required=False, + action="store_true" + ) + wflow = parser.add_argument_group("Workflow", + description="Workflow or execution ID and workflow payload", + gooey_options={"columns": 1} + ) + wflow.add_argument("-i", "--id", + help="Workflow definition ID", + required=False, + choices=attempt_to_prefill_workflow_ids() + ) + wflow.add_argument("-ei", "--execution_id", + help="Workflow execution ID", + required=False + ) + wflow.add_argument("-p", "--payload", + help="Workflow execution payload", + required=False, + default='{\n "key": "value"\n}', + widget="Textarea" + ) + imp = parser.add_argument_group("Import", description="Import a workflow from a file") + imp.add_argument("-n", "--workflow_name", + help="Name for the imported workflow", + required=False + ) + imp.add_argument("-v", "--validate_only", + dest="validate", + help="Validate the workflow only, do not save upon import", + required=False, + default=False, + widget="BlockCheckbox", + action="store_true", + gooey_options={"checkbox_label": " Validate only"} + ) + imp.add_argument("-iw", "--import_workflow", + help="Location of the YAML workflow file to import", + required=False, + widget="FileChooser", + gooey_options={"default_dir": os.path.dirname(os.path.abspath(__file__)), + "wildcard": "YAML files (*.yml)|*.yml", + "message": "Select YAML file" + } + ) + exp = parser.add_argument_group("Export", description="Export a workflow to a file") + exp.add_argument("-ew", "--export_workflow", + help="Location to save the exported workflow (YAML format)\n" + "Use the Workflow tab to specify the desired workflow ID", + required=False, + widget="FileSaver", + gooey_options={"default_dir": os.path.dirname(os.path.abspath(__file__)), + "default_file": "exported.yml", "message": "Specify YAML file" + } + ) + auth = parser.add_argument_group("Environment", + description="Authentication and program execution options", + gooey_options={"columns": 3} + ) + auth.add_argument("-k", "--client_id", + help=prefill_check(), + required=False, + default=getenv("FALCON_CLIENT_ID"), + widget="PasswordField" + ) + auth.add_argument("-s", "--client_secret", + help=prefill_check("secret"), + required=False, + default=getenv("FALCON_CLIENT_SECRET"), + widget="PasswordField" + ) + auth.add_argument("-b", "--base_url", + help="CrowdStrike Region\n('auto' not implemented for usgov1 or usgov2)", + required=False, + default="auto", + choices=["auto", "us1", "us2", "eu1", "usgov1", "usgov2"] + ) + auth.add_argument("-lf", "--logfile", + help="Log output results to a local file as well as the console", + required=False, + widget="FileSaver", + gooey_options={"default_dir": os.path.dirname(os.path.abspath(__file__)), + "default_file": "workflow-manager.log", + "message": "Specify log file", + } + ) + auth.add_argument("-d", "--debug", + help=" Activate API debugging", + action="store_true", + required=False + ) + + auth.add_argument("-o", "--compress_output", + help=" Compress display output", + action="store_true", + default=False + ) + auth.add_argument("-sk", "--skip_preflight", + help=" Skip preflight API lookups", + default=False, + action="store_true", + gooey_options={"visible": False} + ) + frmt = auth.add_mutually_exclusive_group("Format", + gooey_options={ + "initial_selection": check_for_json_format(), + "show_border": False, + "show_underline": True + } + ) + frmt.add_argument("-j", "--json", + help="Display execution results in JSON format", + required=False, + action="store_true" + ) + frmt.add_argument("-t", "--table_format", + help="Tabular display format\n" + "Selecting CSV format will output to a file and display a table " + "to the console using simple format", + required=False, + default="simple", + choices=["plain", "simple", "github", "grid", "simple_grid", "rounded_grid", + "heavy_grid", "mixed_grid", "double_grid", "fancy_grid", "outline", + "simple_outline", "rounded_outline", "heavy_outline", + "mixed_outline", "double_outline", "fancy_outline", "pipe", "csv", + "orgtbl", "asciidoc", "jira", "presto", "pretty", "psql", "rst", + "mediawiki", "moinmoin", "youtrack", "html", "unsafehtml", "latex", + "latex_raw", "latex_booktabs", "latex_longtable", "textile", "tsv" + ] + ) + + arglist = sys.argv + for arg in arglist: + if arg in ["-h", "--help"]: + parser.print_help() + sys.exit(0) + + hold_id = None + try: + parsed = parser.parse_args() + except ArgumentError as bad_arg: + if "invalid choice" in bad_arg.message: + cnt = 0 + arglist = sys.argv + for arg in arglist: + if arg in ["-i", "--id"]: + hold_id = sys.argv[cnt+1] + sys.argv.pop(cnt+1) + sys.argv.pop(cnt) + cnt += 1 + else: + raise bad_arg + finally: + parsed = parser.parse_args() + if hold_id: + parsed.id = hold_id + + return parsed + + +def get_workflows(sdk: Workflows): + """Print a list of workflows within the tenant to the screen.""" + workflow_list = [] + pop_keys = ["trigger", "actions", "description", "conditions", "loops"] + for workflow in sdk.search_definitions().data: + pop_list = [k for k in pop_keys if k in workflow] + workflow["type"] = "Unknown" + if "conditions" in workflow: + workflow["type"] = "Event" + if "trigger" in workflow: + if "schedule" in workflow["trigger"]: + workflow["type"] = "Scheduled" + if "event" not in workflow["trigger"]: + workflow["type"] = "On demand" + for key in pop_list: + workflow.pop(key) + workflow_list.append(workflow) + + return workflow_list + + +def get_executions(sdk: Workflows, definition_id: str): + """Retrieve all executions for the specified workflow.""" + execution_list = [] + pop_keys = ["definition_id", "definition_version", "ancestor_executions", + "retryable", "trigger", "activities", "loops" + ] + if definition_id in [w["id"] for w in get_workflows(sdk)]: + for execution in sdk.search_executions(filter=f"definition_id:'{definition_id}'").data: + pop_list = [k for k in pop_keys if k in execution] + for key in pop_list: + execution.pop(key) + execution_list.append(execution) + else: + print("Invalid workflow ID") + + return execution_list + + +def log_write(logging_file: str, json_mode: bool, log_text: str): + """Perform the log action.""" + with open(logging_file, "a", encoding="utf-8") as logfile: + logfile.write("\n") + if json_mode: + dump(log_text, logfile, indent=4) + else: + logfile.write(log_text) + + +def display_executions(sdk: Workflows, + tformat: str, + use_json: bool, + log: str, + definition_id: str = None + ): + """Display the execution list to the terminal.""" + if not definition_id: + print("Invalid workflow ID specified.") + return + exec_list = get_executions(sdk, definition_id=definition_id.split(" ", maxsplit=1)[0]) + if exec_list: + if use_json: + if log: + log_write(log, use_json, exec_list) + print(dumps(exec_list, indent=4)) + else: + display_keys = {"execution_id": "ID", + "status": "Status", + "start_timestamp": "Start", + "end_timestamp": "End" + } + if log: + log_write(log, use_json, f"{EXECUTION_HEADER}\n" + f"{tabulate(exec_list, headers=display_keys, tablefmt=tformat)}" + ) + if tformat.lower() == "csv": + output_file = "workflow_executions.csv" + with open(output_file, "w", newline="", encoding="utf-8") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=display_keys.keys()) + writer.writeheader() + writer.writerows(exec_list) + print(f"Tabular results shown below have been output to {output_file}") + tformat = "simple" + print(EXECUTION_HEADER) + print(tabulate(exec_list, headers=display_keys, tablefmt=tformat)) + else: + print("No executions found.") + + +def display_workflows(sdk: Workflows, tformat: str, use_json: bool, log: str): + """Display the workflow list to the terminal.""" + workflow_list = get_workflows(sdk) + if workflow_list: + if use_json: + if log: + log_write(log, use_json, workflow_list) + print(dumps(workflow_list, indent=4)) + else: + display_keys = {"id": "ID", + "name": "Name", + "enabled": "Enabled", + "type": "Type", + "last_modified_timestamp": "Last modified", + "version": "Version" + } + if log: + log_write(log, use_json, f"{WORKFLOW_HEADER}\n" + f"{tabulate(workflow_list, headers=display_keys, tablefmt=tformat)}" + ) + if tformat.lower() == "csv": + output_file = "workflows.csv" + with open(output_file, "w", newline="", encoding="utf-8") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=display_keys.keys()) + writer.writeheader() + writer.writerows(workflow_list) + print(f"Tabular results shown below have been output to {output_file}") + tformat = "simple" + print(WORKFLOW_HEADER) + print(tabulate(workflow_list, + headers=display_keys, + tablefmt=tformat + )) + else: + print("No workflows found.") + + +def fix_quotes(inbound: str): + """Format the payload string and remove disallowed characters.""" + returned = inbound.replace('‘', '"').replace('’', '"').replace('“', '"').replace('”', '"') + return loads(returned) + + +def execute_workflow(sdk: Workflows, payload: str, log: str, definition_id: str = None): + """Execute the specified workflow.""" + def inform(msg: str): + """Display and log the output.""" + if log: + log_write(log, False, msg) + print(msg) + + if not definition_id: + print("Invalid workflow ID specified.") + return + + definition_id = definition_id.split(" ", maxsplit=1)[0] + inform(f"Attempting to execute workflow {definition_id}") + if definition_id in [w["id"] for w in get_workflows(sdk)]: + try: + result: Result = sdk.execute(definition_id=definition_id, body=fix_quotes(payload)) + if result.status_code == 200: + inform(f"Workflow execution {result.data} triggered successfully") + else: + for err in result.errors: + inform(f"[{err['code']}] {err['message']}") + except APIError as failure: + output = failure.message + if log: + log_write(log, False, output) + print(output) + + else: + inform("Invalid workflow ID specified.") + + +def display_execution_results(sdk: Workflows, execution_id: str, use_json: bool, log: str): + """Display the results for the specified execution.""" + if not execution_id: + print("Invalid execution ID specified.") + return + print(RESULT_HEADER) + try: + for result in sdk.execution_results(ids=execution_id).data: + for activity in result.get("activities"): + output = activity.get("result") + if log: + log_write(log, use_json, output if use_json else str(output)) + if use_json: + output = dumps(output, indent=4) + print(output) + except APIError as err: + print(err) + + +def export_workflow(sdk: Workflows, definition_id: str, filename: str): + """Export the specified workflow to a YAML file.""" + if not definition_id: + print("You must specify a workflow definition ID to export.") + else: + definition_id = definition_id.split(" ", maxsplit=1)[0] + print(EXPORT_HEADER) + print(f"Exporting workflow {definition_id} to {filename}...\n") + try: + with open(filename, "wb") as export_file: + export_file.write(sdk.export_definition(id=definition_id).data) + except APIError as err: + print(err) + + +def import_workflow(sdk: Workflows, name: str, filename: str, validate_only: bool): + """Import the specified YAML file as a workflow.""" + print(IMPORT_HEADER) + stub = "Validat" if validate_only else "Import" + nam = "a new workflow" if not name else name + print(f"{stub}ing {filename} as {nam}...\n") + try: + response: Result = sdk.import_definition(name=name, + data_file=filename, + validate_only=validate_only + ) + if response.status_code == 200: + if validate_only: + print("Workflow import passes validation.") + else: + print(f"Workflow imported successfully [{response.data[0].get('id')}].") + except APIError as err: + print(err) + + +def main(): + """Execute the main routine.""" + cmd_line = consume_arguments() + _debug = False + if cmd_line.debug: + # Activate debug logging + logging.basicConfig(level=logging.DEBUG) + _debug = True + # Check for a specified action, if not present, set the default to list all workflows + if not max(cmd_line.execute, + cmd_line.list_workflows, + cmd_line.list_executions, + cmd_line.get_result, + cmd_line.workflow_export, + cmd_line.workflow_import + ): + cmd_line.list_workflows = True + # Open the Service Collection as a context manager so we log out automatically + with Workflows(debug=_debug, + pythonic=True, + client_id=cmd_line.client_id, + client_secret=cmd_line.client_secret, + base_url=cmd_line.base_url + ) as workflows: + + if not workflows.token_valid: + print(LOGIN_FAIL) + return + + if cmd_line.execute: + execute_workflow(workflows, cmd_line.payload, cmd_line.logfile, cmd_line.id) + + if cmd_line.list_executions: + display_executions(workflows, + cmd_line.table_format, + cmd_line.json, + cmd_line.logfile, + cmd_line.id + ) + + if cmd_line.list_workflows: + display_workflows(workflows, + cmd_line.table_format, + cmd_line.json, + cmd_line.logfile + ) + + if cmd_line.get_result: + display_execution_results(workflows, + cmd_line.execution_id, + cmd_line.json, + cmd_line.logfile + ) + + if cmd_line.workflow_export: + export_workflow(workflows, cmd_line.id, cmd_line.export_workflow) + + if cmd_line.workflow_import: + import_workflow(workflows, + cmd_line.workflow_name, + cmd_line.import_workflow, + cmd_line.validate + ) + if not cmd_line.compress_output: + # Divide multiple execution results in the console display with a horizontal rule + print(f"\n{'_' * 120}\n") + + +WORKFLOW_HEADER = r""" +_ _ _ ____ ____ _ _ ____ _ ____ _ _ _ ____ +| | | | | |__/ |_/ |___ | | | | | | [__ +|_|_| |__| | \ | \_ | |___ |__| |_|_| ___] +""" + +EXECUTION_HEADER = r""" +_ _ _ ____ ____ _ _ ____ _ ____ _ _ _ ____ _ _ ____ ____ _ _ ___ _ ____ _ _ ____ +| | | | | |__/ |_/ |___ | | | | | | |___ \/ |___ | | | | | | | |\ | [__ +|_|_| |__| | \ | \_ | |___ |__| |_|_| |___ _/\_ |___ |___ |__| | | |__| | \| ___] +""" + +RESULT_HEADER = r""" +____ _ _ ____ ____ _ _ ___ _ ____ _ _ ____ ____ ____ _ _ _ ___ ____ +|___ \/ |___ | | | | | | | |\ | |__/ |___ [__ | | | | [__ +|___ _/\_ |___ |___ |__| | | |__| | \| | \ |___ ___] |__| |___ | ___] +""" + +IMPORT_HEADER = r""" +_ _ _ ___ ____ ____ ___ +| |\/| |__] | | |__/ | +| | | | |__| | \ | +""" + +EXPORT_HEADER = r""" +____ _ _ ___ ____ ____ ___ +|___ \/ |__] | | |__/ | +|___ _/\_ | |__| | \ | +""" + +LOGIN_FAIL = r""" + + _\\/|(/_ + >_ _ / + (_)-(_) + ) o ( + \ = / Invalid CrowdStrike API credentials or region specified. + |W| + | | + __| |__ + / \ u / \ + | `-' | + |__| |__| + |||ADM||| + ||| ||| + ||| ||| +""" + + +if __name__ == "__main__": + gui_image("program_icon.png", "program_icon.png") + gui_image("csfalcon.png", "running_icon.png") + gui_image("cs-logo.png", "config_icon.png") + main() diff --git a/samples/workflows/workflow_manager_gui_requirements.txt b/samples/workflows/workflow_manager_gui_requirements.txt new file mode 100644 index 00000000..2771dc98 --- /dev/null +++ b/samples/workflows/workflow_manager_gui_requirements.txt @@ -0,0 +1,4 @@ +crowdstrike-falconpy +gooey +requests +tabulate \ No newline at end of file diff --git a/samples/workflows/workflow_manager_requirements.txt b/samples/workflows/workflow_manager_requirements.txt new file mode 100644 index 00000000..0dc177d1 --- /dev/null +++ b/samples/workflows/workflow_manager_requirements.txt @@ -0,0 +1,3 @@ +crowdstrike-falconpy +tabulate +termcolor \ No newline at end of file