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.
Files changed (48) hide show
  1. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/PKG-INFO +42 -16
  2. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/README.md +39 -13
  3. falcon_toolkit-3.5.1/falcon_toolkit/common/meta.py +11 -0
  4. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/falcon.py +7 -1
  5. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cli.py +1 -1
  6. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/prompt.py +2 -0
  7. falcon_toolkit-3.5.1/falcon_toolkit/users/__init__.py +4 -0
  8. falcon_toolkit-3.5.1/falcon_toolkit/users/cli.py +128 -0
  9. falcon_toolkit-3.5.1/falcon_toolkit/users/user_management.py +278 -0
  10. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/pyproject.toml +3 -3
  11. falcon_toolkit-3.4.2/falcon_toolkit/common/meta.py +0 -9
  12. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/LICENSE.md +0 -0
  13. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/__init__.py +0 -0
  14. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/__init__.py +0 -0
  15. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth.py +0 -0
  16. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/__init__.py +0 -0
  17. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/default.py +0 -0
  18. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/public_mssp.py +0 -0
  19. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/public_single_cid.py +0 -0
  20. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/auth_backends/utils.py +0 -0
  21. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/cli.py +0 -0
  22. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/config.py +0 -0
  23. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/console_utils.py +0 -0
  24. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/constants.py +0 -0
  25. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/logging_config.py +0 -0
  26. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/namespace.py +0 -0
  27. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/common/utils.py +0 -0
  28. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/__init__.py +0 -0
  29. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/cli.py +0 -0
  30. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/containment/perform_containment.py +0 -0
  31. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/hosts/__init__.py +0 -0
  32. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/hosts/cli.py +0 -0
  33. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/hosts/host_search.py +0 -0
  34. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/__init__.py +0 -0
  35. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/cli.py +0 -0
  36. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/maintenance_token/device_tokens.py +0 -0
  37. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/__init__.py +0 -0
  38. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/cli.py +0 -0
  39. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/constants.py +0 -0
  40. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/container.py +0 -0
  41. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/policies/describe.py +0 -0
  42. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/__init__.py +0 -0
  43. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/__init__.py +0 -0
  44. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/common.py +0 -0
  45. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/cmd_generators/reg.py +0 -0
  46. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/parsers.py +0 -0
  47. {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.1}/falcon_toolkit/shell/refresh.py +0 -0
  48. {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.4.2
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.7.0,<0.8.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
- | &darr; API Scopes // Commands &rarr; | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) | `containment`<br>Host Containment | `maintenance_token`<br>Maintenance Tokens |
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 |
224
+ | &darr; API Scopes // Commands &rarr; | `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
- | &darr; API Scopes // Commands &rarr; | `host_search` | `shell` | `policies`<br>(Prevention) | `policies`<br>(Response) | `containment`<br>Host Containment | `maintenance_token`<br>Maintenance Tokens |
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 |
186
+ | &darr; API Scopes // Commands &rarr; | `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(click.style("Falcon Toolkit", fg="blue", bold=True))
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,4 @@
1
+ """Falcon Toolkit: User Management.
2
+
3
+ This sub-module contains the implementation of the falcon user management commands.
4
+ """
@@ -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.4.2"
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.7.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