Skip to content

Commit 486ea39

Browse files
author
Juliya Smith
authored
Feature/ssl sendto (#190)
SSL Send to option
1 parent de6fb41 commit 486ea39

34 files changed

+3024
-2088
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
The intended audience of this file is for py42 consumers -- as such, changes that don't affect
99
how a consumer would use the library (e.g. adding unit tests, updating documentation, etc) are not captured here.
1010

11+
## Unreleased
12+
13+
### Added
14+
15+
- New choice `TLS-TCP` for `--protocol` option used by `send-to` commands:
16+
- `code42 security-data send-to`
17+
- `code42 alerts send-to`
18+
- `code42 audit-logs send-to`
19+
for more securely transporting data.
20+
21+
- `--certs` option for `send-to` commands when using `--protocol TLS-TCP`.
22+
1123
## 1.2.0 - 2021-01-25
1224

1325
### Added

src/code42cli/click_ext/groups.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from code42cli.errors import LoggedCLIError
1515
from code42cli.errors import UserDoesNotExistError
1616
from code42cli.logger import get_main_cli_logger
17+
from code42cli.logger.handlers import SyslogServerNetworkConnectionError
1718

1819
_DIFFLIB_CUT_OFF = 0.6
1920

@@ -57,6 +58,7 @@ def invoke(self, ctx):
5758
Py42UserNotOnListError,
5859
Py42InvalidRuleOperationError,
5960
Py42LegalHoldNotFoundOrPermissionDeniedError,
61+
SyslogServerNetworkConnectionError,
6062
) as err:
6163
self.logger.log_error(err)
6264
raise Code42CLIError(str(err))

src/code42cli/cmds/alerts.py

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from _collections import OrderedDict
2-
31
import click
42
import py42.sdk.queries.alerts.filters as f
53
from c42eventextractor.extractors import AlertExtractor
@@ -12,26 +10,18 @@
1210
import code42cli.cmds.search.options as searchopt
1311
import code42cli.errors as errors
1412
import code42cli.options as opt
13+
from code42cli.cmds.search import SendToCommand
1514
from code42cli.cmds.search.cursor_store import AlertCursorStore
1615
from code42cli.cmds.search.extraction import handle_no_events
16+
from code42cli.cmds.search.options import server_options
1717
from code42cli.date_helper import convert_datetime_to_timestamp
1818
from code42cli.date_helper import limit_date_range
19-
from code42cli.logger import get_logger_for_server
2019
from code42cli.options import format_option
21-
from code42cli.options import server_options
2220
from code42cli.output_formats import JsonOutputFormat
2321
from code42cli.output_formats import OutputFormatter
2422

25-
ALERTS_KEYWORD = "alerts"
26-
SEARCH_DEFAULT_HEADER = OrderedDict()
27-
SEARCH_DEFAULT_HEADER["name"] = "RuleName"
28-
SEARCH_DEFAULT_HEADER["actor"] = "Username"
29-
SEARCH_DEFAULT_HEADER["createdAt"] = "ObservedDate"
30-
SEARCH_DEFAULT_HEADER["state"] = "Status"
31-
SEARCH_DEFAULT_HEADER["severity"] = "Severity"
32-
SEARCH_DEFAULT_HEADER["description"] = "Description"
33-
3423

24+
ALERTS_KEYWORD = "alerts"
3525
begin = opt.begin_option(
3626
ALERTS_KEYWORD,
3727
callback=lambda ctx, param, arg: convert_datetime_to_timestamp(
@@ -41,16 +31,6 @@
4131
end = opt.end_option(ALERTS_KEYWORD)
4232
checkpoint = opt.checkpoint_option(ALERTS_KEYWORD)
4333
advanced_query = searchopt.advanced_query_option(ALERTS_KEYWORD)
44-
45-
46-
def search_options(f):
47-
f = checkpoint(f)
48-
f = advanced_query(f)
49-
f = end(f)
50-
f = begin(f)
51-
return f
52-
53-
5434
severity_option = click.option(
5535
"--severity",
5636
multiple=True,
@@ -147,16 +127,34 @@ def search_options(f):
147127
callback=searchopt.contains_filter(f.Description),
148128
help="Filter alerts by description. Does fuzzy search by default.",
149129
)
150-
151130
send_to_format_options = click.option(
152131
"-f",
153132
"--format",
154133
type=click.Choice(JsonOutputFormat(), case_sensitive=False),
155134
help="The output format of the result. Defaults to json format.",
156-
default=JsonOutputFormat.JSON,
135+
default=JsonOutputFormat.RAW,
157136
)
158137

159138

139+
def _get_search_default_header():
140+
return {
141+
"name": "RuleName",
142+
"actor": "Username",
143+
"createdAt": "ObservedDate",
144+
"state": "Status",
145+
"severity": "Severity",
146+
"description": "Description",
147+
}
148+
149+
150+
def search_options(f):
151+
f = checkpoint(f)
152+
f = advanced_query(f)
153+
f = end(f)
154+
f = begin(f)
155+
return f
156+
157+
160158
def alert_options(f):
161159
f = actor_option(f)
162160
f = actor_contains_option(f)
@@ -231,7 +229,7 @@ def search(
231229
):
232230
"""Search for alerts."""
233231
output_header = ext.try_get_default_header(
234-
include_all, SEARCH_DEFAULT_HEADER, format
232+
include_all, _get_search_default_header(), format
235233
)
236234
formatter = OutputFormatter(format, output_header)
237235
cursor = _get_alert_cursor_store(cli_state.profile.name) if use_checkpoint else None
@@ -247,7 +245,7 @@ def search(
247245
handle_no_events(not handlers.TOTAL_EVENTS and not errors.ERRORED)
248246

249247

250-
@alerts.command()
248+
@alerts.command(cls=SendToCommand)
251249
@alert_options
252250
@search_options
253251
@click.option(
@@ -262,31 +260,23 @@ def search(
262260
help="Display simple properties of the primary level of the nested response.",
263261
)
264262
@send_to_format_options
265-
def send_to(
266-
cli_state,
267-
format,
268-
hostname,
269-
protocol,
270-
begin,
271-
end,
272-
advanced_query,
273-
use_checkpoint,
274-
or_query,
275-
**kwargs
276-
):
263+
def send_to(cli_state, begin, end, advanced_query, use_checkpoint, or_query, **kwargs):
277264
"""Send alerts to the given server address.
278265
279266
HOSTNAME format: address:port where port is optional and defaults to 514.
280267
"""
281-
logger = get_logger_for_server(hostname, protocol, format)
282-
cursor = _get_alert_cursor_store(cli_state.profile.name) if use_checkpoint else None
268+
cursor = _get_cursor(cli_state, use_checkpoint)
283269
handlers = ext.create_send_to_handlers(
284-
cli_state.sdk, AlertExtractor, cursor, use_checkpoint, logger,
270+
cli_state.sdk, AlertExtractor, cursor, use_checkpoint, cli_state.logger,
285271
)
286272
_call_extractor(cli_state, handlers, begin, end, or_query, advanced_query, **kwargs)
287273
handle_no_events(not handlers.TOTAL_EVENTS and not errors.ERRORED)
288274

289275

276+
def _get_cursor(state, use_checkpoint):
277+
return _get_alert_cursor_store(state.profile.name) if use_checkpoint else None
278+
279+
290280
def _get_alert_extractor(sdk, handlers):
291281
return AlertExtractor(sdk, handlers)
292282

src/code42cli/cmds/auditlogs.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
from collections import OrderedDict
21
from datetime import datetime
32
from datetime import timezone
43

54
import click
65

76
import code42cli.options as opt
87
from code42cli.click_ext.groups import OrderedGroup
8+
from code42cli.cmds.search import SendToCommand
99
from code42cli.cmds.search.cursor_store import AuditLogCursorStore
10+
from code42cli.cmds.search.options import server_options
1011
from code42cli.date_helper import convert_datetime_to_timestamp
11-
from code42cli.logger import get_logger_for_server
1212
from code42cli.options import checkpoint_option
1313
from code42cli.options import format_option
1414
from code42cli.options import sdk_options
15-
from code42cli.options import server_options
1615
from code42cli.output_formats import OutputFormatter
1716
from code42cli.util import hash_event
1817
from code42cli.util import warn_interrupt
@@ -21,13 +20,17 @@
2120
AUDIT_LOGS_KEYWORD = "audit-logs"
2221
AUDIT_LOG_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
2322

24-
AUDIT_LOGS_DEFAULT_HEADER = OrderedDict()
25-
AUDIT_LOGS_DEFAULT_HEADER["timestamp"] = "Timestamp"
26-
AUDIT_LOGS_DEFAULT_HEADER["type$"] = "Type"
27-
AUDIT_LOGS_DEFAULT_HEADER["actorName"] = "ActorName"
28-
AUDIT_LOGS_DEFAULT_HEADER["actorIpAddress"] = "ActorIpAddress"
29-
AUDIT_LOGS_DEFAULT_HEADER["userName"] = "AffectedUser"
30-
AUDIT_LOGS_DEFAULT_HEADER["userId"] = "AffectedUserUID"
23+
24+
def _get_audit_logs_default_header():
25+
return {
26+
"timestamp": "Timestamp",
27+
"type$": "Type",
28+
"actorName": "ActorName",
29+
"actorIpAddress": "ActorIpAddress",
30+
"userName": "AffectedUser",
31+
"userId": "AffectedUserUID",
32+
}
33+
3134

3235
begin_option = opt.begin_option(
3336
AUDIT_LOGS_KEYWORD,
@@ -123,7 +126,7 @@ def search(
123126
use_checkpoint,
124127
):
125128
"""Search audit logs."""
126-
formatter = OutputFormatter(format, AUDIT_LOGS_DEFAULT_HEADER)
129+
formatter = OutputFormatter(format, _get_audit_logs_default_header())
127130
cursor = _get_audit_log_cursor_store(state.profile.name)
128131
if use_checkpoint:
129132
checkpoint_name = use_checkpoint
@@ -158,15 +161,13 @@ def search(
158161
formatter.echo_formatted_list(events)
159162

160163

161-
@audit_logs.command()
164+
@audit_logs.command(cls=SendToCommand)
162165
@filter_options
163166
@checkpoint_option(AUDIT_LOGS_KEYWORD)
164167
@server_options
165168
@sdk_options()
166169
def send_to(
167170
state,
168-
hostname,
169-
protocol,
170171
begin,
171172
end,
172173
event_type,
@@ -176,12 +177,12 @@ def send_to(
176177
affected_user_id,
177178
affected_username,
178179
use_checkpoint,
180+
**kwargs,
179181
):
180182
"""Send audit logs to the given server address in JSON format.
181183
182184
HOSTNAME format: address:port where port is optional and defaults to 514.
183185
"""
184-
logger = get_logger_for_server(hostname, protocol, "RAW-JSON")
185186
cursor = _get_audit_log_cursor_store(state.profile.name)
186187
if use_checkpoint:
187188
checkpoint_name = use_checkpoint
@@ -210,7 +211,7 @@ def send_to(
210211
with warn_interrupt():
211212
event = None
212213
for event in events:
213-
logger.info(event)
214+
state.logger.info(event)
214215
if event is None: # generator was empty
215216
click.echo("No results found.")
216217

src/code42cli/cmds/detectionlists/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ def update_user(sdk, username, cloud_alias=None, risk_tag=None, notes=None):
4848
4949
Args:
5050
sdk (py42.sdk.SDKClient): py42 sdk.
51-
username (str or unicode): The username of the user to update.
52-
cloud_alias (str or unicode): A cloud alias to add to the user.
53-
risk_tag (iter[str or unicode]): A list of risk tags associated with user.
54-
notes (str or unicode): Notes about the user.
51+
username (str): The username of the user to update.
52+
cloud_alias (str): A cloud alias to add to the user.
53+
risk_tag (iter[str]): A list of risk tags associated with user.
54+
notes (str): Notes about the user.
5555
"""
5656
user_id = get_user_id(sdk, username)
5757
_update_cloud_alias(sdk, user_id, cloud_alias)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import click
2+
3+
from code42cli.errors import Code42CLIError
4+
from code42cli.logger import get_logger_for_server
5+
from code42cli.output_formats import OutputFormat
6+
7+
8+
def _try_get_logger_for_server(hostname, protocol, output_format, certs):
9+
try:
10+
return get_logger_for_server(hostname, protocol, output_format, certs)
11+
except Exception as err:
12+
raise Code42CLIError(
13+
"Unable to connect to {}. Failed with error: {}.".format(hostname, str(err))
14+
)
15+
16+
17+
class SendToCommand(click.Command):
18+
def invoke(self, ctx):
19+
certs = ctx.params.get("certs")
20+
hostname = ctx.params.get("hostname")
21+
protocol = ctx.params.get("protocol")
22+
output_format = ctx.params.get("format", OutputFormat.RAW)
23+
ignore_cert_validation = ctx.params.get("ignore_cert_validation")
24+
if ignore_cert_validation:
25+
certs = "ignore"
26+
27+
ctx.obj.logger = _try_get_logger_for_server(
28+
hostname, protocol, output_format, certs
29+
)
30+
return super().invoke(ctx)

src/code42cli/cmds/search/enums.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/code42cli/cmds/search/options.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from code42cli.click_ext.options import incompatible_with
99
from code42cli.click_ext.types import FileOrString
10+
from code42cli.logger.enums import ServerProtocol
11+
from code42cli.output_formats import SendToFileEventsOutputFormat
1012

1113

1214
def is_in_filter(filter_cls):
@@ -140,3 +142,39 @@ def advanced_query_option(term, **kwargs):
140142
)
141143
defaults.update(kwargs)
142144
return click.option("--advanced-query", **defaults)
145+
146+
147+
def server_options(f):
148+
hostname_arg = click.argument("hostname")
149+
protocol_option = click.option(
150+
"-p",
151+
"--protocol",
152+
type=click.Choice(ServerProtocol(), case_sensitive=False),
153+
default=ServerProtocol.UDP,
154+
help="Protocol used to send logs to server. "
155+
"Use TLS for additional security. Defaults to UDP.",
156+
)
157+
certs_option = click.option(
158+
"--certs", type=str, help="A CA certificates-chain file for the TLS protocol."
159+
)
160+
ignore_cert_validation = click.option(
161+
"--ignore-cert-validation",
162+
help="Set to skip CA certificate validation. "
163+
"Incompatible with the 'certs' option.",
164+
is_flag=True,
165+
cls=incompatible_with(["certs"]),
166+
)
167+
f = hostname_arg(f)
168+
f = protocol_option(f)
169+
f = certs_option(f)
170+
f = ignore_cert_validation(f)
171+
return f
172+
173+
174+
send_to_format_options = click.option(
175+
"-f",
176+
"--format",
177+
type=click.Choice(SendToFileEventsOutputFormat(), case_sensitive=False),
178+
help="The output format of the result. Defaults to RAW-JSON format.",
179+
default=SendToFileEventsOutputFormat.RAW,
180+
)

0 commit comments

Comments
 (0)