falcon-toolkit 3.4.2__tar.gz → 3.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/PKG-INFO +42 -16
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/README.md +39 -13
- falcon_toolkit-3.5.1/falcon_toolkit/common/meta.py +11 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/falcon.py +7 -1
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cli.py +1 -1
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/prompt.py +2 -0
- falcon_toolkit-3.5.1/falcon_toolkit/users/__init__.py +4 -0
- falcon_toolkit-3.5.1/falcon_toolkit/users/cli.py +128 -0
- falcon_toolkit-3.5.1/falcon_toolkit/users/user_management.py +278 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/pyproject.toml +3 -3
- falcon_toolkit-3.4.2/falcon_toolkit/common/meta.py +0 -9
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/LICENSE.md +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/default.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/public_mssp.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/public_single_cid.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/utils.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/config.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/console_utils.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/constants.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/logging_config.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/namespace.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/utils.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/perform_containment.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/hosts/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/hosts/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/hosts/host_search.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/device_tokens.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/constants.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/container.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/describe.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/common.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/reg.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/parsers.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/refresh.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: falcon-toolkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.1
|
|
4
4
|
Summary: Toolkit to interface with CrowdStrike Falcon via the API
|
|
5
5
|
Home-page: http://github.com/CrowdStrike/Falcon-Toolkit
|
|
6
6
|
License: MIT
|
|
@@ -18,11 +18,12 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
23
|
Classifier: Topic :: Security
|
|
23
24
|
Classifier: Topic :: System :: Shells
|
|
24
25
|
Classifier: Topic :: Terminals
|
|
25
|
-
Requires-Dist: caracara (>=0.
|
|
26
|
+
Requires-Dist: caracara (>=0.9.0,<0.10.0)
|
|
26
27
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
27
28
|
Requires-Dist: click-option-group (>=0.5.6,<0.6.0)
|
|
28
29
|
Requires-Dist: click-spinner (>=0.1.10,<0.2.0)
|
|
@@ -31,7 +32,6 @@ Requires-Dist: colorama (>=0.4.5,<0.5.0)
|
|
|
31
32
|
Requires-Dist: keyring (>=24,<26)
|
|
32
33
|
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
|
33
34
|
Requires-Dist: prompt-toolkit (>=3.0.43,<4.0.0)
|
|
34
|
-
Requires-Dist: setuptools (>=69.0,<70.0)
|
|
35
35
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
36
36
|
Project-URL: Repository, http://github.com/CrowdStrike/Falcon-Toolkit
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
@@ -221,19 +221,22 @@ Two types of configuration backends are provided out of the box: the default, wh
|
|
|
221
221
|
|
|
222
222
|
Your API keys should have the following scopes enabled in the Falcon dashboard:
|
|
223
223
|
|
|
224
|
-
| ↓ API Scopes // Commands → |
|
|
225
|
-
|
|
226
|
-
| **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* |
|
|
227
|
-
| **Hosts: Read** |
|
|
228
|
-
| **Hosts: Write** |
|
|
229
|
-
| **Prevention Policies: Read** |
|
|
230
|
-
| **Prevention Policies: Write** |
|
|
231
|
-
| **Real Time Response: Read** |
|
|
232
|
-
| **Real Time Response: Write** |
|
|
233
|
-
| **Real Time Response: Admin** |
|
|
234
|
-
| **Response Policies: Read** |
|
|
235
|
-
| **Response Policies: Write** |
|
|
236
|
-
| **Sensor Update Policies: Write** |
|
|
224
|
+
| ↓ API Scopes // Commands → | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) | `containment`<br>Host Containment | `maintenance_token`<br>Maintenance Tokens | `users`<br>User Management |
|
|
225
|
+
| ------------------------------------ | :--------------------------------------: | :--------------------------------------: | :--------------------------------------: | :--------------------------------------: | :-------------------------------: | :---------------------------------------: | :----------------------------------------------------------------: |
|
|
226
|
+
| **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* | | |
|
|
227
|
+
| **Hosts: Read** | X | X | | | X | X | |
|
|
228
|
+
| **Hosts: Write** | | | | | X | | |
|
|
229
|
+
| **Prevention Policies: Read** | | | X<br>`describe` / `export` sub-commands | | | | |
|
|
230
|
+
| **Prevention Policies: Write** | | | X<br>`import` sub-command | | | | |
|
|
231
|
+
| **Real Time Response: Read** | | X | | | | | |
|
|
232
|
+
| **Real Time Response: Write** | | X | | | | | |
|
|
233
|
+
| **Real Time Response: Admin** | | X<br>*for admin commands* | | | | | |
|
|
234
|
+
| **Response Policies: Read** | | | | X<br>`describe` / `export` sub-commands | | | |
|
|
235
|
+
| **Response Policies: Write** | | | | X<br>`import` sub-command | | | |
|
|
236
|
+
| **Sensor Update Policies: Write** | | | | | | X | |
|
|
237
|
+
| **Sensor Download: Read** | | | | | | | X<br>*For adding roles to users only* |
|
|
238
|
+
| **User Management: Read** | | | | | | | X<br>*For `describe` and `list` sub-commands* |
|
|
239
|
+
| **User Management: Write** | | | | | | | X<br>*For user and user role add, import, and change sub-commands* |
|
|
237
240
|
|
|
238
241
|
### Showing Your Profiles
|
|
239
242
|
|
|
@@ -505,6 +508,29 @@ $ falcon policies -p import MyExportedPolicy.json
|
|
|
505
508
|
...
|
|
506
509
|
```
|
|
507
510
|
|
|
511
|
+
## User Manipulation
|
|
512
|
+
|
|
513
|
+
The `falcon users` command has multiple sub-commands that let you view and manipulate users in your Falcon tenant.
|
|
514
|
+
- `add_user`: Add a new user.
|
|
515
|
+
- `add_roles`: Assign new roles to a user.
|
|
516
|
+
- `delete_user`: Delete a user.
|
|
517
|
+
- `describe_users`: Show all users in a Falcon tenant alongside their roles.
|
|
518
|
+
- `import_users`: Import a list of users from a CSV.
|
|
519
|
+
- `list_roles`: Show all roles that may be assigned to users.
|
|
520
|
+
|
|
521
|
+
### CSV Formatting for `import_users`
|
|
522
|
+
|
|
523
|
+
The CSV must contain the header first_name,last_name,email_address, and optionally may contain the roles field.
|
|
524
|
+
|
|
525
|
+
first_name,last_name,email_address,roles
|
|
526
|
+
alice,example,alice.example@example.com
|
|
527
|
+
bob,example,bob.example@example.com,risk_manager
|
|
528
|
+
eve,example,eve.example@example.com
|
|
529
|
+
|
|
530
|
+
```shell
|
|
531
|
+
$ falcon users import_users NewUsers.csv
|
|
532
|
+
```
|
|
533
|
+
|
|
508
534
|
## Support & Community Forums
|
|
509
535
|
|
|
510
536
|
Falcon Toolkit is an open source project, and not a formal CrowdStrike product, designed to assist users with managing their Falcon tenants and executing commands at scale. As such, it carries no formal support, express or implied. This project originated out of the CrowdStrike Services Incident Response (IR) team's need to execute commands across Falcon tenants quickly, at scale, and with auditing, and is maintained by [Chris Hammond](mailto:Chris.Hammond@crowdstrike.com).
|
|
@@ -183,19 +183,22 @@ Two types of configuration backends are provided out of the box: the default, wh
|
|
|
183
183
|
|
|
184
184
|
Your API keys should have the following scopes enabled in the Falcon dashboard:
|
|
185
185
|
|
|
186
|
-
| ↓ API Scopes // Commands → |
|
|
187
|
-
|
|
188
|
-
| **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* |
|
|
189
|
-
| **Hosts: Read** |
|
|
190
|
-
| **Hosts: Write** |
|
|
191
|
-
| **Prevention Policies: Read** |
|
|
192
|
-
| **Prevention Policies: Write** |
|
|
193
|
-
| **Real Time Response: Read** |
|
|
194
|
-
| **Real Time Response: Write** |
|
|
195
|
-
| **Real Time Response: Admin** |
|
|
196
|
-
| **Response Policies: Read** |
|
|
197
|
-
| **Response Policies: Write** |
|
|
198
|
-
| **Sensor Update Policies: Write** |
|
|
186
|
+
| ↓ API Scopes // Commands → | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) | `containment`<br>Host Containment | `maintenance_token`<br>Maintenance Tokens | `users`<br>User Management |
|
|
187
|
+
| ------------------------------------ | :--------------------------------------: | :--------------------------------------: | :--------------------------------------: | :--------------------------------------: | :-------------------------------: | :---------------------------------------: | :----------------------------------------------------------------: |
|
|
188
|
+
| **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* | | |
|
|
189
|
+
| **Hosts: Read** | X | X | | | X | X | |
|
|
190
|
+
| **Hosts: Write** | | | | | X | | |
|
|
191
|
+
| **Prevention Policies: Read** | | | X<br>`describe` / `export` sub-commands | | | | |
|
|
192
|
+
| **Prevention Policies: Write** | | | X<br>`import` sub-command | | | | |
|
|
193
|
+
| **Real Time Response: Read** | | X | | | | | |
|
|
194
|
+
| **Real Time Response: Write** | | X | | | | | |
|
|
195
|
+
| **Real Time Response: Admin** | | X<br>*for admin commands* | | | | | |
|
|
196
|
+
| **Response Policies: Read** | | | | X<br>`describe` / `export` sub-commands | | | |
|
|
197
|
+
| **Response Policies: Write** | | | | X<br>`import` sub-command | | | |
|
|
198
|
+
| **Sensor Update Policies: Write** | | | | | | X | |
|
|
199
|
+
| **Sensor Download: Read** | | | | | | | X<br>*For adding roles to users only* |
|
|
200
|
+
| **User Management: Read** | | | | | | | X<br>*For `describe` and `list` sub-commands* |
|
|
201
|
+
| **User Management: Write** | | | | | | | X<br>*For user and user role add, import, and change sub-commands* |
|
|
199
202
|
|
|
200
203
|
### Showing Your Profiles
|
|
201
204
|
|
|
@@ -467,6 +470,29 @@ $ falcon policies -p import MyExportedPolicy.json
|
|
|
467
470
|
...
|
|
468
471
|
```
|
|
469
472
|
|
|
473
|
+
## User Manipulation
|
|
474
|
+
|
|
475
|
+
The `falcon users` command has multiple sub-commands that let you view and manipulate users in your Falcon tenant.
|
|
476
|
+
- `add_user`: Add a new user.
|
|
477
|
+
- `add_roles`: Assign new roles to a user.
|
|
478
|
+
- `delete_user`: Delete a user.
|
|
479
|
+
- `describe_users`: Show all users in a Falcon tenant alongside their roles.
|
|
480
|
+
- `import_users`: Import a list of users from a CSV.
|
|
481
|
+
- `list_roles`: Show all roles that may be assigned to users.
|
|
482
|
+
|
|
483
|
+
### CSV Formatting for `import_users`
|
|
484
|
+
|
|
485
|
+
The CSV must contain the header first_name,last_name,email_address, and optionally may contain the roles field.
|
|
486
|
+
|
|
487
|
+
first_name,last_name,email_address,roles
|
|
488
|
+
alice,example,alice.example@example.com
|
|
489
|
+
bob,example,bob.example@example.com,risk_manager
|
|
490
|
+
eve,example,eve.example@example.com
|
|
491
|
+
|
|
492
|
+
```shell
|
|
493
|
+
$ falcon users import_users NewUsers.csv
|
|
494
|
+
```
|
|
495
|
+
|
|
470
496
|
## Support & Community Forums
|
|
471
497
|
|
|
472
498
|
Falcon Toolkit is an open source project, and not a formal CrowdStrike product, designed to assist users with managing their Falcon tenants and executing commands at scale. As such, it carries no formal support, express or implied. This project originated out of the CrowdStrike Services Incident Response (IR) team's need to execute commands across Falcon tenants quickly, at scale, and with auditing, and is maintained by [Chris Hammond](mailto:Chris.Hammond@crowdstrike.com).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Falcon Toolkit: Meta.
|
|
2
|
+
|
|
3
|
+
This is where all functions and variable setting that are based on application metadata live.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.metadata
|
|
7
|
+
|
|
8
|
+
# Derive the version via importlib.metadata, which is populated based on the version in
|
|
9
|
+
# pyproject.toml. We used to use pkg_resources for this, but using importlib.metadata means
|
|
10
|
+
# that setuptools is no longer required.
|
|
11
|
+
__version__ = importlib.metadata.version("falcon-toolkit")
|
|
@@ -48,12 +48,14 @@ from falcon_toolkit.common.constants import (
|
|
|
48
48
|
OLD_DEFAULT_CONFIG_DIR,
|
|
49
49
|
)
|
|
50
50
|
from falcon_toolkit.common.logging_config import configure_logger
|
|
51
|
+
from falcon_toolkit.common.meta import __version__
|
|
51
52
|
from falcon_toolkit.common.utils import configure_data_dir
|
|
52
53
|
from falcon_toolkit.containment.cli import cli_containment
|
|
53
54
|
from falcon_toolkit.hosts.cli import cli_host_search
|
|
54
55
|
from falcon_toolkit.maintenance_token.cli import cli_maintenance_token
|
|
55
56
|
from falcon_toolkit.policies.cli import cli_policies
|
|
56
57
|
from falcon_toolkit.shell.cli import cli_shell
|
|
58
|
+
from falcon_toolkit.users.cli import cli_users
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
@click.group()
|
|
@@ -134,7 +136,10 @@ def cli(
|
|
|
134
136
|
|
|
135
137
|
# Configure context that can be passed down to other options
|
|
136
138
|
ctx.ensure_object(dict)
|
|
137
|
-
click.echo(
|
|
139
|
+
click.echo(
|
|
140
|
+
click.style("Falcon Toolkit", fg="blue", bold=True)
|
|
141
|
+
+ click.style(f" v{__version__}", fg="black", bold=True)
|
|
142
|
+
)
|
|
138
143
|
hyperlink = build_file_hyperlink(config_path, config_path, "falcon_config_path")
|
|
139
144
|
click.echo(click.style(f"Configuration Directory: {hyperlink}", fg="black"))
|
|
140
145
|
if verbose:
|
|
@@ -312,3 +317,4 @@ cli.add_command(cli_list_filters)
|
|
|
312
317
|
cli.add_command(cli_maintenance_token)
|
|
313
318
|
cli.add_command(cli_policies)
|
|
314
319
|
cli.add_command(cli_shell)
|
|
320
|
+
cli.add_command(cli_users)
|
|
@@ -94,7 +94,7 @@ from falcon_toolkit.shell.prompt import RTRPrompt
|
|
|
94
94
|
required=False,
|
|
95
95
|
help="Set the timeout for RTR (default: 30s)",
|
|
96
96
|
)
|
|
97
|
-
def cli_shell( # pylint: disable=too-many-arguments,too-many-locals
|
|
97
|
+
def cli_shell( # pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
|
|
98
98
|
ctx: click.Context,
|
|
99
99
|
device_id_list: str,
|
|
100
100
|
device_id_file: str,
|
|
@@ -49,6 +49,7 @@ class RTRPrompt(Cmd):
|
|
|
49
49
|
|
|
50
50
|
# pylint: disable=too-many-arguments
|
|
51
51
|
# pylint: disable=too-many-locals
|
|
52
|
+
# pylint: disable=too-many-positional-arguments
|
|
52
53
|
# pylint: disable=too-many-statements
|
|
53
54
|
def __init__(
|
|
54
55
|
self,
|
|
@@ -388,6 +389,7 @@ class RTRPrompt(Cmd):
|
|
|
388
389
|
|
|
389
390
|
return get_file_data
|
|
390
391
|
|
|
392
|
+
# pylint: disable=too-many-positional-arguments
|
|
391
393
|
def write_result_row(self, command: str, aid: str, complete: bool, stdout: str, stderr: str):
|
|
392
394
|
"""Write a row of output to the CSV log file."""
|
|
393
395
|
hostname = self.device_data[aid].get("hostname", "<NO HOSTNAME>")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Falcon Toolkit: User Modification.
|
|
2
|
+
|
|
3
|
+
This file contains the CLI options for the falcon user modification commands.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from os import path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from caracara import Client
|
|
10
|
+
from prompt_toolkit import prompt
|
|
11
|
+
|
|
12
|
+
from falcon_toolkit.users.user_management import (
|
|
13
|
+
user_deletion,
|
|
14
|
+
add_csv_users,
|
|
15
|
+
add_single_user,
|
|
16
|
+
add_roles_to_user,
|
|
17
|
+
print_formatted_user_roles,
|
|
18
|
+
describe_existing_users,
|
|
19
|
+
)
|
|
20
|
+
from falcon_toolkit.common.cli import get_instance
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group(
|
|
24
|
+
name="users",
|
|
25
|
+
help="Manage Falcon User Creation and Deletion",
|
|
26
|
+
)
|
|
27
|
+
@click.pass_context
|
|
28
|
+
def cli_users(ctx: click.Context):
|
|
29
|
+
"""Falcon Toolkit user management base command."""
|
|
30
|
+
instance = get_instance(ctx)
|
|
31
|
+
client: Client = instance.auth_backend.authenticate(ctx)
|
|
32
|
+
ctx.obj["client"] = client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.command(
|
|
36
|
+
name="add_user",
|
|
37
|
+
help="Create users via CLI inputs",
|
|
38
|
+
)
|
|
39
|
+
@click.pass_context
|
|
40
|
+
def add_user(ctx: click.Context):
|
|
41
|
+
"""Create a new user in the Falcon tenant."""
|
|
42
|
+
single_email = prompt("Enter the new user's email address: ")
|
|
43
|
+
first_name = prompt(f"Enter {single_email}'s first name: ")
|
|
44
|
+
last_name = prompt(f"Enter {single_email}'s last name: ")
|
|
45
|
+
add_single_user(ctx.obj["client"], first_name, last_name, single_email)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@click.command(
|
|
49
|
+
name="import_users",
|
|
50
|
+
help="Create users from a CSV file formatted first_name,last_name,email_address",
|
|
51
|
+
)
|
|
52
|
+
@click.argument("csv_file")
|
|
53
|
+
@click.pass_context
|
|
54
|
+
def import_users(ctx: click.Context, csv_file):
|
|
55
|
+
"""Create users and set their roles in the Falcon tenant from a CSV file."""
|
|
56
|
+
if path.isfile(csv_file):
|
|
57
|
+
add_csv_users(ctx.obj["client"], csv_file)
|
|
58
|
+
else:
|
|
59
|
+
click.echo(click.style("Error: Invalid File Path", bold=True, fg="red"))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@click.command(
|
|
63
|
+
name="delete_user",
|
|
64
|
+
help="Delete users via email address",
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"-f",
|
|
68
|
+
"--file",
|
|
69
|
+
"file_name",
|
|
70
|
+
help="path to line-seperated file of user accounts to be deleted",
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"-e",
|
|
74
|
+
"--user_email",
|
|
75
|
+
help="Email address of user to be deleted",
|
|
76
|
+
)
|
|
77
|
+
@click.pass_context
|
|
78
|
+
def delete_user(ctx: click.Context, file_name: str = None, user_email: str = None):
|
|
79
|
+
"""Delete a user from the Falcon tenant by email address, or delete many using a file."""
|
|
80
|
+
email_list = []
|
|
81
|
+
if file_name is not None:
|
|
82
|
+
try:
|
|
83
|
+
with open(file_name, newline="", encoding="utf-8") as email_list_file:
|
|
84
|
+
email_list = email_list_file.read().splitlines()
|
|
85
|
+
user_deletion(ctx.obj["client"], email_list)
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
click.echo(click.style("Error: Invalid File Path", bold=True, fg="red"))
|
|
88
|
+
elif user_email is not None:
|
|
89
|
+
email_list.append(user_email)
|
|
90
|
+
user_deletion(ctx.obj["client"], email_list)
|
|
91
|
+
else:
|
|
92
|
+
click.echo(
|
|
93
|
+
click.style(
|
|
94
|
+
"Please specify either a file or user email. "
|
|
95
|
+
"Try 'falcon users delete_user --help' for help.",
|
|
96
|
+
bold=True,
|
|
97
|
+
fg="red",
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@click.command(name="describe_users", help="List all existing users in the cloud instance")
|
|
103
|
+
@click.pass_context
|
|
104
|
+
def describe_users(ctx: click.Context):
|
|
105
|
+
"""Show all the user accounts in the Falcon tenant, and their respective roles."""
|
|
106
|
+
describe_existing_users(ctx.obj["client"])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@click.command(name="list_roles", help="List all available roles in the cloud instance")
|
|
110
|
+
@click.pass_context
|
|
111
|
+
def list_roles(ctx: click.Context):
|
|
112
|
+
"""Show all the roles that exist within the Falcon tenant and may be assigned to users."""
|
|
113
|
+
print_formatted_user_roles(ctx.obj["client"])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@click.command(name="add_roles", help="Add roles via user email and role ID")
|
|
117
|
+
@click.pass_context
|
|
118
|
+
def add_roles(ctx: click.Context):
|
|
119
|
+
"""Grant new roles to a user account in the Falcon tenant."""
|
|
120
|
+
add_roles_to_user(ctx.obj["client"])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
cli_users.add_command(add_user)
|
|
124
|
+
cli_users.add_command(import_users)
|
|
125
|
+
cli_users.add_command(delete_user)
|
|
126
|
+
cli_users.add_command(describe_users)
|
|
127
|
+
cli_users.add_command(list_roles)
|
|
128
|
+
cli_users.add_command(add_roles)
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Falcon Toolkit: User Management.
|
|
2
|
+
|
|
3
|
+
This file contains the logic required to create and configure users.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import sys
|
|
8
|
+
from operator import itemgetter
|
|
9
|
+
from textwrap import TextWrapper
|
|
10
|
+
from typing import Dict, List, Optional, Set, Union
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
import click_spinner
|
|
14
|
+
import tabulate
|
|
15
|
+
from caracara import Client
|
|
16
|
+
from caracara.common.csdialog import csradiolist_dialog
|
|
17
|
+
from caracara.common.exceptions import GenericAPIError
|
|
18
|
+
from prompt_toolkit import prompt
|
|
19
|
+
from prompt_toolkit.completion import FuzzyWordCompleter
|
|
20
|
+
|
|
21
|
+
from falcon_toolkit.hosts import host_search
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _tabulate_users( # pylint: disable=R0914
|
|
25
|
+
user_data: Union[List[Dict[str, str]], Dict[str, str]],
|
|
26
|
+
role_data: Optional[Dict[str, Dict]] = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Pretty print a list of users to screen in a tabular format."""
|
|
29
|
+
header_row = [
|
|
30
|
+
click.style("Name", bold=True, fg="blue"),
|
|
31
|
+
click.style("Email Address", bold=True, fg="blue"),
|
|
32
|
+
click.style("Status", bold=True, fg="blue"),
|
|
33
|
+
click.style("Creation Date", bold=True, fg="blue"),
|
|
34
|
+
click.style("Modification Date", bold=True, fg="blue"),
|
|
35
|
+
click.style("UUID", bold=True, fg="blue"),
|
|
36
|
+
click.style("Roles", bold=True, fg="blue"),
|
|
37
|
+
]
|
|
38
|
+
table_rows = []
|
|
39
|
+
|
|
40
|
+
grouping_tag_wrap = TextWrapper()
|
|
41
|
+
grouping_tag_wrap.width = 40
|
|
42
|
+
|
|
43
|
+
sixteen_wrap = TextWrapper()
|
|
44
|
+
sixteen_wrap.width = 16
|
|
45
|
+
|
|
46
|
+
for extracted_dict in user_data:
|
|
47
|
+
email_address = extracted_dict["uid"]
|
|
48
|
+
name = str(extracted_dict["first_name"]) + " " + str(extracted_dict["last_name"])
|
|
49
|
+
creation_date = extracted_dict["created_at"]
|
|
50
|
+
modification_date = extracted_dict["updated_at"]
|
|
51
|
+
uuid = extracted_dict["uuid"]
|
|
52
|
+
if role_data:
|
|
53
|
+
roles_list = sorted(
|
|
54
|
+
[
|
|
55
|
+
role_data[x]["display_name"] if x in role_data else x
|
|
56
|
+
for x in extracted_dict["roles"]
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
roles_list = extracted_dict["roles"]
|
|
61
|
+
|
|
62
|
+
activation_status = extracted_dict["status"]
|
|
63
|
+
if activation_status == "active":
|
|
64
|
+
status = click.style("Active", fg="green")
|
|
65
|
+
else:
|
|
66
|
+
status = click.style(activation_status, fg="red")
|
|
67
|
+
|
|
68
|
+
string_roles = ""
|
|
69
|
+
for role in roles_list:
|
|
70
|
+
string_roles = string_roles + role + "\n"
|
|
71
|
+
|
|
72
|
+
row = [name, email_address, status, creation_date, modification_date, uuid, string_roles]
|
|
73
|
+
table_rows.append(row)
|
|
74
|
+
|
|
75
|
+
table_rows = sorted(table_rows, key=itemgetter(1, 0))
|
|
76
|
+
|
|
77
|
+
for row in table_rows:
|
|
78
|
+
host_search.vertically_align_middle(row)
|
|
79
|
+
|
|
80
|
+
table_rows.insert(0, header_row)
|
|
81
|
+
|
|
82
|
+
click.echo(
|
|
83
|
+
tabulate.tabulate(
|
|
84
|
+
table_rows,
|
|
85
|
+
tablefmt="fancy_grid",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def add_csv_users(client: Client, csv_name: str): # pylint: disable=R0914
|
|
91
|
+
"""Create users based on a CSV formatted first_name,last_name,email_address."""
|
|
92
|
+
successful_uuid_set: Set[str] = set()
|
|
93
|
+
|
|
94
|
+
with open(csv_name, newline="", encoding="utf-8") as csv_file:
|
|
95
|
+
reader = csv.DictReader(csv_file)
|
|
96
|
+
if (
|
|
97
|
+
"first_name" not in reader.fieldnames
|
|
98
|
+
or "last_name" not in reader.fieldnames
|
|
99
|
+
or "email_address" not in reader.fieldnames
|
|
100
|
+
):
|
|
101
|
+
raise KeyError(
|
|
102
|
+
"The CSV must contain at least the fields first_name, last_name, and email_address"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
for row in reader:
|
|
106
|
+
first_name = row["first_name"]
|
|
107
|
+
last_name = row["last_name"]
|
|
108
|
+
email_address = row["email_address"]
|
|
109
|
+
uuid: Optional[str] = None
|
|
110
|
+
|
|
111
|
+
click.echo(
|
|
112
|
+
"Processing user: "
|
|
113
|
+
+ click.style(f"{first_name} {last_name}", bold=True)
|
|
114
|
+
+ " ("
|
|
115
|
+
+ click.style(email_address, bold=True)
|
|
116
|
+
+ ")"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
user_add_result = client.users.add_user(
|
|
121
|
+
first_name,
|
|
122
|
+
last_name,
|
|
123
|
+
email_address,
|
|
124
|
+
)
|
|
125
|
+
uuid = user_add_result["uuid"]
|
|
126
|
+
successful_uuid_set.add(uuid)
|
|
127
|
+
|
|
128
|
+
except GenericAPIError as bad_user_error:
|
|
129
|
+
print(click.style(bad_user_error, bold=True, fg="yellow"))
|
|
130
|
+
except KeyError:
|
|
131
|
+
print(click.style("The API response did not include a new user UUID", fg="red"))
|
|
132
|
+
|
|
133
|
+
if "roles" in row and row["roles"] is not None:
|
|
134
|
+
roles_str = row["roles"]
|
|
135
|
+
click.echo(f"Adding roles to user {email_address}: {roles_str}")
|
|
136
|
+
|
|
137
|
+
if not uuid:
|
|
138
|
+
uuid = client.users.get_uuid_by_email(email_address)
|
|
139
|
+
|
|
140
|
+
roles = roles_str.split(",")
|
|
141
|
+
if client.users.add_user_roles(uuid, roles):
|
|
142
|
+
successful_uuid_set.add(uuid)
|
|
143
|
+
else:
|
|
144
|
+
print(click.style(f"Role addition failed for {roles}", bold=True, fg="red"))
|
|
145
|
+
|
|
146
|
+
if not successful_uuid_set:
|
|
147
|
+
# Nothing succeeded
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
with click_spinner.spinner():
|
|
151
|
+
user_data = client.users.describe_users(user_uuids=list(successful_uuid_set))
|
|
152
|
+
role_data = client.users.describe_available_roles()
|
|
153
|
+
|
|
154
|
+
click.echo(click.style("Affected Users", bold=True))
|
|
155
|
+
_tabulate_users(list(user_data.values()), role_data)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def add_single_user(client: Client, first_name: str, last_name: str, email_address: str):
|
|
159
|
+
"""Create a single user in the Falcon tenant."""
|
|
160
|
+
with click_spinner.spinner():
|
|
161
|
+
result = client.users.add_user(first_name, last_name, email_address)
|
|
162
|
+
user_data = client.users.describe_users(user_uuids=[result["uuid"]])
|
|
163
|
+
|
|
164
|
+
click.echo(click.style("New User", bold=True))
|
|
165
|
+
_tabulate_users(list(user_data.values()))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def delete_user_guardrails(email_addresses: list):
|
|
169
|
+
"""Confirm via a visual Prompt Toolkit box whether the user really wants to delete a user."""
|
|
170
|
+
confirmation_options = [
|
|
171
|
+
(False, "Abort"),
|
|
172
|
+
(True, "Delete accounts"),
|
|
173
|
+
]
|
|
174
|
+
prompt_text = "Are you sure you want to delete these accounts?\n" + "\n".join(email_addresses)
|
|
175
|
+
confirmation: bool = csradiolist_dialog(
|
|
176
|
+
title="Confirm User Deletion Action",
|
|
177
|
+
text=prompt_text,
|
|
178
|
+
values=confirmation_options,
|
|
179
|
+
).run()
|
|
180
|
+
|
|
181
|
+
if confirmation:
|
|
182
|
+
click.echo(click.style("User confirmed action", bold=True, fg="green"))
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
click.echo(click.style("Aborted!", bold=True, fg="red"))
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def user_deletion(client: Client, email_addresses: list):
|
|
190
|
+
"""Validate email addresses, confirm deletion, and ultimately delete users."""
|
|
191
|
+
uuid_list = []
|
|
192
|
+
bad_emails = []
|
|
193
|
+
for i in email_addresses:
|
|
194
|
+
try:
|
|
195
|
+
uuid = client.users.get_uuid_by_email(i)
|
|
196
|
+
uuid_list.append(uuid)
|
|
197
|
+
except GenericAPIError:
|
|
198
|
+
click.echo(click.style(f"Invalid Email Address: {i}", bold=True, fg="red"))
|
|
199
|
+
bad_emails.append(i)
|
|
200
|
+
|
|
201
|
+
good_emails = [email for email in email_addresses if email not in bad_emails]
|
|
202
|
+
if good_emails and delete_user_guardrails(good_emails):
|
|
203
|
+
for i in uuid_list:
|
|
204
|
+
deletion_result = client.users.delete_user(i)
|
|
205
|
+
if deletion_result is False:
|
|
206
|
+
click.echo(click.style(f"Error deleting user: {i}", bold=True, fg="red"))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def print_formatted_user_roles(client: Client):
|
|
210
|
+
"""Retrieve all available user roles, and format them on screen."""
|
|
211
|
+
unformatted_roles = client.users.describe_available_roles()
|
|
212
|
+
for role_key in unformatted_roles:
|
|
213
|
+
all_values = unformatted_roles[role_key]
|
|
214
|
+
user_id = all_values["id"]
|
|
215
|
+
display_name = all_values["display_name"]
|
|
216
|
+
description = all_values["description"]
|
|
217
|
+
is_global = all_values["is_global"]
|
|
218
|
+
print(click.style(f"ID: {user_id}", bold=True, fg="green"))
|
|
219
|
+
print(f"Display Name: {display_name}")
|
|
220
|
+
print(f"Is Global: {is_global}")
|
|
221
|
+
print("Description:")
|
|
222
|
+
print(description)
|
|
223
|
+
print("")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def add_roles_to_user(client: Client):
|
|
227
|
+
"""Autocomplete role names to efficiently add roles to a user via email address.
|
|
228
|
+
|
|
229
|
+
Sensor Download READ permissions required.
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
user_email = prompt("Enter the user email address: ")
|
|
233
|
+
user_uuid = client.users.get_uuid_by_email(user_email)
|
|
234
|
+
except GenericAPIError as error:
|
|
235
|
+
print(click.style(error, bold=True, fg="red"))
|
|
236
|
+
sys.exit()
|
|
237
|
+
|
|
238
|
+
selected_roles = []
|
|
239
|
+
all_roles = []
|
|
240
|
+
all_roles_meta_dict = {}
|
|
241
|
+
|
|
242
|
+
unformatted_roles = client.users.describe_available_roles()
|
|
243
|
+
for role_key in unformatted_roles:
|
|
244
|
+
all_values = unformatted_roles[role_key]
|
|
245
|
+
all_roles.append(all_values["id"])
|
|
246
|
+
all_roles_meta_dict[all_values["id"]] = all_values["display_name"]
|
|
247
|
+
|
|
248
|
+
list_completer = FuzzyWordCompleter(
|
|
249
|
+
all_roles,
|
|
250
|
+
meta_dict=all_roles_meta_dict,
|
|
251
|
+
)
|
|
252
|
+
add_more_roles = "yes"
|
|
253
|
+
while add_more_roles.lower() == "yes" or add_more_roles.lower() == "y":
|
|
254
|
+
role_option = prompt("Select Role: ", completer=list_completer)
|
|
255
|
+
selected_roles.append(role_option)
|
|
256
|
+
add_more_roles = prompt("Add more roles? (yes or no): ")
|
|
257
|
+
|
|
258
|
+
print(f"Adding selected roles: {selected_roles}")
|
|
259
|
+
actually_add = prompt("Continue? (yes or no): ")
|
|
260
|
+
if actually_add.lower() == "yes" or actually_add.lower() == "y":
|
|
261
|
+
|
|
262
|
+
result = client.users.add_user_roles(user_uuid, selected_roles)
|
|
263
|
+
if result is True:
|
|
264
|
+
print(click.style("Successfully added roles", bold=True, fg="green"))
|
|
265
|
+
else:
|
|
266
|
+
print(click.style("Operation Failed, Check Role Name", bold=True, fg="red"))
|
|
267
|
+
else:
|
|
268
|
+
print("Cancelling operation")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def describe_existing_users(client: Client):
|
|
272
|
+
"""Show all users in a CID within a formatted table."""
|
|
273
|
+
with click_spinner.spinner():
|
|
274
|
+
user_data = client.users.describe_users()
|
|
275
|
+
role_data = client.users.describe_available_roles()
|
|
276
|
+
formatted_data = list(user_data.values())
|
|
277
|
+
|
|
278
|
+
_tabulate_users(formatted_data, role_data)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "falcon-toolkit"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.5.1"
|
|
4
4
|
description = "Toolkit to interface with CrowdStrike Falcon via the API"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = [
|
|
7
7
|
"Chris Hammond <chris.hammond@crowdstrike.com>",
|
|
8
8
|
"Kira Noël <kira.noel@crowdstrike.com>",
|
|
9
|
+
"Natalie Eckles <natalie.eckles@crowdstrike.com>",
|
|
9
10
|
]
|
|
10
11
|
repository = "http://github.com/CrowdStrike/Falcon-Toolkit"
|
|
11
12
|
readme = "README.md"
|
|
@@ -30,7 +31,7 @@ classifiers = [
|
|
|
30
31
|
|
|
31
32
|
[tool.poetry.dependencies]
|
|
32
33
|
python = "^3.9"
|
|
33
|
-
caracara = "^0.
|
|
34
|
+
caracara = "^0.9.0"
|
|
34
35
|
click = "^8.1.3"
|
|
35
36
|
click-option-group = "^0.5.6"
|
|
36
37
|
click-spinner = "^0.1.10"
|
|
@@ -40,7 +41,6 @@ keyring = ">=24,<26"
|
|
|
40
41
|
platformdirs = "^4.2.0"
|
|
41
42
|
tabulate = "^0.9.0"
|
|
42
43
|
prompt-toolkit = "^3.0.43"
|
|
43
|
-
setuptools = "^69.0"
|
|
44
44
|
|
|
45
45
|
[tool.poetry.scripts]
|
|
46
46
|
falcon = "falcon_toolkit.falcon:cli"
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"""Falcon Toolkit: Meta.
|
|
2
|
-
|
|
3
|
-
This is where all functions and variable setting that are based on application metadata live.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import pkg_resources
|
|
7
|
-
|
|
8
|
-
# Derive the version via pkg_resources, which is populated based on the version in pyproject.toml
|
|
9
|
-
__version__ = pkg_resources.get_distribution(__name__.split(".", maxsplit=1)[0]).version
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/__init__.py
RENAMED
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/default.py
RENAMED
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/public_mssp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/perform_containment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/device_tokens.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|