Skip to content

Commit

Permalink
Merge pull request #124 from CrowdStrike/feat/103/host-containment
Browse files Browse the repository at this point in the history
Host Containment and Uncontainment
  • Loading branch information
ChristopherHammond13 authored Apr 15, 2024
2 parents 7921185 + b62766c commit b3232cd
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 12 deletions.
66 changes: 54 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ The toolkit provides:
- Multiple profile support, including support for MSSP / Falcon Flight Control configurations.
- A shell allowing you to interface with many hosts via RTR at once, and get the output via CSV.
- Scriptability! You can program the shell by providing pre-written routines via a file on disk, and a full Python extensibility API is provided.
- Prevention policy import and export
- Response policy import and export
- Prevention policy import and export.
- Response policy import and export.
- Contain and uncontain systems in bulk, according to filters or a list of device IDs.
- More functionality is coming soon! Want more functionality? Open an [Issue](https://github.com/CrowdStrike/Falcon-Toolkit/issues/new)!

Since this is built on top of Caracara, you get a bunch of great functionality and flexibility free, including the ability to filter hosts using dynamically generated FQL queries, full debug logging where desired, Falcon Flight Control integration, and more! Plus, the tool is lightning quick as it leverages Caracara's parallelisation tricks to pull more information quickly.
Expand Down Expand Up @@ -182,17 +183,18 @@ Two types of configuration backends are provided out of the box: the default, wh

Your API keys should have the following scopes enabled in the Falcon dashboard:

| &darr; API Scopes // Commands &rarr; | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) |
|--------------------------------------|:-------------:|:-------:|:--------------------------:|:-------------------------:|
| &darr; API Scopes // Commands &rarr; | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) | `containment`<br>Host Containment |
|--------------------------------------|:-------------:|:-------:|:--------------------------:|:-------------------------:|:---------------------------------:|
| **Falcon Flight Control: Read** | X<br>*When using parent<br>CID API Keys* | X<br>*When using parent<br>CID API Keys* | X<br>*When using parent<br>CID API Keys* | X<br>*When using parent<br>CID API Keys* |
| **Hosts: Read** | X | X | | |
| **Prevention Policies: Read** | | | X<br>`describe` / `export` sub-commands | |
| **Prevention Policies: Write** | | | X<br>`import` sub-command | |
| **Real Time Response: Read** | | X | | |
| **Real Time Response: Write** | | X | | |
| **Real Time Response: Admin** | | X<br>*for admin commands* | | |
| **Response Policies: Read** | | | | X<br>`describe` / `export` sub-commands |
| **Response Policies: Write** | | | | X<br>`import` sub-command |
| **Hosts: Read** | X | X | | | X |
| **Hosts: Write** | | | | | X |
| **Prevention Policies: Read** | | | X<br>`describe` / `export` sub-commands | | |
| **Prevention Policies: Write** | | | X<br>`import` sub-command | | |
| **Real Time Response: Read** | | X | | | |
| **Real Time Response: Write** | | X | | | |
| **Real Time Response: Admin** | | X<br>*for admin commands* | | | |
| **Response Policies: Read** | | | | X<br>`describe` / `export` sub-commands | |
| **Response Policies: Write** | | | | X<br>`import` sub-command | |

### Showing Your Profiles

Expand Down Expand Up @@ -355,6 +357,46 @@ Some example usages of this functionality are as follows:

</details>

## Network Containment

You can `contain` and `uncontain` systems through Falcon Toolkit. Network containment restricts the network connectivity of matching systems to just the Falcon Platform.

The containment functionality accepts the same command line parameters as `host_search` and `shell` (`-d`, `-df`, or multiple instances of `-f` to apply Falcon filters).

### Examples

Network contain all systems that match the FQL hostname pattern `DEVTEST*`:

```shell
$ falcon -p MyCompany containment -f Hostname=DEVTEST\* contain
...
```

Lift the containment status on the same machines:

```shell
$ falcon -p MyCompany containment -f Hostname=DEVTEST\* uncontain
...
```

Network contain specific systems by AID:

```shell
$ falcon -p MyCompany containment -d aid1,aid2,aid3 contain
...
```

Network contain all systems in a CID:

```shell
$ falcon -p MyCompany containment contain
You did not specify any parameters. This command will manage the containment status of ALL devices in the Falcon tenant!
You must enter the string "I AM SURE!" to proceed.
Are you sure? I AM SURE!
...
```


## Policy Manipulation

You can `describe`, `import` and `export` two types of policies: Prevention and Response. The three verbs are applied to the `falcon policies` command to specify what you would like to do with policies, and a command line switch is used to specify the policy type to work with. Exported policies are written to disk as JSON with some Falcon Toolkit-specific data needed to import a policy back again.
Expand Down
4 changes: 4 additions & 0 deletions falcon_toolkit/containment/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Falcon Toolkit: Host Containment.
This sub-module contains the implementation of the falcon containment commands.
"""
205 changes: 205 additions & 0 deletions falcon_toolkit/containment/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
"""Falcon Toolkit: Containment.
This file contains the CLI options for the falcon containment commands.
"""
import logging
import sys

from typing import List

import click

from caracara import Client
from click_option_group import (
optgroup,
MutuallyExclusiveOptionGroup,
)

from falcon_toolkit.common.cli import (
get_instance,
parse_cli_filters,
)
from falcon_toolkit.containment.perform_containment import perform_containment_action


@click.group(
name='containment',
help="Manage the containment status of systems in a CID",
)
@click.pass_context
@optgroup.group(
"Specify devices",
cls=MutuallyExclusiveOptionGroup,
help="Choose no more than one method to choose systems to contain or uncontain"
)
@optgroup.option(
'-d',
'--device-id-list',
'device_id_list',
type=click.STRING,
help="Specify a list of Device IDs (AIDs), comma delimited"
)
@optgroup.option(
'-df',
'--device-id-file',
'device_id_file',
type=click.STRING,
help=(
"Specify a list of Device IDs (AIDs) in an external file, one per line; "
"this can help you get round command line length limits in your workstation's shell,"
),
)
@optgroup.option(
'-f',
'--filter',
'filter_kv_string',
type=click.STRING,
multiple=True,
help="Filter hosts based on standard Falcon filters",
)
def cli_containment(
ctx: click.Context,
device_id_list: str,
device_id_file: str,
filter_kv_string: List[str],
):
"""Manage the containment status of hosts in Falcon."""
instance = get_instance(ctx)
client: Client = instance.auth_backend.authenticate()
ctx.obj['client'] = client

device_ids = None
params = True

if filter_kv_string:
click.echo(click.style(
"Managing all hosts that match the provided Falcon filters",
fg='magenta',
bold=True,
))
logging.info(
"Managing the containment status of all hosts that match the "
"provided Falcon filters"
)

filters = parse_cli_filters(filter_kv_string, client).get_fql()
click.echo(click.style("FQL filter string: ", bold=True), nl=False)
click.echo(filters)
logging.info(filters)

device_ids = client.hosts.get_device_ids(filters=filters)

elif device_id_list:
click.echo(click.style(
"Managing the devices identified by the IDs provided on the command line",
fg='magenta',
bold=True,
))
logging.info("Managing the devices identified by the IDs provided on the command line")

device_ids = set()
for device_id in device_id_list.split(","):
device_id = device_id.strip()
if device_id:
device_ids.add(device_id)

elif device_id_file:
click.echo(click.style(
"Managing the devices identified by the IDs listed in a file",
fg='magenta',
bold=True,
))
click.echo(click.style("File path: ", bold=True), nl=False)
click.echo(device_id_file)
logging.info("Managing the devices identified by the IDs listed in %s", device_id_file)

with open(device_id_file, 'rt', encoding='ascii') as device_id_file_handle:
device_ids = set()
for line in device_id_file_handle:
line = line.strip()
if line:
device_ids.add(line)
else:
params = False

if params and device_ids is None:
click.echo(click.style(
"No devices matched the provided filters",
fg='red',
bold=True,
))
sys.exit(1)

ctx.obj['device_ids'] = device_ids


def check_empty_device_ids(client) -> List[str]:
"""Confirm with the user whether all devices should be managed.
This function has been split out from the group as group parameters are evaluated first,
before the individual command parameters. This means that if the user accidentally provides
the filter parameters after the individual commands, rather than before, they'll need to
write I AM SURE! before being told about their mistake.
This therefore shifts the user logic to after the group parameters have been evaluated to
improve the user experience and avoid confusion.
"""
click.echo(click.style(
"You did not specify any parameters. This command will manage the containment "
"status of ALL devices in the Falcon tenant!",
fg='yellow',
))

click.echo("You must enter the string \"I AM SURE!\" to proceed.")
confirmation = input("Are you sure? ")
if confirmation != "I AM SURE!":
print("You did not confirm you were sure. Aborting!")
sys.exit(1)

logging.info("Managing all hosts in the Falcon tenant")
device_ids = client.hosts.get_device_ids()
return device_ids


@cli_containment.command(
name='contain',
help='Network contain systems in a Falcon tenant',
)
@click.pass_context
def contain(ctx: click.Context):
"""Network contain systems."""
client: Client = ctx.obj['client']
device_ids: List[str] = ctx.obj['device_ids']

if device_ids is None:
device_ids = check_empty_device_ids(client)

click.echo(f"Network containing {len(device_ids)} systems.")

perform_containment_action(
device_ids=device_ids,
client=client,
action="contain",
)


@cli_containment.command(
name='uncontain',
help='Lift network containment from systems in a Falcon tenant',
)
@click.pass_context
def uncontain(ctx: click.Context):
"""Lift network containment on systems."""
client: Client = ctx.obj['client']
device_ids: List[str] = ctx.obj['device_ids']

if device_ids is None:
device_ids = check_empty_device_ids(client)

click.echo(f"Lifting network containment on {len(device_ids)} systems.")

perform_containment_action(
device_ids=device_ids,
client=client,
action="lift_containment",
)
Loading

0 comments on commit b3232cd

Please sign in to comment.