diff --git a/samples/README.md b/samples/README.md index 55b38087d..aef43f11d 100644 --- a/samples/README.md +++ b/samples/README.md @@ -43,6 +43,7 @@ The following samples are categorized by CrowdStrike Falcon API service collecti | [Hosts](#hosts) | [List sensors by hostname](#list-sensors-by-hostname)
[Manage duplicate sensors](#manage-duplicate-sensors)
[CUSSED (Manage stale sensors)](#cussed-manage-stale-sensors)
[Match usernames to hosts](#match-usernames-to-hosts)
[Offset vs. Token](#offset-vs-token)
[Prune Hosts by Hostname or AID](#prune-hosts-by-hostname-or-aid)
[Quarantine a host](#quarantine-a-host)
[Quarantine a host (updated version)](#quarantine-a-host-updated-version) | | [Identity Protection](#identity-protection) | [GraphQL Pagination](#graphql-pagination) | | [Incidents](#incidents) | [CrowdScore QuickChart](#crowdscore-quickchart)
[Incident Triage](#incident-triage) | +| [Installation Tokens](#installation-tokens) | [Token Dispenser](#token-dispenser) | | [Intel](#intel) | [MISP Import](#misp-import)
[Intel Search](#intel-search) | | [IOC](#ioc) | [Create indicators](#create-indicators) | | [MalQuery](#malquery) | [Malqueryinator](#malqueryinator) | @@ -643,6 +644,41 @@ This sample demonstrates the following CrowdStrike Incidents API operations: --- +## Installation Tokens +This category is dedicated to demonstrating the functionality provided by the CrowdStrike Installation Tokens API service collection. + +- [Token Dispenser](#token-dispenser) + +### Token Dispenser +Easily manage installation tokens within your tenant or across child tenants with the [Token Dispenser](installation_tokens#token-dispenser). + +[![Installation Tokens](https://img.shields.io/badge/Service%20Class-Token_Dispenser-silver?style=for-the-badge&labelColor=red&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==)](installation_tokens#token-dispenser) +[![MSSP Use supported](https://img.shields.io/badge/-Supports%20MSSP-darkblue?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==&style=for-the-badge)](installation_tokens#token-dispenser) + +#### Installation Tokens API operations discussed +This sample demonstrates the following CrowdStrike Installation Tokens API operations: + +| Operation | Description | +| :--- | :--- | +| [tokens_create](https://www.falconpy.io/Service-Collections/Installation-Tokens.html#tokens_create) | Creates a token. | +| [tokens_delete](https://www.falconpy.io/Service-Collections/Installation-Tokens.html#tokens_delete) | Deletes a token immediately. To revoke a token, use `token_update` instead. | +| [tokens_read](https://www.falconpy.io/Service-Collections/Installation-Tokens.html#tokens_read) | Get the details of one or more tokens by ID. | +| [tokens_update](https://www.falconpy.io/Service-Collections/Installation-Tokens.html#tokens_update) | Updates one or more tokens. Use this endpoint to edit labels, change expiration, revoke, or restore. | + +#### Flight Control API operations discussed +This sample demonstrates the following CrowdStrike Flight Control API operations: +| Operation | Description | +| :--- | :--- | +| [queryChildren](https://www.falconpy.io/Service-Collections/MSSP.html#querychildren) | Query for customers linked as children. | + +#### Sensor Download API operations discussed +This sample demonstrates the following CrowdStrike Sensor Download API operations: +| Operation | Description | +| :--- | :--- | +| [GetSensorInstallersCCIDByQuery](https://www.falconpy.io/Service-Collections/Sensor-Download.html#getsensorinstallersccidbyquery) | Get CCID to use with sensor installers. | + +--- + ## Intel This category provides samples that demonstrate the CrowdStrike Falcon Intel API service collection. diff --git a/samples/installation_tokens/README.md b/samples/installation_tokens/README.md new file mode 100644 index 000000000..771e6e1dc --- /dev/null +++ b/samples/installation_tokens/README.md @@ -0,0 +1,908 @@ +![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) + +# Installation Tokens examples +The examples in this folder focus on leveraging CrowdStrike's Installation Tokens API to manage sensor installation tokens. +- [Token Dispenser](#token-dispenser) + +## Token Dispenser +This application displays and manages installation tokens within your CrowdStrike tenant. +> [!NOTE] +> This solution supports Flight Control (MSSP) usage for all functionality, allowing administrators to manage multiple tokens across child tenants with a single command. + +- [Requirements](#requirements) +- [Running the program](#running-the-program) +- [Execution syntax](#execution-syntax) +- [Commands](#commands) +- [Source code](#example-source-code) + +### Requirements +- [Python 3.7 or greater](https://www.python.org) +- [CrowdStrike FalconPy v1.3.4 or greater](https://github.com/CrowdStrike/falconpy/releases/tag/v1.3.4) +- [pyfiglet](https://pypi.org/project/pyfiglet/) +- [tabulate](https://pypi.org/project/tabulate/) + +### Running the program +In order to run this demonstration, you will need access to CrowdStrike API keys with the following scope: +| Service Collection | Scope | +| :---- | :---- | +| Installation Tokens | __READ__, __WRITE__ | + +To take advantage of MSSP mode (Flight Control) functionality, you will also need the following scopes: +| Service Collection | Scope | +| :---- | :---- | +| Flight Control | __READ__ | +| Sensor Downloads | __READ__ | + +> [!NOTE] +> All operations within the Installation Tokens service collection maintain low rate limits. This application automatically backs off and retries the request when these limits are exceeded. + +### Execution syntax +This application provides multiple commands, each with unique options. + +```shell +python3 token_dispenser.py [-h] command [options] +``` + +##### Command line help +The menu of commands can be retrieved by providing `-h` on the command line with no other arguments. + +```shell +Installation Token management utility. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | |::.. . | FalconPy v1.3.4 +`-------' `-------' + +_______ _____ _ _ _______ __ _ + | | | |____/ |______ | \ | + | |_____| | \_ |______ | \_| + +______ _____ _______ _____ _______ __ _ _______ _______ ______ +| \ | |______ |_____] |______ | \ | |______ |______ |_____/ +|_____/ __|__ ______| | |______ | \_| ______| |______ | \_ + + .-------. with ________) + |Jackpot| (, / /) , /) + ____________|_______|____________ /___, // _ (/ _/_ + | __ __ ___ _____ __ | ) / (/__(_(_/_/ )_(__ + | / _\ / / /___\/__ \ / _\ | (_/ .-/ + | \ \ / / // // / /\ \\ \ 25| (_/ ) ___ + | _\ \/ /___/ \_// / / \/_\ \ []| __ (__/_____) /) + | \__/\____/\___/ \/ \__/ []| (__) / _____ _/_ __ ___// + |===_______===_______===_______===| || / (_) / (_(__/ (_(_)(/_ + ||*| _____ |*| ,_ |*| ___ |*|| || (______) + ||*|| ||*| | \ _ |*| |_ | |*|| || + ||*||*BAR*||*| \_(_)|*| / / |*|| || + ||*||_____||*| (_) |*| /_/ |*|| || + ||*|_______|*|_______|*|_______|*||_// Creation date: 11.15.2023 + | \=___________________________=/ |_/ jshcodes@CrowdStrike + _| \_______________________/ |_ WE STOP BREACHES +(_____________________________________) + +positional arguments: + Token command Command description + list (l) List all tokens [default] + create (c) Create tokens + revoke (x) Revoke tokens + restore (r) Restore tokens + update (u) Update tokens + delete (d) Delete tokens + +optional arguments: + -h, --help show this help message and exit +``` + +### Commands +The token dispenser supports 6 primary commands, each accepting optional arguments that alter how the command is performed. +When using [MSSP mode](#flight-control-mssp-mode-arguments) operations performed cross all tenants. +> Example: Calling the `list` command while also enabling MSSP mode with the `-m` command line argument will show tokens for the parent and all children. + +- [List](#list-tokens) - List all tokens within the environment. +- [Create](#create-tokens) - Create one or multiple tokens with a specified expiration and label. +- [Revoke](#revoke-tokens) - Revoke one or multiple tokens by label or ID. +- [Restore](#restore-tokens) - Restore one or multiple tokens by label or ID. +- [Update](#update-tokens) - Update the label or expiration for one or multiple tokens by label or ID. +- [Delete](#delete-tokens) - Delete one or multiple tokens by label or ID. + +#### Authentication, display and saving results to a file +All commands accept universal arguments that may be mixed with command-specific arguments. +These arguments control configuration elements that are shared across all available commands such as: +- Authentication +- Flight Control (MSSP mode) +- Display options (such as filtering, sorting and formatting) +- Outputting displayed results to CSV or JSON format + +##### Universal arguments +The following options are available as command line arguments regardless of command performed. +Universal arguments may be provided in any order. + +###### General, display and output arguments +These arguments allow users to control debug and result display settings. +Results can also be exported to a file in JSON or CSV format using these options. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | + | `-h` | `--help` | Show help for the specified command and exit. | General | + | `-d` | `--debug` | Enable debug. | General | + | `-f` FILTER | `--filter` FILTER | Filter results by searching token labels (stemmed search). | Display | + | `-o` ORDER_BY | `--order-by` ORDER_BY | Sort key to use for tabular displays. | Display | + | `-r` | `--reverse` | Reverses the sort order. | Display | + | `-t` TABLE_FORMAT | `--table-format` TABLE_FORMAT | Format to use for tabular output. | Display | + | `-v` | `--show-version` | Show FalconPy version in output. | Display | + | | `--output-file` OUTPUT_FILE | Output token list results to a CSV or JSON file. | Output | + | | `--output-format` OUTPUT_FORMAT | Set output file format.

Allowed options: | Output | + +###### Authentication arguments +> [!NOTE] +> The following arguments are not required when you are using [environment authentication](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication). + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| `-k` CLIENT_ID | `--client_id` CLIENT_ID | Falcon API client ID. | Authentication | +| `-s` CLIENT_SECRET | `--client_secret` CLIENT_SECRET | Falcon API client secret. | Authentication + +###### Flight Control (MSSP mode) arguments +> [!NOTE] +> The following arguments are not required when you are not using Flight Control. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| `-c` CHILD | `--child` CHILD | CID of the child tenant to target. | MSSP | +| `-m` | `--mssp` | Flight Control (MSSP) mode.
Commands executed are performed within every tenant unless the parent is explicitly skipped. | MSSP | +| | `--skip-parent`| Do not execute commands within the parent tenant. | MSSP | +| | `--show-tenant` | Display tenant CID values as part of execution. | MSSP | + +##### Examples +The following examples demonstrate different universal argument variations. + +###### Enable debugging +Passing the `-d` (`--debug`) argument will enable API debugging for every operation performed. + +```shell +python3 token_dispenser.py -d +``` + +###### Filter display results by label +The `-f` (`--filter`) option will only display results that include the word "Example" in any position within the label. + +```shell +python3 token_dispenser.py -f Example +``` + +###### Sort display results +You can sort results by any column in the display results using the `-o` (`order-by`) argument. +Using the `-r` (`--reverse`) argument will reverse the sort. + +```shell +python3 token_dispenser.py -o status -r +``` + +###### Change the display table format +You can change the format of the display table to any of the following options using the `-t` (`table-format`) argument. + +```shell +python3 token_dispenser.py -t fancy_grid +``` + +###### *Avialable table format options* +| | | | | | | +| :-- | :-- | :-- | :-- | :-- | :-- | +| `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` | +| `orgtbl` | `asciidoc` | `jira` | `presto` | `pretty` | `psql` | +| `rst` | `mediawiki` | `moinmoin` | `youtrack` | `html` | `unsafehtml` | +| `latex` | `latex_raw` | `latex_booktabs` | `latex_longtable` | `textile` | `tsv` | + + +###### Authenticating to a single tenant +If you are not using [Environment Authentication](https://www.falconpy.io/Usage/Authenticating-to-the-API.html#environment-authentication), you will need to provide authentication detail on the command line using the `-k` (`--client-id`) and `-s` (`--client-secret`) arguments. + +```shell +python3 token_dispenser.py -k $FALCON_CLIENT_ID -s $FALCON_CLIENT_SECRET +``` + +###### Authenticating to a parent tenant and enabling MSSP mode +MSSP mode will perform commands against all child tenants and the parent (if not explicitly skipped using the `--skip-parent` argument). +This includes API calls used to create display results. + +```shell +python3 token_dispenser.py -k $PARENT_CLIENT_ID -s $PARENT_CLIENT_SECRET -m +``` + +###### Authenticating as a parent to a single child +You can also directly authenticate (as a parent) to the child tenant using the `-c` (`--child`) argument. +This argument does not require MSSP mode and may be provided with or without the `-m` argument. + +```shell +python3 token_dispenser.py -k $PARENT_CLIENT_ID -s $PARENT_CLIENT_SECRET -c $CHILD_TENANT_CID +``` + +###### Displaying the tenant ID +You can display the tenant ID for the parent and child tenants before the operation is performed with the `--show-tenant` argument. + +```shell +python3 token_dispenser.py --show-tenant +``` + +--- + +#### List tokens +The list command is the default command, and is executed when no command is specified. +After the execution of any other command, the list command is executed to display the results generated. + +There are no list command-specific arguments. All universal arguments are accepted. + +##### Command line help (list) +Command-line help for this command is available when the command is called along with the `-h` argument. + +```shell +usage: token_dispenser.py list [-h] [-d] [-f FILTER] [-o ORDER_BY] [-r] [-t TABLE_FORMAT] [-v] [--output-file OUTPUT_FILE] [--output-format {csv,json}] [-k CLIENT_ID] [-s CLIENT_SECRET] [-c CHILD] [-m] [--skip-parent] + [--show-tenant] + + _ _ _ +| | (_) | | +| | _ ___| |_ +| | | / __| __| +| |____| \__ \ |_ +|______|_|___/\__| + + + +optional arguments: + -h, --help show this help message and exit + -d, --debug Enable debug. + -f FILTER, --filter FILTER + Filter results by searching token labels (stemmed search). + -o ORDER_BY, --order-by ORDER_BY + Sort key to use for tabular displays. + -r, --reverse Reverses the sort order. + -t TABLE_FORMAT, --table-format TABLE_FORMAT + Format to use for tabular output. + -v, --show-version Show FalconPy version in output. + --output-file OUTPUT_FILE + Output token list results to a CSV or JSON file. + --output-format {csv,json} + Set output file format. + +authentication arguments (not required if using environment authentication): + -k CLIENT_ID, --client_id CLIENT_ID + Falcon API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + Falcon API client secret + +mssp arguments: + -c CHILD, --child CHILD + CID of the child tenant to target. + -m, --mssp Flight Control (MSSP) mode. + --skip-parent Do not take action within the parent tenant. + --show-tenant Display tenant CID values. +``` + +--- + +#### Create tokens +Create tokens within your tenant, or across parent and child tenants simultaneously. Supports the creation of multiple tokens with specified expiration dates. +Expiration may be set by number of days or by specifying a specific date in UTC format. + +##### Create command arguments +There are two create command-specific required arguments (`token-label` and `expiration`). There are also two optional arguments `count` and `force`. +All [universal arguments](#universal-arguments) are supported and can be mixed with create command arguments in any order or combination. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| | `--force` | Perform the operation without asking for confirmation. | General | +| `-l` TOKEN_LABEL | `--token-label` TOKEN_LABEL | Label for the token. | Create | +| `-e` EXPIRATION | `--expiration` EXPIRATION | Token expiration.
(number of days or a specific date in `YYYY-mm-ddTHH:MM:SSZ` format). | Create | +| `-n` COUNT | `--count` COUNT | Number of tokens to create. | Create | + +##### Examples +The following examples demonstrate different create command variations. + +###### Create a single token in a standard tenant +This example will create a token labelled "ExampleToken" with an expiration of 5 days from now. + +```shell +python3 token_dispenser.py create -l ExampleToken -e 5 +``` + +##### Flight Control examples +> [!IMPORTANT] +> You must provide either the MSSP mode (`-m`) or the child (`-c`) argument in order to execute operations within child tenants. + +###### Create a single token across the parent and child tenants +This example will create a token labelled "ExampleToken" with an expiration 10 days from now in the parent and every child tenant. + +```shell +python3 token_dispenser.py create -l ExampleToken -e 10 -m +``` + +###### Create multiple tokens in all child tenants but do not create one in the parent +This example will create three tokens with a specific expiration date, labelled "ExampleToken1", "ExampleToken2", and "ExampleToken3" within child tenants. +The parent tenant will remain unchanged as the `skip-parent` argument has been provided. + +```shell +python3 token_dispenser.py create -l ExampleToken -e 2025-01-01T00:00:01Z -n 3 -m --skip-parent +``` + +> [!NOTE] +> To skip the confirmation dialog presented when performing multi-tenant operations, provide the `--force` argument. This argument has no impact on operations where a confirmation dialog is not normally presented. + +##### Command line help (create) +Command-line help for this command is available when the command is called along with the `-h` argument. + +```shell +usage: token_dispenser.py create [-h] -l TOKEN_LABEL -e EXPIRATION [-n COUNT] [--force] [-d] [-f FILTER] [-o ORDER_BY] [-r] [-t TABLE_FORMAT] [-v] [--output-file OUTPUT_FILE] [--output-format {csv,json}] [-k CLIENT_ID] + [-s CLIENT_SECRET] [-c CHILD] [-m] [--skip-parent] [--show-tenant] + + _____ _ + / ____| | | +| | _ __ ___ __ _| |_ ___ +| | | '__/ _ \/ _` | __/ _ \ +| |____| | | __/ (_| | || __/ + \_____|_| \___|\__,_|\__\___| + + + +optional arguments: + -h, --help show this help message and exit + -n COUNT, --count COUNT + Number of tokens to create + --force Perform the operation without asking for confirmation. + -d, --debug Enable debug. + -f FILTER, --filter FILTER + Filter results by searching token labels (stemmed search). + -o ORDER_BY, --order-by ORDER_BY + Sort key to use for tabular displays. + -r, --reverse Reverses the sort order. + -t TABLE_FORMAT, --table-format TABLE_FORMAT + Format to use for tabular output. + -v, --show-version Show FalconPy version in output. + --output-file OUTPUT_FILE + Output token list results to a CSV or JSON file. + --output-format {csv,json} + Set output file format. + +required arguments: + -l TOKEN_LABEL, --token-label TOKEN_LABEL + Label for the token. + -e EXPIRATION, --expiration EXPIRATION + Token expiration (number of days or YYYY-mm-ddTHH:MM:SSZ). + +authentication arguments (not required if using environment authentication): + -k CLIENT_ID, --client_id CLIENT_ID + Falcon API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + Falcon API client secret + +mssp arguments: + -c CHILD, --child CHILD + CID of the child tenant to target. + -m, --mssp Flight Control (MSSP) mode. + --skip-parent Do not take action within the parent tenant. + --show-tenant Display tenant CID values. +``` + +--- + +#### Revoke tokens +Revoke tokens within your tenant, or across parent and child tenants simultaneously. Supports the revocation of multiple tokens. + +##### Revoke command arguments +There are two revoke command-specific required arguments (`token-id` and `token-label`). These arguments are mutually exclusive. There is one optional argument `force`. +All [universal arguments](#universal-arguments) are supported and can be mixed with create command arguments in any order or combination. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| | `--force` | Perform the operation without asking for confirmation. | General | +| `-i` TOKEN_ID | `--token-id` TOKEN_ID | ID of the token to revoke. | Revoke | +| `-l` TOKEN_LABEL | `--token-label` TOKEN_LABEL | Label of the token to revoke (starts with match). | Revoke | + +##### Examples +The following examples demonstrate different revoke command variations. + +###### Revoke tokens in a standard tenant +This example will revoke any token with a label starting with "ExampleToken". + +```shell +python3 token_dispenser.py revoke -l ExampleToken +``` + +You can also revoke specific tokens by ID. + +```shell +python3 token_dispenser.py delete -i $TOKEN_ID +``` + +##### Flight Control examples +> [!IMPORTANT] +> You must provide the MSSP mode (`-m`) argument in order to access child tenants. If you wish processing to only occur within child tenants, you must provide the `--skip-parent` argument. + +###### Revoke a single token in a child tenant +This example will revoke a single token within a child tenant. + +```shell +python3 token_dispenser.py revoke -i $TOKEN_ID -c $CHILD_TENANT_CID +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for a token that matches the ID. + +```shell +python3 token_dispenser.py revoke -i $TOKEN_ID -m +``` + +###### Revoke tokens in a child tenant that have a label starting with a specific string +This example will revoke tokens labelled "ExampleToken" (or any variation starting with this string) within child tenants. + +```shell +python3 token_dispenser.py revoke -l ExampleToken -c $CHILD_TENANT_CID +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for labels that match the specified string. + +```shell +python3 token_dispenser.py revoke -l ExampleToken -m +``` + +> [!NOTE] +> To skip the confirmation dialog presented when performing multi-tenant operations, provide the `--force` argument. This argument has no impact on operations where a confirmation dialog is not normally presented. + +##### Command line help (revoke) +Command-line help for this command is available when the command is called along with the `-h` argument. + +```shell +usage: token_dispenser.py revoke [-h] (-i TOKEN_ID | -l TOKEN_LABEL) [--force] [-d] [-f FILTER] [-o ORDER_BY] [-r] [-t TABLE_FORMAT] [-v] [--output-file OUTPUT_FILE] [--output-format {csv,json}] [-k CLIENT_ID] + [-s CLIENT_SECRET] [-c CHILD] [-m] [--skip-parent] [--show-tenant] + + _____ _ +| __ \ | | +| |__) |_____ _____ | | _____ +| _ // _ \ \ / / _ \| |/ / _ \ +| | \ \ __/\ V / (_) | < __/ +|_| \_\___| \_/ \___/|_|\_\___| + + + +optional arguments: + -h, --help show this help message and exit + --force Perform the operation without asking for confirmation. + -d, --debug Enable debug. + -f FILTER, --filter FILTER + Filter results by searching token labels (stemmed search). + -o ORDER_BY, --order-by ORDER_BY + Sort key to use for tabular displays. + -r, --reverse Reverses the sort order. + -t TABLE_FORMAT, --table-format TABLE_FORMAT + Format to use for tabular output. + -v, --show-version Show FalconPy version in output. + --output-file OUTPUT_FILE + Output token list results to a CSV or JSON file. + --output-format {csv,json} + Set output file format. + +required arguments (mutually exclusive): + -i TOKEN_ID, --token-id TOKEN_ID + ID of the token to revoke. + -l TOKEN_LABEL, --token-label TOKEN_LABEL + Label of the token to revoke (starts with match). + +authentication arguments (not required if using environment authentication): + -k CLIENT_ID, --client_id CLIENT_ID + Falcon API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + Falcon API client secret + +mssp arguments: + -c CHILD, --child CHILD + CID of the child tenant to target. + -m, --mssp Flight Control (MSSP) mode. + --skip-parent Do not take action within the parent tenant. + --show-tenant Display tenant CID values. +``` + +--- + +#### Restore tokens +Restore tokens within your tenant, or across parent and child tenants simultaneously. Supports the restoration of multiple tokens. + +##### Restore command arguments +There are two restore command-specific required arguments (`token-id` and `token-label`). These arguments are mutually exclusive. There is one optional argument `force`. +All [universal arguments](#universal-arguments) are supported and can be mixed with create command arguments in any order or combination. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| | `--force` | Perform the operation without asking for confirmation. | General | +| `-i` TOKEN_ID | `--token-id` TOKEN_ID | ID of the token to restore. | Restore | +| `-l` TOKEN_LABEL | `--token-label` TOKEN_LABEL | Label of the token to restore (starts with match). | Restore | + +##### Examples +The following examples demonstrate different restore command variations. + +###### Restore tokens in a standard tenant +This example will restore any token with a label starting with "ExampleToken". + +```shell +python3 token_dispenser.py restore -l ExampleToken +``` + +You can also restore specific tokens by ID. + +```shell +python3 token_dispenser.py restore -i $TOKEN_ID +``` + +##### Flight Control examples +> [!IMPORTANT] +> You must provide the MSSP mode (`-m`) argument in order to access child tenants. If you wish processing to only occur within child tenants, you must provide the `--skip-parent` argument. + +###### Restore a single token in a child tenant +This example will restore a single token within a child tenant. + +```shell +python3 token_dispenser.py restore -i $TOKEN_ID -c $CHILD_TENANT_CID +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for a token that matches the ID. + +```shell +python3 token_dispenser.py restore -i $TOKEN_ID -m +``` + +###### Restore tokens in a child tenant that have a label starting with a specific string +This example will restore tokens labelled "ExampleToken" (or any variation starting with this string) within child tenants. + +```shell +python3 token_dispenser.py restore -l ExampleToken -c $CHILD_TENANT_CID +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for labels that match the specified string. + +```shell +python3 token_dispenser.py restore -l ExampleToken -m +``` + +> [!NOTE] +> To skip the confirmation dialog presented when performing multi-tenant operations, provide the `--force` argument. This argument has no impact on operations where a confirmation dialog is not normally presented. + +##### Command line help (restore) +Command-line help for this command is available when the command is called along with the `-h` argument. + +```shell +usage: token_dispenser.py restore [-h] (-i TOKEN_ID | -l TOKEN_LABEL) [--force] [-d] [-f FILTER] [-o ORDER_BY] [-r] [-t TABLE_FORMAT] [-v] [--output-file OUTPUT_FILE] [--output-format {csv,json}] [-k CLIENT_ID] + [-s CLIENT_SECRET] [-c CHILD] [-m] [--skip-parent] [--show-tenant] + + _____ _ +| __ \ | | +| |__) |___ ___| |_ ___ _ __ ___ +| _ // _ \/ __| __/ _ \| '__/ _ \ +| | \ \ __/\__ \ || (_) | | | __/ +|_| \_\___||___/\__\___/|_| \___| + + + +optional arguments: + -h, --help show this help message and exit + --force Perform the operation without asking for confirmation. + -d, --debug Enable debug. + -f FILTER, --filter FILTER + Filter results by searching token labels (stemmed search). + -o ORDER_BY, --order-by ORDER_BY + Sort key to use for tabular displays. + -r, --reverse Reverses the sort order. + -t TABLE_FORMAT, --table-format TABLE_FORMAT + Format to use for tabular output. + -v, --show-version Show FalconPy version in output. + --output-file OUTPUT_FILE + Output token list results to a CSV or JSON file. + --output-format {csv,json} + Set output file format. + +required arguments (mutually exclusive): + -i TOKEN_ID, --token-id TOKEN_ID + ID of the token to restore. + -l TOKEN_LABEL, --token-label TOKEN_LABEL + Label of the token to restore (starts with match). + +authentication arguments (not required if using environment authentication): + -k CLIENT_ID, --client_id CLIENT_ID + Falcon API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + Falcon API client secret + +mssp arguments: + -c CHILD, --child CHILD + CID of the child tenant to target. + -m, --mssp Flight Control (MSSP) mode. + --skip-parent Do not take action within the parent tenant. + --show-tenant Display tenant CID values. +``` + +--- + +#### Update tokens +Update tokens within your tenant, or across parent and child tenants simultaneously. Supports the restoration of multiple tokens. + +##### Update command arguments +There are two sets of update command-specific required arguments. The first set includes `token-id` and `token-label` which are mutually exclusive to each other. +The second set of required arguments includes `add-days`, `expiration` and `new_token_label`. These three are mutually exclusive to each other. There is one optional argument `force`. +All [universal arguments](#universal-arguments) are supported and can be mixed with create command arguments in any order or combination. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| | `--force` | Perform the operation without asking for confirmation. | General | +| `-i` TOKEN_ID | `--token-id` TOKEN_ID | ID of the token to update. | Update | +| `-l` TOKEN_LABEL | `--token-label` TOKEN_LABEL | Label of the token to update (starts with match). | Update | +| `-a` ADD_DAYS | `--add-days` ADD_DAYS | Add specified number of days to token expiration. | +| `-e` EXPIRATION | `--expiration` EXPIRATION | Token expiration (`YYYY-mm-ddTHH:MM:SSZ` format). | Update | +| `-n` NEW_TOKEN_LABEL | `--new-label` NEW_TOKEN_LABEL | New label for the token. | Update | + +##### Examples +The following examples demonstrate different update command variations. + +###### Update tokens in a standard tenant to extend the expiration +This example will update all tokens with a label starting with "ExampleToken" and add 5 days to the expiration. + +```shell +python3 token_dispenser.py update -l ExampleToken -a 5 +``` + +You can also update specific tokens by ID. + +```shell +python3 token_dispenser.py update -i $TOKEN_ID -a 5 +``` + +###### Update tokens in a standard tenant to a specific expiration +This example will update all tokens with a label starting with "ExampleToken" to have the specified expiration date. + +```shell +python3 token_dispenser.py update -l ExampleToken -e 2025-01-01T12:01:01Z +``` + +You can also perform this update on a specific token by providing the ID. + +```shell +python3 token_dispenser.py update -i $TOKEN_ID -e 2025-01-01T12:01:01Z +``` + +###### Change the label of tokens within a standard tenant +This example will change the label for any token with a label starting with "ExampleToken" to be "NewExampleToken". If multiple tokens are renamed within a tenant, a number will be appended at the end of each. + +```shell +python3 token_dispenser.py update -l ExampleToken -n NewExampleToken +``` + +You can also update a token label by providing the specific token ID. + +```shell +python3 token_dispenser.py delete -i $TOKEN_ID -n NewExampleToken +``` + +##### Flight Control examples +> [!IMPORTANT] +> You must provide the MSSP mode (`-m`) argument in order to access child tenants. If you wish processing to only occur within child tenants, you must provide the `--skip-parent` argument. + +###### Update a single token to extend the expiration +This example will update a single token within a parent or child tenant to add 5 days to the expiration. + +```shell +python3 token_dispenser.py update -i $TOKEN_ID -c $CHILD_TENANT_CID -a 5 +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for the token with the matching ID. + +```shell +python3 token_dispenser.py update -i $TOKEN_ID -m -a 5 +``` + +###### Update tokens that have a label starting with a specific string to a specific expiration +This example will update tokens labelled "ExampleToken" (or any variation starting with this string) within the parent and child tenants to have the specified expiration date. + +```shell +python3 token_dispenser.py update -l ExampleToken -c $CHILD_TENANT_CID -e 2025-01-01T12:01:01Z +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for labels that match the specified string. + +```shell +python3 token_dispenser.py update -l ExampleToken -m -e 2025-01-01T12:01:01Z +``` + +###### Changing the label of a token +This example will change the label for the token "ExampleToken" to be "NewExampleToken" within the tenant it is found. + +```shell +python3 token_dispenser.py update -i $TOKEN_ID -m -n NewExampleToken +``` + +This example will change the label for any token matching "ExampleToken" to be "NewExampleToken" within the tenant it is found. If multiple tokens are updated within a tenant, a number will be appended to the end of each. + +```shell +python3 token_dispenser.py update -l ExampleToken -m -n NewExampleToken +``` + +> [!NOTE] +> To skip the confirmation dialog presented when performing multi-tenant operations, provide the `--force` argument. This argument has no impact on operations where a confirmation dialog is not normally presented. + +##### Command line help (update) +Command-line help for this command is available when the command is called along with the `-h` argument. + +```shell +usage: token_dispenser.py update [-h] (-i TOKEN_ID | -l TOKEN_LABEL) (-a ADD_DAYS | -e EXPIRATION | -n NEW_TOKEN_LABEL) [--force] [-d] [-f FILTER] [-o ORDER_BY] [-r] [-t TABLE_FORMAT] [-v] [--output-file OUTPUT_FILE] + [--output-format {csv,json}] [-k CLIENT_ID] [-s CLIENT_SECRET] [-c CHILD] [-m] [--skip-parent] [--show-tenant] + + _ _ _ _ +| | | | | | | | +| | | |_ __ __| | __ _| |_ ___ +| | | | '_ \ / _` |/ _` | __/ _ \ +| |__| | |_) | (_| | (_| | || __/ + \____/| .__/ \__,_|\__,_|\__\___| + | | + |_| + +optional arguments: + -h, --help show this help message and exit + --force Perform the operation without asking for confirmation. + -d, --debug Enable debug. + -f FILTER, --filter FILTER + Filter results by searching token labels (stemmed search). + -o ORDER_BY, --order-by ORDER_BY + Sort key to use for tabular displays. + -r, --reverse Reverses the sort order. + -t TABLE_FORMAT, --table-format TABLE_FORMAT + Format to use for tabular output. + -v, --show-version Show FalconPy version in output. + --output-file OUTPUT_FILE + Output token list results to a CSV or JSON file. + --output-format {csv,json} + Set output file format. + +required arguments: + -i TOKEN_ID, --token-id TOKEN_ID + ID of the token to update. + -l TOKEN_LABEL, --token-label TOKEN_LABEL + Label of the token to update (starts with match). + -a ADD_DAYS, --add-days ADD_DAYS + Add specified number of days to token expiration. + -e EXPIRATION, --expiration EXPIRATION + Token expiration (YYYY-mm-ddTHH:MM:SSZ). + -n NEW_TOKEN_LABEL, --new-label NEW_TOKEN_LABEL + New label for the token. + +authentication arguments (not required if using environment authentication): + -k CLIENT_ID, --client_id CLIENT_ID + Falcon API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + Falcon API client secret + +mssp arguments: + -c CHILD, --child CHILD + CID of the child tenant to target. + -m, --mssp Flight Control (MSSP) mode. + --skip-parent Do not take action within the parent tenant. + --show-tenant Display tenant CID values. +``` + +#### Delete tokens +Delete tokens within your tenant, or across parent and child tenants simultaneously. Supports the restoration of multiple tokens. + +##### Delete command arguments +There are two delete command-specific required arguments (`token-id` and `token-label`). These arguments are mutually exclusive. There is one optional argument `force`. +All [universal arguments](#universal-arguments) are supported and can be mixed with create command arguments in any order or combination. + +| Argument | Long Argument | Description | Category | +| :-- | :-- | :-- | :-- | +| | `--force` | Perform the operation without asking for confirmation. | General | +| `-i` TOKEN_ID | `--token-id` TOKEN_ID | ID of the token to delete. | Delete | +| `-l` TOKEN_LABEL | `--token-label` TOKEN_LABEL | Label of the token to delete (starts with match). | Delete | + +##### Examples +The following examples demonstrate different delete command variations. + +###### Delete tokens in a standard tenant +This example will delete any token with a label starting with "ExampleToken". + +```shell +python3 token_dispenser.py delete -l ExampleToken +``` + +You can also delete specific tokens by ID. + +```shell +python3 token_dispenser.py delete -i $TOKEN_ID +``` + +##### Flight Control examples +> [!IMPORTANT] +> You must provide the MSSP mode (`-m`) argument in order to access child tenants. If you wish processing to only occur within child tenants, you must provide the `--skip-parent` argument. + +###### Delete a single token in a child tenant +This example will delete a single token within a child tenant. + +```shell +python3 token_dispenser.py delete -i $TOKEN_ID -c $CHILD_TENANT_CID +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for a token that matches the ID. + +```shell +python3 token_dispenser.py delete -i $TOKEN_ID -m +``` + +###### Delete tokens in a child tenant that have a label starting with a specific string +This example will delete tokens labelled "ExampleToken" (or any variation starting with this string) within child tenants. + +```shell +python3 token_dispenser.py delete -l ExampleToken -c $CHILD_TENANT_CID +``` + +You can also accomplish this leveraging MSSP mode. All child tenants will be searched for labels that match the specified string. + +```shell +python3 token_dispenser.py delete -l ExampleToken -m +``` + +> [!NOTE] +> To skip the confirmation dialog presented when performing multi-tenant operations, provide the `--force` argument. This argument has no impact on operations where a confirmation dialog is not normally presented. + +##### Command line help (delete) +Command-line help for this command is available when the command is called along with the `-h` argument. + +```shell +usage: token_dispenser.py delete [-h] (-i TOKEN_ID | -l TOKEN_LABEL) [--force] [-d] [-f FILTER] [-o ORDER_BY] [-r] [-t TABLE_FORMAT] [-v] [--output-file OUTPUT_FILE] [--output-format {csv,json}] [-k CLIENT_ID] + [-s CLIENT_SECRET] [-c CHILD] [-m] [--skip-parent] [--show-tenant] + + _____ _ _ +| __ \ | | | | +| | | | ___| | ___| |_ ___ +| | | |/ _ \ |/ _ \ __/ _ \ +| |__| | __/ | __/ || __/ +|_____/ \___|_|\___|\__\___| + + + +optional arguments: + -h, --help show this help message and exit + --force Perform the operation without asking for confirmation. + -d, --debug Enable debug. + -f FILTER, --filter FILTER + Filter results by searching token labels (stemmed search). + -o ORDER_BY, --order-by ORDER_BY + Sort key to use for tabular displays. + -r, --reverse Reverses the sort order. + -t TABLE_FORMAT, --table-format TABLE_FORMAT + Format to use for tabular output. + -v, --show-version Show FalconPy version in output. + --output-file OUTPUT_FILE + Output token list results to a CSV or JSON file. + --output-format {csv,json} + Set output file format. + +required arguments (mutually exclusive): + -i TOKEN_ID, --token-id TOKEN_ID + ID of the token to remove. + -l TOKEN_LABEL, --token-label TOKEN_LABEL + Label of the token to remove (starts with match). + +authentication arguments (not required if using environment authentication): + -k CLIENT_ID, --client_id CLIENT_ID + Falcon API client ID + -s CLIENT_SECRET, --client_secret CLIENT_SECRET + Falcon API client secret + +mssp arguments: + -c CHILD, --child CHILD + CID of the child tenant to target. + -m, --mssp Flight Control (MSSP) mode. + --skip-parent Do not take action within the parent tenant. + --show-tenant Display tenant CID values. +``` + +### Example source code +The source code for this example can be found [here](token_dispenser.py). \ No newline at end of file diff --git a/samples/installation_tokens/token_dispenser.py b/samples/installation_tokens/token_dispenser.py new file mode 100755 index 000000000..836e213ff --- /dev/null +++ b/samples/installation_tokens/token_dispenser.py @@ -0,0 +1,999 @@ +r"""Installation Token management utility. + + _______ __ _______ __ __ __ +| _ .----.-----.--.--.--.--| | _ | |_.----|__| |--.-----. +|. 1___| _| _ | | | | _ | 1___| _| _| | <| -__| +|. |___|__| |_____|________|_____|____ |____|__| |__|__|__|_____| +|: 1 | |: 1 | +|::.. . | |::.. . | FalconPy v1.3.4 +`-------' `-------' + +_______ _____ _ _ _______ __ _ + | | | |____/ |______ | \ | + | |_____| | \_ |______ | \_| + +______ _____ _______ _____ _______ __ _ _______ _______ ______ +| \ | |______ |_____] |______ | \ | |______ |______ |_____/ +|_____/ __|__ ______| | |______ | \_| ______| |______ | \_ + + .-------. with ________) + |Jackpot| (, / /) , /) + ____________|_______|____________ /___, // _ (/ _/_ + | __ __ ___ _____ __ | ) / (/__(_(_/_/ )_(__ + | / _\ / / /___\/__ \ / _\ | (_/ .-/ + | \ \ / / // // / /\ \\ \ 25| (_/ ) ___ + | _\ \/ /___/ \_// / / \/_\ \ []| __ (__/_____) /) + | \__/\____/\___/ \/ \__/ []| (__) / _____ _/_ __ ___// + |===_______===_______===_______===| || / (_) / (_(__/ (_(_)(/_ + ||*| _____ |*| ,_ |*| ___ |*|| || (______) + ||*|| ||*| | \ _ |*| |_ | |*|| || + ||*||*BAR*||*| \_(_)|*| / / |*|| || + ||*||_____||*| (_) |*| /_/ |*|| || + ||*|_______|*|_______|*|_______|*||_// Creation date: 11.15.2023 + | \=___________________________=/ |_/ jshcodes@CrowdStrike + _| \_______________________/ |_ WE STOP BREACHES +(_____________________________________) +""" +# _____ _______ _____ _____ ______ _______ _______ +# | | | | |_____] | | |_____/ | |______ +# __|__ | | | | |_____| | \_ | ______| +# +import sys +from argparse import ArgumentParser, Namespace, RawTextHelpFormatter, _SubParsersAction +from copy import deepcopy +from csv import writer +from datetime import datetime, timedelta +from json import dump +from logging import basicConfig, DEBUG +from os import getenv +from random import randrange +from time import sleep +from typing import Tuple, Callable, List, Dict +try: + from pyfiglet import figlet_format +except ImportError as no_figlet: + raise SystemExit("The pyfiglet library is required to use this program.") from no_figlet +try: + from tabulate import tabulate +except ImportError as no_tabulate: + raise SystemExit("The tabulate library is required to use this program.") from no_tabulate +try: + from falconpy import ( + APIError, + InstallationTokens, + SensorDownload, + FlightControl, + Result, + version + ) +except ImportError as no_falconpy: + raise SystemExit("The CrowdStrike FalconPy library (version 1.3.4 or greater) is required " + "to use this program." + ) from no_falconpy + + +# _____ _____ _______ _____ _____ __ _ _______ +# | | |_____] | | | | | \ | |_____| | +# |_____| | | __|__ |_____| | \_| | | |_____ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def add_opt_arguments(sbp: ArgumentParser) -> ArgumentParser: + """Add shared optional arguments to the provided command line argument subparser.""" + sbp.add_argument("-d", "--debug", help="Enable debug.", default=False, action="store_true") + sbp.add_argument("-f", "--filter", + help="Filter results by searching token labels (stemmed search)." + ) + sbp.add_argument("-o", "--order-by", + help="Sort key to use for tabular displays.", + dest="order_by", + default="label" + ) + sbp.add_argument("-r", "--reverse", + help="Reverses the sort order.", + default=False, + action="store_true" + ) + sbp.add_argument("-t", "--table-format", + dest="table_format", + help="Format to use for tabular output.", + default="simple" + ) + sbp.add_argument("-v", "--show-version", + dest="show_version", + help="Show FalconPy version in output.", + default=False, + action="store_true" + ) + sbp.add_argument("--output-file", + dest="output_file", + help="Output token list results to a CSV or JSON file.", + default=None + ) + sbp.add_argument("--output-format", + dest="output_format", + help="Set output file format.", + default="csv", + choices=["csv", "json"] + ) + auth = sbp.add_argument_group("authentication arguments " + "(not required if using environment authentication)") + auth.add_argument("-k", "--client_id", + help="Falcon API client ID", + default=getenv("FALCON_CLIENT_ID") + ) + auth.add_argument("-s", "--client_secret", + help="Falcon API client secret", + default=getenv("FALCON_CLIENT_SECRET") + ) + mssp = sbp.add_argument_group("mssp arguments") + mssp.add_argument("-c", "--child", + dest="child", + help="CID of the child tenant to target.", + default=None + ) + mssp.add_argument("-m", "--mssp", + dest="mssp", + help="Flight Control (MSSP) mode.", + default=False, + action="store_true" + ) + mssp.add_argument("--skip-parent", + dest="skip_parent", + help="Do not take action within the parent tenant.", + action="store_true", + default=False + ) + mssp.add_argument("--show-tenant", + dest="show_tenant", + help="Display tenant CID values.", + action="store_true", + default=False + ) + return sbp + + +def add_force_argument(sbp: ArgumentParser) -> ArgumentParser: + """Add shared optional arguments to the provided command line argument subparser.""" + sbp.add_argument("--force", + help="Perform the operation without asking for confirmation.", + action="store_true", + default=False + ) + return sbp + + +def extra_args(subp: ArgumentParser) -> ArgumentParser: + """Add in optional arguments along with the force argument.""" + subp = add_force_argument(subp) + subp = add_opt_arguments(subp) + return subp + + +# | _____ _______ _______ +# | | |______ | +# |_____ __|__ ______| | +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def handle_list_arguments(sub: _SubParsersAction, head: str) -> ArgumentParser: + """Handle list command arguments.""" + do_list: ArgumentParser = sub.add_parser("list", + help="List all tokens [default]", + aliases=["l"], + description=figlet_format("List", font=head), + formatter_class=RawTextHelpFormatter + ) + do_list = add_opt_arguments(do_list) + return do_list + + +def show_tenant_list(arg_list: Namespace, cids_to_show: list): + """Show CIDs for all tenants searched.""" + if arg_list.mssp and arg_list.show_tenant: + parent = False + for kid in cids_to_show: + if not parent: + print(f"Tenant: {kid}") + parent = True + else: + print(f"Child tenant: {kid}") + + +# | _____ _______ _______ +# | | |______ | +# |_____ __|__ ______| | +# +def show_all_tokens(sdk: InstallationTokens, cmdline: str, filter_str: str = None): + """Display every token in the tenant (or across all tenants).""" + hold_creds = sdk.auth_object.creds + this_cid, cid_list = get_cid_ids(sdk, cmdline) + show_tenant_list(cmdline, cid_list) + token_details = [] + ptokens = [] + for cid in cid_list: + if cid != this_cid: + hold_creds["member_cid"] = cid + api = sdk + if cmdline.mssp and (len(cid_list) > 1 and ptokens): + api = InstallationTokens(creds=hold_creds, pythonic=True, debug=cmdline.debug) + token_details = get_all_tokens(api, cmdline, filter_str, cid, token_details) + if cid == this_cid and not ptokens: + ptokens = deepcopy(token_details) + display_tokens(token_details, cmdline, ptokens) + + +# _______ ______ _______ _______ _______ _______ +# | |_____/ |______ |_____| | |______ +# |_____ | \_ |______ | | | |______ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def handle_create_arguments(sub: _SubParsersAction, head: str) -> ArgumentParser: + """Handle create command arguments.""" + do_create: ArgumentParser = sub.add_parser("create", + help="Create tokens", + aliases=["c"], + description=figlet_format("Create", font=head), + formatter_class=RawTextHelpFormatter + ) + create_req = do_create.add_argument_group("required arguments") + create_req.add_argument("-l", "--token-label", + dest="token_label", + help="Label for the token.", + required=True + ) + create_req.add_argument("-e", "--expiration", + help="Token expiration (number of days or YYYY-mm-ddTHH:MM:SSZ).", + required=True + ) + do_create.add_argument("-n", "--count", help="Number of tokens to create.", type=int, default=1) + do_create = extra_args(do_create) + return do_create + + +# _______ ______ _______ _______ _______ _______ +# | |_____/ |______ |_____| | |______ +# |_____ | \_ |______ | | | |______ +# +def create_token(sdk: InstallationTokens, cmdline: Namespace): + """Create a token with the specified expiration and label.""" + hold_creds = sdk.auth_object.creds + this_cid, cids = get_cid_ids(sdk, cmdline) + token_expiration = cmdline.expiration + cids_to_process = [ + c for c in cids if (cmdline.skip_parent and not c == this_cid) or not cmdline.skip_parent + ] + for cid in cids_to_process: + if cid != this_cid: + hold_creds["member_cid"] = cid + api = sdk + if cmdline.mssp and len(cids) > 1: + api = InstallationTokens(creds=hold_creds, pythonic=True, debug=cmdline.debug) + try: + if int(cmdline.expiration) > 0: + token_expiration = ( + datetime.now() + timedelta(days=int(cmdline.expiration)) + ).strftime("%Y-%m-%dT%H:%M:%SZ") + else: + raise SystemExit("Token expiration days must be an integer greater than zero.") + except ValueError: + pass + + for num in range(1, cmdline.count+1): + label = f"{cmdline.token_label}{num if cmdline.count > 1 else ''}" + create_result = sdk_operation(api.tokens_create, + LONG_WAIT, + cmdline.debug, + label=label, + expires_timestamp=token_expiration + ) + if create_result.errors: + for error in create_result.errors: + print(f"NONFATAL {error['code']} ERROR: {error['message']}") + + +# ______ _______ _ _ _____ _ _ _______ +# |_____/ |______ \ / | | |____/ |______ +# | \_ |______ \/ |_____| | \_ |______ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def handle_revoke_arguments(sub: _SubParsersAction, head: str) -> ArgumentParser: + """Handle revoke command arguments.""" + do_revoke: ArgumentParser = sub.add_parser("revoke", + help="Revoke tokens", + aliases=["x"], + description=figlet_format("Revoke", font=head), + formatter_class=RawTextHelpFormatter + ) + revoke_req = do_revoke.add_argument_group("required arguments (mutually exclusive)") + revoke_grp = revoke_req.add_mutually_exclusive_group(required=True) + revoke_grp.add_argument("-i", "--token-id", dest="token_id", help="ID of the token to revoke.") + revoke_grp.add_argument("-l", "--token-label", + dest="token_label", + help="Label of the token to revoke (starts with match)." + ) + do_revoke = extra_args(do_revoke) + return do_revoke + + +# ______ _______ _______ _______ _____ ______ _______ +# |_____/ |______ |______ | | | |_____/ |______ +# | \_ |______ ______| | |_____| | \_ |______ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def handle_restore_arguments(sub: _SubParsersAction, head: str) -> ArgumentParser: + """Handle restore command arguments.""" + do_restore: ArgumentParser = sub.add_parser("restore", + help="Restore tokens", + aliases=["r"], + description=figlet_format("Restore", font=head), + formatter_class=RawTextHelpFormatter + ) + restore_req = do_restore.add_argument_group("required arguments (mutually exclusive)") + restore_grp = restore_req.add_mutually_exclusive_group(required=True) + restore_grp.add_argument("-i", "--token-id", + dest="token_id", + help="ID of the token to restore." + ) + restore_grp.add_argument("-l", "--token-label", + dest="token_label", + help="Label of the token to restore (starts with match)." + ) + do_restore = extra_args(do_restore) + return do_restore + + +# ______ _______ _ _ _____ _ _ _______ _______ __ _ ______ +# |_____/ |______ \ / | | |____/ |______ |_____| | \ | | \ +# | \_ |______ \/ |_____| | \_ |______ | | | \_| |_____/ +# ______ _______ _______ _______ _____ ______ _______ +# |_____/ |______ |______ | | | |_____/ |______ +# | \_ |______ ______| | |_____| | \_ |______ +# +def token_revocation(sdk: InstallationTokens, cmdline: Namespace, revoking: bool = False): + """Revoke a token by ID or name.""" + hold_creds = sdk.auth_object.creds + this_cid, cids = get_cid_ids(sdk, cmdline) + cids_to_process = [ + c for c in cids if (cmdline.skip_parent and not c == this_cid) or not cmdline.skip_parent + ] + for cid in cids_to_process: + if cid != this_cid: + hold_creds["member_cid"] = cid + api = sdk + if cmdline.mssp and len(cids) > 1: + api = InstallationTokens(creds=hold_creds, pythonic=True, debug=cmdline.debug) + + if cmdline.token_id: + revoke_result = sdk_operation(api.tokens_update, + SHORT_WAIT, + cmdline.debug, + ids=cmdline.token_id, + revoked=revoking + ) + if revoke_result.errors: + for error in revoke_result.errors: + print(f"NONFATAL {error['code']} ERROR: {error['message']} ({error['id']})") + if cmdline.token_label: + token_lookup = sdk_operation(api.tokens_query, + LONG_WAIT, + cmdline.debug, + filter=f"label:*'{cmdline.token_label}*'" + ) + if token_lookup.status_code == 200 and len(token_lookup.data): + for returned_token_id in token_lookup.data: + sdk_operation(api.tokens_update, + LONG_WAIT, + cmdline.debug, + ids=returned_token_id, + revoked=revoking + ) + else: + print(f"NONFATAL 404 ERROR: Not Found ({cmdline.token_label})") + + +# _ _ _____ ______ _______ _______ _______ +# | | |_____] | \ |_____| | |______ +# |_____| | |_____/ | | | |______ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def handle_update_arguments(sub: _SubParsersAction, head: str) -> ArgumentParser: + """Handle update command arguments.""" + do_update: ArgumentParser = sub.add_parser("update", + help="Update tokens", + aliases=["u"], + description=figlet_format("Update", font=head), + formatter_class=RawTextHelpFormatter + ) + update_req = do_update.add_argument_group("required arguments") + update_grp1 = update_req.add_mutually_exclusive_group(required=True) + update_grp1.add_argument("-i", "--token-id", + dest="token_id", + help="ID of the token to update." + ) + update_grp1.add_argument("-l", "--token-label", + dest="token_label", + help="Label of the token to update (starts with match)." + ) + update_grp2 = update_req.add_mutually_exclusive_group(required=True) + update_grp2.add_argument("-a", "--add-days", + help="Add specified number of days to token expiration." + ) + update_grp2.add_argument("-e", "--expiration", help="Token expiration (YYYY-mm-ddTHH:MM:SSZ).") + update_grp2.add_argument("-n", "--new-label", + dest="new_token_label", + help="New label for the token." + ) + do_update = extra_args(do_update) + return do_update + + +# _ _ _____ ______ _______ _______ _______ ______ __ __ _____ ______ +# | | |_____] | \ |_____| | |______ |_____] \_/ | | \ +# |_____| | |_____/ | | | |______ |_____] | __|__ |_____/ +# +def update_token_by_id(sdk: InstallationTokens, cmdline: Namespace): + """Update a token by ID.""" + hold_creds = sdk.auth_object.creds + this_cid, cids = get_cid_ids(sdk, cmdline) + cids_to_process = [ + c for c in cids if (cmdline.skip_parent and not c == this_cid) or not cmdline.skip_parent + ] + for cid in cids_to_process: + if cid != this_cid: + hold_creds["member_cid"] = cid + api = sdk + if cmdline.mssp and len(cids) > 1: + api = InstallationTokens(creds=hold_creds, pythonic=True, debug=cmdline.debug) + updates = {} + if cmdline.new_token_label: + updates["label"] = cmdline.new_token_label + if cmdline.expiration: + updates["expires_timestamp"] = cmdline.expiration + if cmdline.add_days: + exp = sdk_operation(api.tokens_read, + LONG_WAIT, + cmdline.debug, + ids=cmdline.token_id + ) + if exp.status_code == 200 and len(exp.data): + new_exp = datetime.strptime(exp.data[0]["expires_timestamp"], "%Y-%m-%dT%H:%M:%SZ") + updates["expires_timestamp"] = ( + new_exp + timedelta(days=int(cmdline.add_days)) + ).strftime("%Y-%m-%dT%H:%M:%SZ") + if updates: + updates["ids"] = cmdline.token_id + update_result = sdk_operation(api.tokens_update, SHORT_WAIT, cmdline.debug, **updates) + if update_result.errors: + for error in update_result.errors: + print(f"NONFATAL {error['code']} ERROR: {error['message']} ({error['id']})") + + +# _ _ _____ ______ _______ _______ _______ +# | | |_____] | \ |_____| | |______ +# |_____| | |_____/ | | | |______ +# ______ __ __ _______ ______ _______ +# |_____] \_/ | |_____| |_____] |______ | +# |_____] | |_____ | | |_____] |______ |_____ +# +def update_token_by_label(sdk: InstallationTokens, cmdline: Namespace): + """Update a token by label.""" + hold_creds = sdk.auth_object.creds + this_cid, cids = get_cid_ids(sdk, cmdline) + cids_to_process = [ + c for c in cids if (cmdline.skip_parent and not c == this_cid) or not cmdline.skip_parent + ] + for cid in cids_to_process: + if cid != this_cid: + hold_creds["member_cid"] = cid + api = sdk + if cmdline.mssp and len(cids) > 1: + api = InstallationTokens(creds=hold_creds, pythonic=True, debug=cmdline.debug) + updates = {} + if cmdline.new_token_label: + updates["label"] = cmdline.new_token_label + if cmdline.expiration: + updates["expires_timestamp"] = cmdline.expiration + token_lookup = sdk_operation(api.tokens_query, + LONG_WAIT, + cmdline.debug, + filter=f"label:*'{cmdline.token_label}*'" + ) + if token_lookup.status_code == 200 and len(token_lookup.data): + loop = 1 + for returned_token_id in token_lookup.data: + if cmdline.add_days: + exp = sdk_operation(api.tokens_read, + LONG_WAIT, + cmdline.debug, + ids=returned_token_id + ) + if exp.status_code == 200 and len(exp.data): + new_exp = datetime.strptime(exp.data[0]["expires_timestamp"], + "%Y-%m-%dT%H:%M:%SZ" + ) + updates["expires_timestamp"] = ( + new_exp + timedelta(days=int(cmdline.add_days)) + ).strftime("%Y-%m-%dT%H:%M:%SZ") + if cmdline.new_token_label and len(token_lookup.data) > 1: + updates["label"] = f"{cmdline.new_token_label}{loop}" + if updates: + updates["ids"] = returned_token_id + sdk_operation(api.tokens_update, LONG_WAIT, cmdline.debug, **updates) + loop += 1 + else: + print(f"NONFATAL 404 ERROR: Not Found ({cmdline.token_label})") + + +# ______ _______ _______ _______ _______ +# | \ |______ | |______ | |______ +# |_____/ |______ |_____ |______ | |______ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def handle_delete_arguments(sub: _SubParsersAction, head: str) -> ArgumentParser: + """Handle delete command arguments.""" + do_delete: ArgumentParser = sub.add_parser("delete", + help="Delete tokens", + aliases=["d"], + description=figlet_format("Delete", font=head), + formatter_class=RawTextHelpFormatter + ) + delete_req = do_delete.add_argument_group("required arguments (mutually exclusive)") + delete_grp = delete_req.add_mutually_exclusive_group(required=True) + delete_grp.add_argument("-i", "--token-id", dest="token_id", help="ID of the token to remove.") + delete_grp.add_argument("-l", "--token-label", + dest="token_label", + help="Label of the token to remove (starts with match)." + ) + do_delete = extra_args(do_delete) + return do_delete + + +# ______ _______ _______ _______ _______ +# | \ |______ | |______ | |______ +# |_____/ |______ |_____ |______ | |______ +# +def delete_token(sdk: InstallationTokens, cmdline: Namespace): + """Delete a token by ID or name.""" + hold_creds = sdk.auth_object.creds + this_cid, cids = get_cid_ids(sdk, cmdline) + cids_to_process = [ + c for c in cids if (cmdline.skip_parent and not c == this_cid) or not cmdline.skip_parent + ] + for cid in cids_to_process: + if cid != this_cid: + hold_creds["member_cid"] = cid + api = sdk + if cmdline.mssp and len(cids) > 1: + api = InstallationTokens(creds=hold_creds, pythonic=True, debug=cmdline.debug) + if cmdline.token_id: + delete_result = sdk_operation(api.tokens_delete, + SHORT_WAIT, + cmdline.debug, + ids=cmdline.token_id + ) + if delete_result.errors: + for error in delete_result.errors: + print(f"NONFATAL {error['code']} ERROR: {error['message']} ({error['id']})") + if cmdline.token_label: + token_lookup = sdk_operation(api.tokens_query, + LONG_WAIT, + cmdline.debug, + filter=f"label:*'{cmdline.token_label}*'" + ) + if token_lookup.status_code == 200 and len(token_lookup.data): + for returned_token_id in token_lookup.data: + sdk_operation(api.tokens_delete, + LONG_WAIT, + cmdline.debug, + ids=returned_token_id + ) + else: + print(f"NONFATAL 404 ERROR: Not Found ({cmdline.token_label})") + + +# _____ _______ ______ _______ _______ +# |_____] |_____| |_____/ |______ |______ +# | | | | \_ ______| |______ +# _______ _____ _______ _______ _______ __ _ ______ _____ __ _ _______ +# | | | | | | | | | |_____| | \ | | \ | | | \ | |______ +# |_____ |_____| | | | | | | | | | \_| |_____/ |_____ __|__ | \_| |______ +# _______ ______ ______ _ _ _______ _______ __ _ _______ _______ +# |_____| |_____/ | ____ | | | | | |______ | \ | | |______ +# | | | \_ |_____| |_____| | | | |______ | \_| | ______| +# +def consume_arguments() -> Tuple[Namespace, ArgumentParser]: + """Retrieve any provided command line arguments.""" + subcommands = [ + "create", "c", "list", "l", "delete", "d", "revoke", "x", "restore", "r", "update", "u" + ] + parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) + header_font = "big" + subparsers = parser.add_subparsers(help="Command description", + dest="subcommand", + required=False, + metavar="Token command" + ) + handle_list_arguments(subparsers, header_font) # List + handle_create_arguments(subparsers, header_font) # Create + handle_revoke_arguments(subparsers, header_font) # Revoke + handle_restore_arguments(subparsers, header_font) # Restore + handle_update_arguments(subparsers, header_font) # Update + handle_delete_arguments(subparsers, header_font) # Delete + # Force "list" as the default subcommand without breaking the help processor + if len(sys.argv) == 1: + sys.argv.append("list") + if sys.argv[1].lower() not in subcommands and "-h" not in sys.argv: + sys.argv.insert(1, "list") + else: + if sys.argv[1].lower() not in subcommands: + sys.argv = [sys.argv[0], "-h"] + return parser.parse_args(), parser + + +# ______ _______ _______ _______ _____ _______ _____ _______ +# |_____/ |_____| | |______ | | | | | | | +# | \_ | | | |______ |_____ __|__ | | | __|__ | +# _ _ _______ __ _ ______ _______ ______ +# |_____| |_____| | \ | | \ | |______ |_____/ +# | | | | | \_| |_____/ |_____ |______ | \_ +# +def rate_delay(wait_time: int): + """Wait for the specified amount of time while informing the user.""" + for wait in range(wait_time, 0, -1): + print(f" Rate limit exceeded, sleeping for {wait} seconds. ", end="\r") + sleep(1) + print(" " * 80, end="\r") + + +def sdk_operation(operation: Callable, delay_time: int, debugging: bool, **kwargs) -> Result: + """Perform an operation against the CrowdStrike API, gracefully handling rate limit errors.""" + rate_limited = True + while rate_limited: + try: + operation_result: Result = operation(**kwargs) + rate_limited = False + except APIError as rate_limit_met: + if rate_limit_met.code == 429: + rate_delay(delay_time) + elif debugging: + raise rate_limit_met + else: + failure = FAIL if randrange(1, 3000, 1) % 2 == 0 else FAIL2 + raise SystemExit( + failure.format(rate_limit_met.code, rate_limit_met.message) + ) from rate_limit_met + return operation_result + + +# _ _ _______ _____ _______ ______ _______ +# |_____| |______ | |_____] |______ |_____/ |______ +# | | |______ |_____ | |______ | \_ ______| +# +def reorganize_token_dictionary(tenant: str, record: dict) -> Dict[str, str]: + """Reorganize a token record dictionary to include the CID column.""" + cid_key = {"cid": tenant} + cid_list = list(cid_key.items()) + token_keys = list(record.keys()) + token_values = list(record.values()) + token_keys.insert(token_keys.index("id")+1, cid_list[0][0]) + token_values.insert(token_keys.index("id")+1, cid_list[0][1]) + return { + token_keys[i]: token_values[i] + for i in range(0, len(token_keys)) + } + + +def get_all_tokens(api_sdk: InstallationTokens, + cmd_args: Namespace, + filt: str, + cur_cid: str, + returning: List[Dict[str, str]] + ) -> List[Dict[str, str]]: + """Retrieve all tokens across all tenants.""" + offset = None + running = True + while running: + token_lookup = sdk_operation(api_sdk.tokens_query, + LONG_WAIT, + cmd_args.debug, + limit=1000, + offset=offset, + filter=f"label:*'*{filt}*'" if filt else None + ) + if not token_lookup.data: + running = False + batches = [token_lookup.data[i:i+100] for i in range(0, len(token_lookup.data), 100)] + for batch in batches: + token_detail = sdk_operation(api_sdk.tokens_read, LONG_WAIT, cmd_args.debug, ids=batch) + found = token_detail.data + if cmd_args.mssp: + found = [] + for token_det in token_detail.data: + new_dict = reorganize_token_dictionary(cur_cid, token_det) + found.append(new_dict) + returning.extend(found) + + offset = len(returning) + if token_lookup.total <= len(returning): + running = False + return returning + + +def confirm(msg: str): + """Request confirmation from the user and return a boolean of the response.""" + return input(msg) in ["y", "yes", "Y", "YES"] + + +def get_this_cid(auth: InstallationTokens, debug_mode: bool = False): + """Retrieve the CID for the current tenant.""" + my_id = "Not available" + running = True + while running: + try: + my_id = sdk_operation( + SensorDownload(auth_object=auth).get_sensor_installer_ccid, + SHORT_WAIT, + debug_mode + ).data[0][:-3].lower() + running = False + except APIError as no_sensor_dl: + if no_sensor_dl.code != 429: + print("NONFATAL 403 ERROR: This API client is not scoped for Sensor Downloads.") + running = False + else: + rate_delay(SHORT_WAIT) + return my_id + + +def check_mssp_scope(auth: InstallationTokens): + """Confirm if this API client has access to Flight Control.""" + valid = False + running = True + while running: + try: + valid = bool(FlightControl(auth_object=auth).query_children(limit=1).status_code == 200) + running = False + except APIError as rate_limit_met: + if rate_limit_met.code != 429: + running = False + raise rate_limit_met + rate_delay(SHORT_WAIT) + return valid + + +def get_cid_ids(interface: InstallationTokens, arguments: Namespace) -> Tuple[str, List[str]]: + """Return all CIDs associated with the API client (if MSSP mode is enabled).""" + this_cid = "NonMSSP" + cid_list = [this_cid] + if arguments.mssp: + this_cid = get_this_cid(interface, arguments.debug) + cid_list = [this_cid] + try: + mssp = FlightControl(auth_object=interface) + cid_list.extend(mssp.query_children().data) + except APIError: + pass + return this_cid, cid_list + + +def write_output_results(cmdline_args: Namespace, tresults: list): + """Write the displayed token results to the requested file.""" + if cmdline_args.output_file: + if cmdline_args.output_format.lower() == "csv": + with open(cmdline_args.output_file, "w", newline="", encoding="utf-8") as csv_file: + csv_writer = writer(csv_file) + if tresults: + csv_writer.writerow(tresults[0].keys()) + for token_row in tresults: + csv_writer.writerow(token_row.values()) + print(f"CSV results output to {cmdline_args.output_file}.") + elif cmdline_args.output_format.lower() == "json": + with open(cmdline_args.output_file, "w", encoding="utf-8") as json_file: + dump(tresults, json_file, indent=4) + print(f"JSON results output to {cmdline_args.output_file}.") + + +def display_tokens(token_results: list, cmd_args: Namespace, parent_tokens: list): + """Display the retrieved tokens in a tabular format.""" + new_token_results = token_results + if cmd_args.mssp: + new_token_results = [] + for tok in token_results: + matched = False + for ptok in parent_tokens: + if ptok["cid"] == tok["cid"]: + if tok["id"] == ptok["id"] and tok["value"] == ptok["value"]: + matched = True + elif ptok["cid"] != tok["cid"]: + matched = True + if matched: + new_token_results.append(tok) + + token_results = sorted(new_token_results, + key=lambda x: x[cmd_args.order_by], + reverse=cmd_args.reverse + ) + vers = "" + if cmd_args.show_version: + vers = f" (FalconPy v{version(agent_string=False)})" + if token_results: + tabular_display = tabulate(tabular_data=[t.values() for t in token_results], + headers=token_results[0].keys(), + tablefmt=cmd_args.table_format + ) + print(tabular_display) + print(f"{len(token_results)} total tokens found{vers}") + write_output_results(cmd_args, token_results) + else: + print(NOT_FOUND.format(vers.replace("(", "").replace(")", ""))) + + +def cross_tenant_action(action: Callable, msg: str, **kwargs): + """Check and warn if this action impacts multiple CIDs.""" + proceed = True + if not kwargs.get("cmdline").force: + if kwargs.get("cmdline").mssp and check_mssp_scope(kwargs.get("sdk")): + parent_to = "the parent and " + if kwargs.get("cmdline").skip_parent: + parent_to = "" + proceed = confirm(WARNING.format(msg, parent_to)) + if proceed: + action(**kwargs) + else: + print("Operation cancelled.") + sys.exit(0) + + +# _______ _____ __ _ _______ _______ _______ __ _ _______ _______ +# | | | | \ | |______ | |_____| | \ | | |______ +# |_____ |_____| | \_| ______| | | | | \_| | ______| +# +LONG_WAIT = 10 +SHORT_WAIT = 5 +FAIL = r""" + , , + (\____/) FATAL {} {} + (_oo_) / + (O) + __||__ \) + []/______\[] / + / \______/ \/ + / /__\ +(\ /____\ +""" +FAIL2 = r""" + _ + [ ] FATAL {} {} + ( ) / + |>| + __/===\__ + //| o=o |\\ +<] | o=o | [> + \=====/ + / / | \ \ + <_________> +""" +NOT_FOUND = r""" + __ No tokens found! + _(\ |@@| / +(__/\__ \--/ __ + \___|----| | __ + \ CS /\ )_ / _\ + /\__/\ \__O (__ + (--/\--) \__/ + _)( )(_ + `---''---` {} +""" +WARNING = r""" + __,_, + [_|_/ ⚠️ Warning ⚠️ + // This action will {} multiple tokens + _// __ / across {}child tenants. +(_|) |@@| + \ \__ \--/ __ + \o__|----| | __ + \ CS /\ )_ / _\ + /\__/\ \__O (__ + (--/\--) \__/ + _)( )(_ + `---''---` + +Are you sure you wish to proceed? (y/n) => """ +# _______ _______ _____ __ _ ______ _____ _ _ _______ _____ __ _ _______ +# | | | |_____| | | \ | |_____/ | | | | | | | \ | |______ +# | | | | | __|__ | \_| | \_ |_____| |_____| | __|__ | \_| |______ +# +if __name__ == "__main__": + begin = datetime.now().timestamp() # Start the timer + if sys.version_info <= (3, 7): # Make sure we're running the minimum version of Python + raise SystemExit("This application only supports Python 3.7 or greater.") + if not version(compare="1.3.4"): # Check for 1.3.4 or greater + raise SystemExit("In order to use this sample application, the CrowdStrike FalconPy " + "library (version 1.3.4 or greater) must be installed." + ) + parsed, handler = consume_arguments() # Retrieve command line arguments and the parser + # There are no credentials in the environment or command line, show help and quit + if not parsed.client_id or not parsed.client_secret: + handler.print_help() + raise SystemExit( + "\nYou must provide API credentials via the environment variables\n" + "FALCON_CLIENT_ID and FALCON_CLIENT_SECRET or you must provide\n" + "these values using the '-k' and '-s' command line arguments." + ) + if parsed.debug: # Enable debug logging to the console if requested + basicConfig(level=DEBUG) + # Construct an instance of the InstallationTokens Service Class + tokens = InstallationTokens(client_id=parsed.client_id, + client_secret=parsed.client_secret, + debug=parsed.debug, + pythonic=True, + member_cid=parsed.child + ) + default_action_args = [tokens, parsed] # We display all tokens regardless of command executed + tcommand = parsed.subcommand.lower() # Selected token command + if tcommand in ["create", "c"]: # Create + cross_tenant_action(create_token, "create", sdk=tokens, cmdline=parsed) + elif tcommand in ["delete", "d"]: # Delete + if parsed.token_label: + cross_tenant_action(delete_token, "delete", sdk=tokens, cmdline=parsed) + else: + delete_token(tokens, parsed) + elif tcommand in ["revoke", "x", "restore", "r"]: # Revoke and Restore + if parsed.token_label: + cross_tenant_action(token_revocation, + "restore" if tcommand in ["restore", "r"] else "revoke", + sdk=tokens, + cmdline=parsed, + revoking=tcommand in ["revoke", "x"] + ) + else: + token_revocation(tokens, parsed, tcommand in ["revoke", "x"]) + elif tcommand in ["update", "u"]: # Update + if parsed.token_id: + update_token_by_id(tokens, parsed) + elif parsed.token_label: + cross_tenant_action(update_token_by_label, "update", sdk=tokens, cmdline=parsed) + if parsed.filter: # List / all commands + # Add any provided command line filters to the arguments for the default action + default_action_args.append(parsed.filter) + show_all_tokens(*default_action_args) # After all processing, display the list of tokens + + +# █ █ +# █ ██ +# ██ _ _ _ _______ _______ _______ _____ _____ ▓█ +# ▒▒███ | | | |______ |______ | | | |_____] ██▓▒▓ +# ▒░▒▓████ |__|__| |______ ______| | |_____| | █████▒▒▒▓ +# █▒▒▓████▒▓███ ▓██▓▓████▒░▒ +# ▒░▒████▒░░▒▒▓▓█▓▓ ████▒▒▒░░▓███▓▒░▒ +# ▓░▒▒███▓░░▒▒▒▓██▓▒█▓█▓▓ ▒▓█▓▓▒███▒▒▒▒░▒████▒░▒ +# ▒░▒▓███▓░░▒▒▒███▒░░░▓███▓█▓ █▓█▓███▒░░░▓██▓▒▒▒░▒████▒░▒ +# ▒░▒▓███▓░░▒▒▒███▒░░░▓███░░▒▓▓█▓ ▓██▒▒░▒███▒░░░▓██▓▒▒▒░▒████▒░▒ +# ▓░▒▒███▓░░▒▒▒▒██▓░░░░░░░░▒▒▒█████▓ ▓█████░▒░░░░░░░░░███▒▒▒▒░▒████▒░▒ +# ▒░▒████▒░░▒▒▒▓███▒░▒▒▒▒░░████▒▒▒▓██ █▓▓▒▒▓███▒░▒▒▒▒░░▓███▒▒▒▒░░▓███▓▒░▒ +# █▒▒▒████▒░▒▒▒▒▒████████████▓▒▒▒▒▒▒███ █▓▒░▒▒▒▒▒████████████▓▒▒▒▒░░▓████▒▒▒ +# ▒░▒▓████▒░░▒▒▒▒▒▓███████▒▒▒▒▒▒░░▓███ ████▒░▒▒▒▒▒▒▓███████▒▒▒▒▒▒░░▓████▒▒░▒ +# ▒░▒▓████▓▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒█▓███▓█ █▓████▓▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒█▓███▒▒▒▓ +# ▒░▒▒██████▒░░░▒▒▒▒▒▒▒▒▒░░▒▒█▓███▓▒▒▓█ █▒▒▒██████▒░░▒▒▒▒▒▒▒▒▒▒░░▒▒█▓███▓▒▒▒▒ +# ▓▒▒▒▒███████▒▒▒▒▒▒▒▒▒▒▓███████▒▒▒▒ ▒▒▒▒███████▒▒▒▒▒░▒▒▒▒▓███████▒▒░▒ +# ▒▒▒▒▒▓█████████▓█████████▒▒▒▒▒ ▓▒▒▒▒▓█████████▓█████████▒▒▒░▒ +# ▓▒▒▒▒▒▒████████████▓▒▒▒▒▒▓ █▒▒▒▒▒▒████████████▓▒▒▒▒░▒ +# ▓▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ █▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▓ +# ▓▒▓▓▓▓▒▓█ █▒░▓▓▓▒▓█ +# +# ______ ______ _______ _______ _______ _ _ _______ _______ +# |_____] |_____/ |______ |_____| | |_____| |______ |______ +# |_____] | \_ |______ | | |_____ | | |______ ______|