Skip to content

Commit c36cb66

Browse files
Feature/user alert rules (#53)
1 parent d7b8c58 commit c36cb66

File tree

20 files changed

+426
-34
lines changed

20 files changed

+426
-34
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1212

1313
### Added
1414

15+
- `code42 alert-rules` commands:
16+
- `add-user` with parameters `--rule-id` and `--username`.
17+
- `remove-user` that takes a rule ID and optionally `--username`.
18+
- `list`.
19+
- `show` takes a rule ID.
20+
- `bulk` with subcommands:
21+
- `add`: that takes a csv file with rule IDs and usernames.
22+
- `generate-template`: that creates the file template. And parameters:
23+
- `cmd`: with options `add` and `remove`.
24+
- `path`
25+
- `remove`: that takes a csv file with rule IDs and usernames.
1526
- Success messages for `profile delete` and `profile update`.
1627
- Additional information in the error log file:
1728
- The full command path for the command that errored.

src/code42cli/args.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import inspect
33

44

5-
PROFILE_HELP = u"The name of the Code42 profile use when executing this command."
5+
PROFILE_HELP = u"The name of the Code42 profile to use when executing this command."
66
SDK_ARG_NAME = u"sdk"
77
PROFILE_ARG_NAME = u"profile"
88

@@ -18,6 +18,7 @@ def __init__(self, *args, **kwargs):
1818
u"help": kwargs.get(u"help"),
1919
u"options_list": list(args),
2020
u"nargs": kwargs.get(u"nargs"),
21+
u"required": kwargs.get(u"required"),
2122
}
2223

2324
@property
@@ -36,6 +37,9 @@ def add_short_option_name(self, short_name):
3637
def as_multi_val_param(self, nargs=u"+"):
3738
self._settings[u"nargs"] = nargs
3839

40+
def set_required(self, required=False):
41+
self._settings[u"required"] = required
42+
3943

4044
class ArgConfigCollection(object):
4145
def __init__(self):

src/code42cli/bulk.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
from code42cli.args import SDK_ARG_NAME, PROFILE_ARG_NAME
99

1010

11+
class BulkCommandType(object):
12+
ADD = u"add"
13+
REMOVE = u"remove"
14+
15+
def __iter__(self):
16+
return iter([self.ADD, self.REMOVE])
17+
18+
1119
def generate_template(handler, path=None):
1220
"""Looks at the parameter names of `handler` and creates a file with the same column names. If
1321
`handler` only has one parameter that is not `sdk` or `profile`, it will create a blank file.

src/code42cli/cmds/alerts/__init__.py

Whitespace-only changes.

src/code42cli/cmds/alerts/rules/__init__.py

Whitespace-only changes.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
from code42cli.commands import Command
2+
from code42cli.bulk import generate_template, BulkCommandType
3+
from code42cli.cmds.alerts.rules.user_rule import (
4+
add_user,
5+
remove_user,
6+
get_rules,
7+
add_bulk_users,
8+
remove_bulk_users,
9+
show_rules,
10+
)
11+
12+
13+
def _customize_add_arguments(argument_collection):
14+
rule_id = argument_collection.arg_configs[u"rule_id"]
15+
rule_id.set_help(u"Observer ID of the rule to be updated. Required.")
16+
rule_id.set_required(True)
17+
username = argument_collection.arg_configs[u"username"]
18+
username.set_help(u"The username of the user to add to the alert rule. Required.")
19+
username.set_required(True)
20+
21+
22+
def _customize_remove_arguments(argument_collection):
23+
rule_id = argument_collection.arg_configs[u"rule_id"]
24+
rule_id.set_help(u"Observer ID of the rule to be updated.")
25+
username = argument_collection.arg_configs[u"username"]
26+
username.set_help(u"The username of the user to remove from the alert rule.")
27+
28+
29+
def _customize_list_arguments(argument_collection):
30+
rule_id = argument_collection.arg_configs[u"rule_id"]
31+
rule_id.set_help(u"Observer ID of the rule.")
32+
33+
34+
def _customize_bulk_arguments(argument_collection):
35+
file_name = argument_collection.arg_configs[u"file_name"]
36+
file_name.set_help(
37+
u"The path to the csv file with columns 'rule_id,user_id' "
38+
u"for bulk adding users to the alert rule."
39+
)
40+
41+
42+
def _generate_template_file(cmd, path=None):
43+
"""Generates a template file a user would need to fill-in for bulk operating.
44+
45+
Args:
46+
cmd (str or unicode): An option from the `BulkCommandType` enum specifying which type of file to
47+
generate.
48+
path (str or unicode, optional): A path to put the file after it's generated. If None, will use
49+
the current working directory. Defaults to None.
50+
"""
51+
handler = None
52+
if cmd == BulkCommandType.ADD:
53+
handler = add_user
54+
elif cmd == BulkCommandType.REMOVE:
55+
handler = remove_user
56+
57+
generate_template(handler, path)
58+
59+
60+
def _load_bulk_generate_template_description(argument_collection):
61+
cmd_type = argument_collection.arg_configs[u"cmd"]
62+
cmd_type.set_help(u"The type of command the template with be used for.")
63+
cmd_type.set_choices(BulkCommandType())
64+
65+
66+
class AlertRulesBulkCommands(object):
67+
@staticmethod
68+
def load_commands():
69+
usage_prefix = u"code42 alert-rules bulk"
70+
71+
generate_template_cmd = Command(
72+
u"generate-template",
73+
u"Generate the necessary csv template needed for bulk adding users.",
74+
u"{} generate-template <cmd> <optional args>".format(usage_prefix),
75+
handler=_generate_template_file,
76+
arg_customizer=_load_bulk_generate_template_description,
77+
)
78+
79+
bulk_add = Command(
80+
u"add",
81+
u"Update alert rule criteria to add users and all their aliases. "
82+
u"CSV file format: rule_id,username",
83+
u"{} add <filename>".format(usage_prefix),
84+
handler=add_bulk_users,
85+
arg_customizer=_customize_bulk_arguments,
86+
)
87+
88+
bulk_remove = Command(
89+
u"remove",
90+
u"Update alert rule criteria to remove users and all their aliases. "
91+
u"CSV file format: rule_id,username",
92+
u"{} remove <filename>".format(usage_prefix),
93+
handler=remove_bulk_users,
94+
arg_customizer=_customize_bulk_arguments,
95+
)
96+
97+
return [generate_template_cmd, bulk_add, bulk_remove]
98+
99+
100+
class AlertRulesCommands(object):
101+
@staticmethod
102+
def load_subcommands():
103+
usage_prefix = u"code42 alert-rules"
104+
105+
add = Command(
106+
u"add-user",
107+
u"Update alert rule criteria to monitor user aliases against the given username.",
108+
u"{} add-user --rule-id <id> --username <username>".format(usage_prefix),
109+
handler=add_user,
110+
arg_customizer=_customize_add_arguments,
111+
)
112+
113+
remove = Command(
114+
u"remove-user",
115+
u"Update alert rule criteria to remove a user and all their aliases.",
116+
u"{} remove-user <rule-id> --username <username>".format(usage_prefix),
117+
handler=remove_user,
118+
arg_customizer=_customize_remove_arguments,
119+
)
120+
121+
list_rules = Command(
122+
u"list",
123+
u"Fetch existing alert rules.",
124+
u"{} list".format(usage_prefix),
125+
handler=get_rules,
126+
)
127+
128+
show = Command(
129+
u"show",
130+
u"Fetch configured alert-rules against the rule ID.",
131+
u"{} show <rule-id>".format(usage_prefix),
132+
handler=show_rules,
133+
arg_customizer=_customize_list_arguments,
134+
)
135+
136+
bulk = Command(
137+
u"bulk",
138+
u"Tools for executing bulk commands.",
139+
subcommand_loader=AlertRulesBulkCommands.load_commands,
140+
)
141+
142+
return [add, remove, list_rules, show, bulk]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class AlertRuleTypes(object):
2+
EXFILTRATION = u"FED_ENDPOINT_EXFILTRATION"
3+
CLOUD_SHARE = u"FED_CLOUD_SHARE_PERMISSIONS"
4+
FILE_TYPE_MISMATCH = u"FED_FILE_TYPE_MISMATCH"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from py42.util import format_json
2+
3+
from code42cli.util import format_to_table, find_format_width
4+
from code42cli.bulk import run_bulk_process, CSVReader
5+
from code42cli.logger import get_main_cli_logger
6+
from code42cli.cmds.detectionlists import get_user_id
7+
from code42cli.cmds.alerts.rules.enums import AlertRuleTypes
8+
9+
10+
_HEADER_KEYS_MAP = {
11+
u"observerRuleId": u"RuleId",
12+
u"name": u"Name",
13+
u"severity": u"Severity",
14+
u"type": u"Type",
15+
u"ruleSource": u"Source",
16+
u"isEnabled": u"Enabled",
17+
}
18+
19+
20+
def add_user(sdk, profile, rule_id=None, username=None):
21+
user_id = get_user_id(sdk, username)
22+
sdk.alerts.rules.add_user(rule_id, user_id)
23+
24+
25+
def remove_user(sdk, profile, rule_id, username=None):
26+
if username:
27+
user_id = get_user_id(sdk, username)
28+
sdk.alerts.rules.remove_user(rule_id, user_id)
29+
else:
30+
sdk.alerts.rules.remove_all_users(rule_id)
31+
32+
33+
def _get_rules_metadata(sdk, rule_id=None):
34+
rules_generator = sdk.alerts.rules.get_all()
35+
selected_rules = [rule for rules in rules_generator for rule in rules[u"ruleMetadata"]]
36+
if rule_id:
37+
selected_rules = [rule for rule in selected_rules if rule[u"observerRuleId"] == rule_id]
38+
return selected_rules
39+
40+
41+
def get_rules(sdk, profile):
42+
selected_rules = _get_rules_metadata(sdk)
43+
rows, column_size = find_format_width(selected_rules, _HEADER_KEYS_MAP)
44+
format_to_table(rows, column_size)
45+
46+
47+
def add_bulk_users(sdk, profile, file_name):
48+
run_bulk_process(
49+
file_name, lambda rule_id, username: add_user(sdk, profile, rule_id, username), CSVReader()
50+
)
51+
52+
53+
def remove_bulk_users(sdk, profile, file_name):
54+
run_bulk_process(
55+
file_name,
56+
lambda rule_id, username: remove_user(sdk, profile, rule_id, username),
57+
CSVReader(),
58+
)
59+
60+
61+
def show_rules(sdk, profile, rule_id):
62+
selected_rule = _get_rules_metadata(sdk, rule_id)
63+
rule_detail = None
64+
if len(selected_rule):
65+
rule_type = selected_rule[0][u"type"]
66+
if rule_type == AlertRuleTypes.EXFILTRATION:
67+
rule_detail = sdk.alerts.rules.exfiltration.get(rule_id)
68+
elif rule_type == AlertRuleTypes.CLOUD_SHARE:
69+
rule_detail = sdk.alerts.rules.cloudshare.get(rule_id)
70+
elif rule_type == AlertRuleTypes.FILE_TYPE_MISMATCH:
71+
rule_detail = sdk.alerts.rules.filetypemismatch.get(rule_id)
72+
if rule_detail:
73+
logger = get_main_cli_logger()
74+
logger.print_info(format_json(rule_detail.text))

src/code42cli/cmds/detectionlists/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@
44
from code42cli.cmds.detectionlists.commands import DetectionListCommandFactory
55
from code42cli.bulk import generate_template, run_bulk_process, CSVReader, FlatFileReader
66
from code42cli.logger import get_main_cli_logger
7-
from code42cli.cmds.detectionlists.enums import (
8-
BulkCommandType,
9-
DetectionLists,
10-
DetectionListUserKeys,
11-
RiskTags,
12-
)
7+
from code42cli.bulk import BulkCommandType
8+
from code42cli.cmds.detectionlists.enums import DetectionLists, DetectionListUserKeys, RiskTags
139

1410

1511
class UserAlreadyAddedError(Exception):

src/code42cli/cmds/detectionlists/commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from code42cli.cmds.detectionlists.enums import BulkCommandType
1+
from code42cli.bulk import BulkCommandType
22
from code42cli.commands import Command
33

44

0 commit comments

Comments
 (0)