Skip to content

Commit 840aeae

Browse files
author
Juliya Smith
authored
Handle begin date complications (#11)
1 parent 38308d7 commit 840aeae

20 files changed

+356
-123
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ 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+
### Fixed
14+
15+
- Fixed bug where port attached to `securitydata send-to` command was not properly applied.
16+
17+
### Changed
18+
19+
- Begin dates are no longer required for subsequent interactive `securitydata` commands.
20+
- When provided, begin dates are now ignored on subsequent interactive `securitydata` commands.
21+
1122
## 0.3.0 - 2020-03-04
1223

1324
### Added

README.md

+21-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
Use the `code42` command to interact with your Code42 environment.
44
`code42 securitydata` is a CLI tool for extracting AED events.
5-
Additionally, `code42 securitydata` can record a checkpoint so that you only get events you have not previously gotten.
5+
Additionally, you can choose to only get events that Code42 previously did not observe since you last recorded a checkpoint
6+
(provided you do not change your query).
67

78
## Requirements
89

@@ -46,19 +47,34 @@ Next, you can query for events and send them to three possible destination types
4647

4748
To print events to stdout, do:
4849
```bash
49-
code42 securitydata print
50+
code42 securitydata print -b 2020-02-02
5051
```
5152

53+
Note that `-b` or `--begin` is usually required.
54+
To specify a time, do:
55+
56+
```bash
57+
code42 securitydata print -b 2020-02-02 12:51
58+
```
59+
Begin date will be ignored if provided on subsequent queries using `-i`.
60+
5261
To write events to a file, do:
5362
```bash
54-
code42 securitydata write-to filename.txt
63+
code42 securitydata write-to filename.txt -b 2020-02-02
5564
```
5665

5766
To send events to a server, do:
5867
```bash
59-
code42 securitydata send-to https://syslog.company.com -p TCP
68+
code42 securitydata send-to syslog.company.com -p TCP -b 2020-02-02
6069
```
6170

71+
To only get events that Code42 previously did not observe since you last recorded a checkpoint, use the `-i` flag.
72+
```bash
73+
code42 securitydata send-to syslog.company.com -i
74+
```
75+
This is only guaranteed if you did not change your query.
76+
77+
6278
Each destination-type subcommand shares query parameters
6379
* `-t` (exposure types)
6480
* `-b` (begin date)
@@ -75,8 +91,7 @@ Each destination-type subcommand shares query parameters
7591
* `--include-non-exposure` (does not work with `-t`)
7692
* `--advanced-query` (raw JSON query)
7793

78-
Note that you cannot use other query parameters if you use `--advanced-query`.
79-
94+
You cannot use other query parameters if you use `--advanced-query`.
8095
To learn more about acceptable arguments, add the `-h` flag to `code42` or and of the destination-type subcommands.
8196

8297

src/code42cli/securitydata/cursor_store.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
_INSERTION_TIMESTAMP_FIELD_NAME = u"insertionTimestamp"
88

99

10-
class SecurityEventCursorStore(object):
10+
class BaseCursorStore(object):
1111
_PRIMARY_KEY_COLUMN_NAME = u"cursor_id"
1212

1313
def __init__(self, db_table_name, db_file_path=None):
@@ -52,11 +52,11 @@ def _is_empty(self):
5252
return int(query_result[0]) <= 0
5353

5454

55-
class AEDCursorStore(SecurityEventCursorStore):
55+
class FileEventCursorStore(BaseCursorStore):
5656
_PRIMARY_KEY = 1
5757

5858
def __init__(self, db_file_path=None):
59-
super(AEDCursorStore, self).__init__(u"aed_checkpoint", db_file_path)
59+
super(FileEventCursorStore, self).__init__(u"aed_checkpoint", db_file_path)
6060
if self._is_empty():
6161
self._init_table()
6262

src/code42cli/securitydata/date_helper.py

+25-18
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,45 @@
33
from c42eventextractor.common import convert_datetime_to_timestamp
44
from py42.sdk.file_event_query.event_query import EventTimestamp
55

6-
_DEFAULT_LOOK_BACK_DAYS = 60
76
_MAX_LOOK_BACK_DAYS = 90
87
_FORMAT_VALUE_ERROR_MESSAGE = u"input must be a date in YYYY-MM-DD or YYYY-MM-DD HH:MM:SS format."
98

109

11-
def create_event_timestamp_range(begin_date, end_date=None):
12-
"""Creates a `py42.sdk.file_event_query.event_query.EventTimestamp.in_range` filter
13-
using the provided dates. If begin_date is None, it uses a date that is 60 days back.
14-
If end_date is None, it uses the current UTC time.
10+
def create_event_timestamp_filter(begin_date=None, end_date=None):
11+
"""Creates a `py42.sdk.file_event_query.event_query.EventTimestamp` filter using the given dates.
12+
Returns None if not given a begin_date or an end_date.
1513
1614
Args:
17-
begin_date: The begin date for the range. If None, defaults to 60 days back from the current UTC time.
18-
end_date: The end date for the range. If None, defaults to the current time.
15+
begin_date: The begin date for the range.
16+
end_date: The end date for the range.
1917
2018
"""
2119
end_date = _get_end_date_with_eod_time_if_needed(end_date)
20+
if begin_date and end_date:
21+
return _create_in_range_filter(begin_date, end_date)
22+
elif begin_date and not end_date:
23+
return _create_on_or_after_filter(begin_date)
24+
elif end_date and not begin_date:
25+
return _create_on_or_before_filter(end_date)
26+
27+
28+
def _create_in_range_filter(begin_date, end_date):
2229
min_timestamp = _parse_min_timestamp(begin_date)
23-
max_timestamp = _parse_max_timestamp(end_date)
30+
max_timestamp = _parse_timestamp(end_date)
2431
_verify_timestamp_order(min_timestamp, max_timestamp)
2532
return EventTimestamp.in_range(min_timestamp, max_timestamp)
2633

2734

35+
def _create_on_or_after_filter(begin_date):
36+
min_timestamp = _parse_min_timestamp(begin_date)
37+
return EventTimestamp.on_or_after(min_timestamp)
38+
39+
40+
def _create_on_or_before_filter(end_date):
41+
max_timestamp = _parse_timestamp(end_date)
42+
return EventTimestamp.on_or_before(max_timestamp)
43+
44+
2845
def _get_end_date_with_eod_time_if_needed(end_date):
2946
if end_date and len(end_date) == 1:
3047
return end_date[0], "23:59:59"
@@ -40,12 +57,6 @@ def _parse_min_timestamp(begin_date_str):
4057
return min_timestamp
4158

4259

43-
def _parse_max_timestamp(end_date_str):
44-
if not end_date_str:
45-
return _get_default_max_timestamp()
46-
return _parse_timestamp(end_date_str)
47-
48-
4960
def _verify_timestamp_order(min_timestamp, max_timestamp):
5061
if min_timestamp is None or max_timestamp is None:
5162
return
@@ -74,7 +85,3 @@ def _join_date_tuple(date_tuple):
7485
else:
7586
raise ValueError(_FORMAT_VALUE_ERROR_MESSAGE)
7687
return date_str
77-
78-
79-
def _get_default_max_timestamp():
80-
return convert_datetime_to_timestamp(datetime.utcnow())

src/code42cli/securitydata/extraction.py

+41-21
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from code42cli.securitydata import date_helper as date_helper
1919
from code42cli.securitydata.arguments.main import IS_INCREMENTAL_KEY
2020
from code42cli.securitydata.arguments.search import SearchArguments
21-
from code42cli.securitydata.cursor_store import AEDCursorStore
21+
from code42cli.securitydata.cursor_store import FileEventCursorStore
2222
from code42cli.securitydata.logger_factory import get_error_logger
2323
from code42cli.securitydata.options import ExposureType as ExposureTypeOptions
2424
from code42cli.util import print_error, print_bold, is_interactive
@@ -37,15 +37,21 @@ def extract(output_logger, args):
3737
args:
3838
Command line args used to build up file event query filters.
3939
"""
40-
handlers = _create_event_handlers(output_logger, args.is_incremental)
40+
store = _create_cursor_store(args)
41+
handlers = _create_event_handlers(output_logger, store)
4142
profile = get_profile()
4243
sdk = _get_sdk(profile, args.is_debug_mode)
4344
extractor = FileEventExtractor(sdk, handlers)
44-
_call_extract(extractor, args)
45+
_call_extract(extractor, store, args)
4546
_handle_result()
4647

4748

48-
def _create_event_handlers(output_logger, is_incremental):
49+
def _create_cursor_store(args):
50+
if args.is_incremental:
51+
return FileEventCursorStore()
52+
53+
54+
def _create_event_handlers(output_logger, cursor_store):
4955
handlers = FileEventHandlers()
5056
error_logger = get_error_logger()
5157

@@ -56,10 +62,9 @@ def handle_error(exception):
5662

5763
handlers.handle_error = handle_error
5864

59-
if is_incremental:
60-
store = AEDCursorStore()
61-
handlers.record_cursor_position = store.replace_stored_insertion_timestamp
62-
handlers.get_cursor_position = store.get_stored_insertion_timestamp
65+
if cursor_store:
66+
handlers.record_cursor_position = cursor_store.replace_stored_insertion_timestamp
67+
handlers.get_cursor_position = cursor_store.get_stored_insertion_timestamp
6368

6469
def handle_response(response):
6570
response_dict = json.loads(response.text)
@@ -86,9 +91,9 @@ def _get_sdk(profile, is_debug_mode):
8691
exit(1)
8792

8893

89-
def _call_extract(extractor, args):
94+
def _call_extract(extractor, cursor_store, args):
9095
if not _determine_if_advanced_query(args):
91-
_verify_begin_date(args.begin_date)
96+
_verify_begin_date_requirements(args, cursor_store)
9297
_verify_exposure_types(args.exposure_types)
9398
filters = _create_filters(args)
9499
extractor.extract(*filters)
@@ -116,23 +121,26 @@ def _verify_compatibility_with_advanced_query(key, val):
116121
return True
117122

118123

119-
def _get_event_timestamp_filter(args):
120-
try:
121-
return date_helper.create_event_timestamp_range(args.begin_date, args.end_date)
122-
except ValueError as ex:
123-
print_error(str(ex))
124-
exit(1)
125-
126-
127-
def _verify_begin_date(begin_date):
128-
if not begin_date:
124+
def _verify_begin_date_requirements(args, cursor_store):
125+
if _begin_date_is_required(args, cursor_store) and not args.begin_date:
129126
print_error(u"'begin date' is required.")
130127
print(u"")
131128
print_bold(u"Try using '-b' or '--begin'. Use `-h` for more info.")
132129
print(u"")
133130
exit(1)
134131

135132

133+
def _begin_date_is_required(args, cursor_store):
134+
if not args.is_incremental:
135+
return True
136+
required = cursor_store is not None and cursor_store.get_stored_insertion_timestamp() is None
137+
138+
# Ignore begin date when is incremental mode, it is not required, and it was passed an argument.
139+
if not required and args.begin_date:
140+
args.begin_date = None
141+
return required
142+
143+
136144
def _verify_exposure_types(exposure_types):
137145
if exposure_types is None:
138146
return
@@ -143,13 +151,25 @@ def _verify_exposure_types(exposure_types):
143151
exit(1)
144152

145153

154+
def _get_event_timestamp_filter(args):
155+
try:
156+
return date_helper.create_event_timestamp_filter(args.begin_date, args.end_date)
157+
except ValueError as ex:
158+
print_error(str(ex))
159+
exit(1)
160+
161+
146162
def _handle_result():
147163
if is_interactive() and _EXCEPTIONS_OCCURRED:
148164
print_error(u"View exceptions that occurred at [HOME]/.code42cli/log/code42_errors.")
149165

150166

151167
def _create_filters(args):
152-
filters = [_get_event_timestamp_filter(args)]
168+
filters = []
169+
event_timestamp_filter = _get_event_timestamp_filter(args)
170+
if event_timestamp_filter:
171+
filters.append(event_timestamp_filter)
172+
153173
not args.c42username or filters.append(DeviceUsername.eq(args.c42username))
154174
not args.actor or filters.append(Actor.eq(args.actor))
155175
not args.md5 or filters.append(MD5.eq(args.md5))

src/code42cli/securitydata/logger_factory.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from code42cli.compat import str
1414
from code42cli.securitydata.options import OutputFormat
15-
from code42cli.util import get_user_project_path, print_error
15+
from code42cli.util import get_user_project_path, print_error, get_url_parts
1616

1717
_logger_deps_lock = Lock()
1818

@@ -56,7 +56,7 @@ def get_logger_for_server(hostname, protocol, output_format):
5656
"""Gets the logger that sends logs to a server for the given format.
5757
5858
Args:
59-
hostname: The hostname of the server.
59+
hostname: The hostname of the server. It may include the port.
6060
protocol: The transfer protocol for sending logs.
6161
output_format: CEF, JSON, or RAW_JSON. Each type results in a different logger instance.
6262
"""
@@ -66,7 +66,11 @@ def get_logger_for_server(hostname, protocol, output_format):
6666

6767
with _logger_deps_lock:
6868
if not _logger_has_handlers(logger):
69-
handler = NoPrioritySysLogHandlerWrapper(hostname, protocol=protocol).handler
69+
url_parts = get_url_parts(hostname)
70+
port = url_parts[1] or 514
71+
handler = NoPrioritySysLogHandlerWrapper(
72+
url_parts[0], port=port, protocol=protocol
73+
).handler
7074
return _init_logger(logger, handler, output_format)
7175
return logger
7276

src/code42cli/securitydata/subcommands/clear_checkpoint.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from code42cli.securitydata.cursor_store import AEDCursorStore
1+
from code42cli.securitydata.cursor_store import FileEventCursorStore
22

33

44
def init(subcommand_parser):
@@ -15,7 +15,7 @@ def clear_checkpoint(*args):
1515
To use, run `code42 clear-checkpoint`.
1616
This affects `incremental` mode by causing it to behave like it has never been run before.
1717
"""
18-
AEDCursorStore().reset()
18+
FileEventCursorStore().reset()
1919

2020

2121
if __name__ == "__main__":

src/code42cli/util.py

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import sys
44
from os import path, makedirs
55

6+
from code42cli.compat import urlparse
7+
68

79
def get_input(prompt):
810
"""Uses correct input function based on Python version."""
@@ -42,3 +44,11 @@ def print_bold(bold_text):
4244

4345
def is_interactive():
4446
return sys.stdin.isatty()
47+
48+
49+
def get_url_parts(url_str):
50+
parts = url_str.split(u":")
51+
port = None
52+
if len(parts) > 1 and parts[1] != u"":
53+
port = int(parts[1])
54+
return parts[0], port

tests/conftest.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import pytest
21
import json as json_module
3-
from datetime import datetime, timedelta
42
from argparse import Namespace
3+
from datetime import datetime, timedelta
4+
5+
import pytest
56

67
from code42cli.profile.config import ConfigurationKeys
78

tests/profile/test_config.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import with_statement
2+
23
import pytest
34

45
import code42cli.profile.config as config

tests/profile/test_profile.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import pytest
21
from argparse import ArgumentParser
2+
3+
import pytest
4+
35
from code42cli.profile import profile
46
from .conftest import CONFIG_NAMESPACE, PASSWORD_NAMESPACE, PROFILE_NAMESPACE
57

0 commit comments

Comments
 (0)