falcon-toolkit 3.4.2__tar.gz → 3.5.0__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.0}/PKG-INFO +2 -2
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/falcon.py +2 -0
- falcon_toolkit-3.5.0/falcon_toolkit/users/__init__.py +4 -0
- falcon_toolkit-3.5.0/falcon_toolkit/users/cli.py +128 -0
- falcon_toolkit-3.5.0/falcon_toolkit/users/user_management.py +278 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/pyproject.toml +3 -2
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/LICENSE.md +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/README.md +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/default.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/public_mssp.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/public_single_cid.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/utils.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/config.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/console_utils.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/constants.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/logging_config.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/meta.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/namespace.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/utils.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/containment/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/containment/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/containment/perform_containment.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/hosts/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/hosts/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/hosts/host_search.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/maintenance_token/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/maintenance_token/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/maintenance_token/device_tokens.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/policies/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/policies/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/policies/constants.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/policies/container.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/policies/describe.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/cli.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/cmd_generators/__init__.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/cmd_generators/common.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/cmd_generators/reg.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/parsers.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/prompt.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/shell/refresh.py +0 -0
- {falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/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.0
|
|
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
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
22
22
|
Classifier: Topic :: Security
|
|
23
23
|
Classifier: Topic :: System :: Shells
|
|
24
24
|
Classifier: Topic :: Terminals
|
|
25
|
-
Requires-Dist: caracara (>=0.
|
|
25
|
+
Requires-Dist: caracara (>=0.8.0,<0.9.0)
|
|
26
26
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
27
27
|
Requires-Dist: click-option-group (>=0.5.6,<0.6.0)
|
|
28
28
|
Requires-Dist: click-spinner (>=0.1.10,<0.2.0)
|
|
@@ -54,6 +54,7 @@ from falcon_toolkit.hosts.cli import cli_host_search
|
|
|
54
54
|
from falcon_toolkit.maintenance_token.cli import cli_maintenance_token
|
|
55
55
|
from falcon_toolkit.policies.cli import cli_policies
|
|
56
56
|
from falcon_toolkit.shell.cli import cli_shell
|
|
57
|
+
from falcon_toolkit.users.cli import cli_users
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
@click.group()
|
|
@@ -312,3 +313,4 @@ cli.add_command(cli_list_filters)
|
|
|
312
313
|
cli.add_command(cli_maintenance_token)
|
|
313
314
|
cli.add_command(cli_policies)
|
|
314
315
|
cli.add_command(cli_shell)
|
|
316
|
+
cli.add_command(cli_users)
|
|
@@ -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.0"
|
|
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.8.0"
|
|
34
35
|
click = "^8.1.3"
|
|
35
36
|
click-option-group = "^0.5.6"
|
|
36
37
|
click-spinner = "^0.1.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/__init__.py
RENAMED
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/falcon_toolkit/common/auth_backends/default.py
RENAMED
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/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
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/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.0}/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
|
|
File without changes
|
{falcon_toolkit-3.4.2 → falcon_toolkit-3.5.0}/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
|
|
File without changes
|