Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Maintenance Token Command #127

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 40 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,19 @@ 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) | `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 | | | 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 | |
| &darr; API Scopes // Commands &rarr; | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) | `containment`<br>Host Containment | `maintenance_token`<br>Maintenance Tokens |
|--------------------------------------|:-------------:|:-------:|:--------------------------:|:-------------------------:|:---------------------------------:|:-----------------------------------------:|
| **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 | | | 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 | | |
| **Sensor Update Policies: Write** | | | | | | X |

### Showing Your Profiles

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

</details>

## Maintenance Tokens

You can fetch maintenance tokens for systems within your Falcon tenant, or retrieve the bulk maintenance token.

### Examples

Show the bulk maintenance token:

```shell
$ falcon -p MyCompany maintenance_token -b
Getting the bulk maintenance token
Bulk maintenance token: redactedexample12345
WARNING: this token must be kept safe, as it can uninstall all Falcon sensors!
```

Show a token for a specific system located by hostname:

```shell
$ falcon -p MyCompany maintenance_token -f Hostname=MY-TEST-BOX-1
```

Show maintenance tokens for a list of device IDs provided on the command line:

```shell
$ falcon -p MyCompany maintenance_token -d aid1,aid2,...
```

## 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.
Expand Down
1 change: 1 addition & 0 deletions falcon_toolkit/containment/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def cli_containment(
sys.exit(1)

ctx.obj['device_ids'] = device_ids
logging.debug(device_ids)


def check_empty_device_ids(client) -> List[str]:
Expand Down
2 changes: 2 additions & 0 deletions falcon_toolkit/falcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from falcon_toolkit.common.utils import configure_data_dir
from falcon_toolkit.containment.cli import cli_containment
from falcon_toolkit.hosts.cli import cli_host_search
from falcon_toolkit.maintenance_token.cli import cli_maintenance_token
from falcon_toolkit.policies.cli import cli_policies
from falcon_toolkit.shell.cli import cli_shell

Expand Down Expand Up @@ -294,5 +295,6 @@ def cli_list_filters():
cli.add_command(cli_containment)
cli.add_command(cli_host_search)
cli.add_command(cli_list_filters)
cli.add_command(cli_maintenance_token)
cli.add_command(cli_policies)
cli.add_command(cli_shell)
4 changes: 4 additions & 0 deletions falcon_toolkit/maintenance_token/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""Falcon Toolkit: Maintenance Token Retrieval.

This sub-module contains the logic required to fetch device maintenance tokens.
"""
179 changes: 179 additions & 0 deletions falcon_toolkit/maintenance_token/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""Falcon Toolkit: Maintenance Token Retrieval.

This file contains the CLI options for the falcon maintenance_token command.
"""
import logging

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.maintenance_token.device_tokens import show_device_maintenance_tokens


@click.command(
name="maintenance_token",
help="Get the maintenance token for a device, or get the bulk maintenance token"
)
@click.pass_context
@optgroup.group(
"Specify devices to get the token for",
cls=MutuallyExclusiveOptionGroup,
help="Choose no more than one method to choose systems to fetch the maintenance tokens for",
)
@optgroup.option(
'-b',
'--bulk',
'bulk_token',
type=click.BOOL,
is_flag=True,
default=False,
help="Get the CID-wide bulk maintenance token",
)
@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_maintenance_token(
ctx: click.Context,
bulk_token: bool,
device_id_list: str,
device_id_file: str,
filter_kv_string: List[str],
):
"""Get system maintenance tokens from Falcon."""
instance = get_instance(ctx)
client: Client = instance.auth_backend.authenticate()
ctx.obj['client'] = client

# Bulk token is a special case we can handle here.
# Device tokens need to be handled elsewhere.
if bulk_token:
click.echo(click.style(
"Getting the bulk maintenance token",
fg='magenta',
bold=True,
))
token = client.sensor_update_policies.get_bulk_maintenance_token(
audit_message="Fetched via Falcon Toolkit",
)
click.echo("Bulk maintenance token: ", nl=False)
click.echo(click.style(token, bold=True, fg='blue'))
click.echo(click.style(
"WARNING: this token must be kept safe, as it can uninstall all Falcon sensors!",
bold=True,
fg='red',
))

return

if filter_kv_string:
click.echo(click.style(
"Getting the maintenance tokens for all hosts that match the provided Falcon filters",
fg='magenta',
bold=True,
))
logging.info("Getting maintenance tokens for all devices that match the provided 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(
"Getting the maintenance tokens for the devices identified by the IDs provided on "
"the command line",
fg='magenta',
bold=True,
))
logging.info(
"Getting the maintenance tokens for 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(
"Getting the maintenance tokens for 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(
"Getting the maintenance tokens for 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:
click.echo(click.style(
"Getting the maintenance token for all systems in the tenant!",
bold=True,
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!")
return

device_ids = client.hosts.get_device_ids()

logging.debug(device_ids)
if device_ids:
show_device_maintenance_tokens(
device_ids=device_ids,
client=client,
)
else:
click.echo(click.style(
"No devices matched the provided filters",
fg='red',
bold=True,
))
49 changes: 49 additions & 0 deletions falcon_toolkit/maintenance_token/device_tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Falcon Toolkit: Maintenance Token Retrieval.

This file contains the logic required to fetch tokens for many devices and write them to screen.
"""
import logging

from operator import itemgetter
from typing import List

import click
import click_spinner
import tabulate

from caracara import Client


def show_device_maintenance_tokens(
device_ids: List[str],
client: Client,
):
"""Get maintenance tokens for many devices and print them to screen."""
click.echo("Fetching requested maintenance tokens. This may take a while.")

tokens = {}
header_row = [
click.style("Device ID", bold=True, fg='blue'),
click.style("Maintenance Token", bold=True, fg='blue'),
]
tokens_table = []

with click_spinner.spinner():
for device_id in device_ids:
token = client.sensor_update_policies.get_maintenance_token(
device_id=device_id,
audit_message="Fetched via Falcon Toolkit",
)
logging.debug("%s -> %s", device_id, token)
tokens[device_id] = token
tokens_table.append([
device_id,
token,
])

tokens_table = sorted(tokens_table, key=itemgetter(1, 0))
tokens_table.insert(0, header_row)
click.echo(tabulate.tabulate(
tokens_table,
tablefmt='fancy_grid',
))
22 changes: 11 additions & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ classifiers = [

[tool.poetry.dependencies]
python = "^3.9"
caracara = "^0.6.1"
caracara = "^0.7.0"
click = "^8.1.3"
click-option-group = "^0.5.6"
click-spinner = "^0.1.10"
Expand Down
Loading