Skip to content

Commit 6855267

Browse files
author
Juliya Smith
authored
Jules/integ 851 profile (#1)
1 parent 7bc0c06 commit 6855267

33 files changed

+929
-1670
lines changed

CHANGELOG.md

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

10+
## Unreleased
11+
12+
### Removed
13+
- Removed config file settings and `-c` CLI arg. Use `c42sec profile set`.
14+
- Removed `--clear-password` CLI argument. Use `c42sec profile set -p`. You will be prompted.
15+
16+
### Added
17+
- Added ability to view your profile: `c42sec profile show`.
18+
19+
### Changed
20+
- Renamed `c42aed` to `c42sec`.
21+
- Moved CLI arguments `-s`, `-u`, and `--ignore-ssl-errors` to `c42sec profile set` command.
22+
23+
1024
## 0.1.1 - 2019-10-29
1125

1226
### Fixed

README.md

Lines changed: 7 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# c42seceventcli - AED
1+
# c42sec
22

33
The c42seceventcli AED module contains a CLI tool for extracting AED events as well as an optional state manager
44
for recording timestamps. The state manager records timestamps so that on future runs,
@@ -10,170 +10,26 @@ you only extract events you did not previously extract.
1010
- Code42 Server 6.8.x+
1111

1212
## Installation
13-
Until we are able to put `py42` and `c42secevents` on PyPI, you will need to first install them manually.
14-
15-
`py42` is available for download [here](https://confluence.corp.code42.com/pages/viewpage.action?pageId=61767969#py42%E2%80%93Code42PythonSDK-Downloads).
16-
For py42 installation instructions, see its [README](https://stash.corp.code42.com/projects/SH/repos/lib_c42_python_sdk/browse/README.md).
17-
18-
`c42secevents` is available [here](https://confluence.corp.code42.com/display/LS/Security+Event+Extractor+-+Python).
19-
For `c42secevents` installation instructions, see its [README](https://stash.corp.code42.com/projects/INT/repos/security-event-extractor/browse/README.md).
20-
21-
Once you've done that, install `c42seceventcli` using:
13+
Install `c42sec` using:
2214

2315
```bash
2416
$ python setup.py install
2517
```
2618

2719
## Usage
2820

29-
A simple usage requires you to pass in your Code42 authority URL and username as arguments:
30-
31-
```bash
32-
c42aed -s https://example.authority.com -u [email protected]
33-
```
34-
35-
Another option is to put your Code42 authority URL and username (and other arguments) in a config file.
36-
Use `default.config.cfg` as an example to make your own config file; it has all the supported arguments.
37-
The arguments in `default.config.cfg` mirror the CLI arguments.
38-
39-
```buildoutcfg
40-
[Code42]
41-
c42_authority_url=https://example.authority.com
42-
43-
```
44-
45-
Then, run the script as follows:
46-
47-
```bash
48-
c42aed -c path/to/config
49-
```
50-
51-
To use the state management service, simply provide the `-r` to the command line.
52-
`-r` is particularly useful if you wish to run this script on a recurring job:
53-
54-
```bash
55-
c42aed -s https://example.authority.com -u [email protected] -r
56-
```
57-
58-
If you are using a config file with `-c`, set `record_cursor` to True:
59-
60-
```buildoutcfg
61-
[Code42]
62-
c42_authority_url=https://example.authority.com
63-
64-
record_cursor=True
65-
```
66-
By excluding `-r`, future runs will not know about previous events you got, and
67-
you will get all the events in the given time range (or default time range of 60 days back).
68-
69-
To clear the cursor:
70-
71-
```bash
72-
c42aed -s https://example.authority.com -u [email protected] -r --clear-cursor
73-
```
74-
There are two possible output formats.
75-
76-
* CEF
77-
* JSON
78-
79-
JSON is the default. To use CEF, use `-o CEF`:
21+
First, set your profile
8022

8123
```bash
82-
c42aed -s https://example.authority.com -u [email protected] -o CEF
83-
```
84-
85-
Or if you are using a config file with `-c`:
86-
87-
```buildoutcfg
88-
[Code42]
89-
c42_authority_url=https://example.authority.com
90-
91-
output_format=CEF
24+
c42sec profile set -s https://example.authority.com -u [email protected] -p
9225
```
9326

94-
There are three possible destination types to use:
95-
96-
* stdout
97-
* file - writing to a file
98-
* server - transmitting to a server, such as syslog
27+
`-p` will prompt for your password securely. If your username does not have a password stored, you will be prompted anyway.
9928

100-
The program defaults to `stdout`. To use a file, use `--dest-type` and `--dest` this way:
29+
To ignore SSL errors, do:
10130

10231
```bash
103-
c42aed -s https://example.authority.com -u [email protected] --dest-type file --dest name-of-file.txt
104-
```
105-
106-
To use a server destination (like syslog):
107-
108-
```bash
109-
c42aed -s https://example.authority.com -u [email protected] --dest-type server --dest https://syslog.example.com
110-
```
111-
112-
Both `destination_type` and `destination` are possible fields in the config file as well.
113-
114-
You can also use CLI arguments with config file arguments, but the program will favor the CLI arguments.
115-
116-
If this is your first time running, you will be prompted for your Code42 password.
117-
118-
If you get a keychain error when running this script, you may have to add a code signature:
119-
120-
```bash
121-
codesign -f -s - $(which python)
122-
```
123-
124-
All errors are sent to an error log file named `c42seceventcli_aed_errors.log`
125-
located in your user directory under `.c42seceventcli/log`.
126-
127-
Full usage:
128-
129-
```
130-
usage: c42aed [-h] [--clear-cursor] [--reset-password] [-c CONFIG_FILE]
131-
[-s C42_AUTHORITY_URL] [-u C42_USERNAME] [-b BEGIN_DATE] [-i]
132-
[-o {CEF,JSON}]
133-
[-t [{SharedViaLink,SharedToDomain,ApplicationRead,CloudStorage,RemovableMedia,IsPublic} [{SharedViaLink,SharedToDomain,ApplicationRead,CloudStorage,RemovableMedia,IsPublic} ...]]]
134-
[-d--debug] [--dest-type {stdout,file,server}]
135-
[--dest DESTINATION] [--dest-port DESTINATION_PORT]
136-
[--dest-protocol {TCP,UDP}] [-e END_DATE | -r]
137-
138-
optional arguments:
139-
-h, --help show this help message and exit
140-
--clear-cursor Resets the stored cursor.
141-
--reset-password Clears stored password and prompts user for password.
142-
-c CONFIG_FILE, --config-file CONFIG_FILE
143-
The path to the config file to use for the rest of the
144-
arguments.
145-
-s C42_AUTHORITY_URL, --server C42_AUTHORITY_URL
146-
The full scheme, url and port of the Code42 server.
147-
-u C42_USERNAME, --username C42_USERNAME
148-
The username of the Code42 API user.
149-
-b BEGIN_DATE, --begin BEGIN_DATE
150-
The beginning of the date range in which to look for
151-
events, in YYYY-MM-DD UTC format OR a number (number
152-
of minutes ago).
153-
-i, --ignore-ssl-errors
154-
Do not validate the SSL certificates of Code42
155-
servers.
156-
-o {CEF,JSON}, --output-format {CEF,JSON}
157-
The format used for outputting events.
158-
-t [{SharedViaLink,SharedToDomain,ApplicationRead,CloudStorage,RemovableMedia,IsPublic} [{SharedViaLink,SharedToDomain,ApplicationRead,CloudStorage,RemovableMedia,IsPublic} ...]], --types [{SharedViaLink,SharedToDomain,ApplicationRead,CloudStorage,RemovableMedia,IsPublic} [{SharedViaLink,SharedToDomain,ApplicationRead,CloudStorage,RemovableMedia,IsPublic} ...]]
159-
To limit extracted events to those with given exposure
160-
types.
161-
-d--debug Turn on debug logging.
162-
--dest-type {stdout,file,server}
163-
The type of destination to send output to.
164-
--dest DESTINATION Either a name of a local file or syslog host address.
165-
Ignored if destination type is 'stdout'.
166-
--dest-port DESTINATION_PORT
167-
Port used when sending logs to server. Ignored if
168-
destination type is not 'server'.
169-
--dest-protocol {TCP,UDP}
170-
Protocol used to send logs to server. Ignored if
171-
destination type is not 'server'.
172-
-e END_DATE, --end END_DATE
173-
The end of the date range in which to look for events,
174-
in YYYY-MM-DD UTC format OR a number (number of
175-
minutes ago).
176-
-r, --record-cursor Only get events that were not previously retrieved.
32+
c42sec profile set --ignore-ssl-errors true
17733
```
17834

17935
# Known Issues

c42sec/.DS_Store

6 KB
Binary file not shown.
File renamed without changes.

c42seceventcli/common/cursor_store.py renamed to c42sec/cursor_store.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import sqlite3
2-
from c42seceventcli.common.util import get_user_project_path
2+
from c42sec.util import get_user_project_path
3+
4+
_INSERTION_TIMESTAMP_FIELD_NAME = u"insertionTimestamp"
35

46

57
class SecurityEventCursorStore(object):
@@ -48,3 +50,36 @@ def _is_empty(self):
4850
query_result = cursor.fetchone()
4951
if query_result:
5052
return int(query_result[0]) <= 0
53+
54+
55+
class AEDCursorStore(SecurityEventCursorStore):
56+
_PRIMARY_KEY = 1
57+
58+
def __init__(self, db_file_path=None):
59+
super(AEDCursorStore, self).__init__("aed_checkpoint", db_file_path)
60+
if self._is_empty():
61+
self._init_table()
62+
63+
def get_stored_insertion_timestamp(self):
64+
rows = self._get(_INSERTION_TIMESTAMP_FIELD_NAME, self._PRIMARY_KEY)
65+
if rows and rows[0]:
66+
return rows[0][0]
67+
68+
def replace_stored_insertion_timestamp(self, new_insertion_timestamp):
69+
self._set(
70+
column_name=_INSERTION_TIMESTAMP_FIELD_NAME,
71+
new_value=new_insertion_timestamp,
72+
primary_key=self._PRIMARY_KEY,
73+
)
74+
75+
def reset(self):
76+
self._drop_table()
77+
self._init_table()
78+
79+
def _init_table(self):
80+
columns = "{0}, {1}".format(self._PRIMARY_KEY_COLUMN_NAME, _INSERTION_TIMESTAMP_FIELD_NAME)
81+
create_table_query = "CREATE TABLE {0} ({1})".format(self._table_name, columns)
82+
insert_query = "INSERT INTO {0} VALUES(?, null)".format(self._table_name)
83+
with self._connection as conn:
84+
conn.execute(create_table_query)
85+
conn.execute(insert_query, (self._PRIMARY_KEY,))

c42sec/main.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from argparse import ArgumentParser
2+
from c42sec.profile import profile
3+
from c42sec.send_to import send_to
4+
from c42sec.write_to import write_to
5+
6+
7+
def main():
8+
c42sec_arg_parser = ArgumentParser()
9+
subcommand_parser = c42sec_arg_parser.add_subparsers()
10+
_init_subcommands(subcommand_parser)
11+
args = c42sec_arg_parser.parse_args()
12+
_call(args, c42sec_arg_parser.print_help)
13+
14+
15+
def _init_subcommands(subcommand_parser):
16+
profile.init(subcommand_parser)
17+
send_to.init(subcommand_parser)
18+
write_to.init(subcommand_parser)
19+
20+
21+
def _call(args, print_help):
22+
"""Call provided subcommand with args."""
23+
try:
24+
args.func(args)
25+
except AttributeError as err:
26+
if str(err) == "'Namespace' object has no attribute 'func'":
27+
print_help()
28+
else:
29+
print(err)
30+
31+
32+
if __name__ == "__main__":
33+
main()
File renamed without changes.

c42sec/profile/_config.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import os
2+
import c42sec.util as util
3+
from configparser import ConfigParser
4+
5+
6+
class ConfigurationKeys(object):
7+
USER_SECTION = u"Code42"
8+
AUTHORITY_KEY = u"c42_authority_url"
9+
USERNAME_KEY = u"c42_username"
10+
IGNORE_SSL_ERRORS_KEY = u"ignore-ssl-errors"
11+
INTERNAL_SECTION = u"Internal"
12+
HAS_SET_PROFILE_KEY = u"has_set_profile"
13+
14+
15+
def get_config_profile():
16+
parser = ConfigParser()
17+
if not profile_has_been_set():
18+
util.print_error("Profile is not set.")
19+
print("")
20+
print("To set, use: ")
21+
util.print_bold("\tc42sec profile set -s <authority-URL> -u <username>")
22+
print("")
23+
exit(1)
24+
25+
return _get_config_profile_from_parser(parser)
26+
27+
28+
def mark_as_set():
29+
parser = ConfigParser()
30+
config_file_path = _get_config_file_path()
31+
parser.read(config_file_path)
32+
settings = parser[ConfigurationKeys.INTERNAL_SECTION]
33+
settings[ConfigurationKeys.HAS_SET_PROFILE_KEY] = "True"
34+
_save(parser, ConfigurationKeys.HAS_SET_PROFILE_KEY)
35+
36+
37+
def profile_has_been_set():
38+
parser = ConfigParser()
39+
config_file_path = _get_config_file_path()
40+
parser.read(config_file_path)
41+
settings = parser[ConfigurationKeys.INTERNAL_SECTION]
42+
return settings.getboolean(ConfigurationKeys.HAS_SET_PROFILE_KEY)
43+
44+
45+
def set_username(new_username):
46+
parser = ConfigParser()
47+
profile = _get_config_profile_from_parser(parser)
48+
profile[ConfigurationKeys.USERNAME_KEY] = new_username
49+
_save(parser, ConfigurationKeys.USERNAME_KEY)
50+
51+
52+
def set_authority_url(new_url):
53+
parser = ConfigParser()
54+
profile = _get_config_profile_from_parser(parser)
55+
profile[ConfigurationKeys.AUTHORITY_KEY] = new_url
56+
_save(parser, ConfigurationKeys.AUTHORITY_KEY)
57+
58+
59+
def set_ignore_ssl_errors(new_value):
60+
parser = ConfigParser()
61+
profile = _get_config_profile_from_parser(parser)
62+
profile[ConfigurationKeys.IGNORE_SSL_ERRORS_KEY] = str(new_value)
63+
_save(parser, ConfigurationKeys.IGNORE_SSL_ERRORS_KEY)
64+
65+
66+
def _get_config_file_path():
67+
path = "{}config.cfg".format(util.get_user_project_path())
68+
if not os.path.exists(path):
69+
_create_new_config_file(path)
70+
71+
return path
72+
73+
74+
def _get_config_profile_from_parser(parser):
75+
config_file_path = _get_config_file_path()
76+
parser.read(config_file_path)
77+
config = parser[ConfigurationKeys.USER_SECTION]
78+
config.ignore_ssl_errors = config.getboolean(ConfigurationKeys.IGNORE_SSL_ERRORS_KEY)
79+
return config
80+
81+
82+
def _create_new_config_file(path):
83+
config_parser = ConfigParser()
84+
config_parser = _create_user_section(config_parser)
85+
config_parser = _create_internal_section(config_parser)
86+
_save(config_parser, None, path)
87+
88+
89+
def _create_user_section(parser):
90+
keys = ConfigurationKeys
91+
parser.add_section(keys.USER_SECTION)
92+
parser[keys.USER_SECTION] = {}
93+
parser[keys.USER_SECTION][keys.AUTHORITY_KEY] = "null"
94+
parser[keys.USER_SECTION][keys.USERNAME_KEY] = "null"
95+
parser[keys.USER_SECTION][keys.IGNORE_SSL_ERRORS_KEY] = "False"
96+
return parser
97+
98+
99+
def _create_internal_section(parser):
100+
keys = ConfigurationKeys
101+
parser.add_section(keys.INTERNAL_SECTION)
102+
parser[keys.INTERNAL_SECTION] = {}
103+
parser[keys.INTERNAL_SECTION][keys.HAS_SET_PROFILE_KEY] = "False"
104+
return parser
105+
106+
107+
def _save(parser, key=None, path=None):
108+
path = _get_config_file_path() if path is None else path
109+
util.open_file(path, "w+", lambda f: parser.write(f))
110+
if key is not None:
111+
print("'{}' has been successfully updated".format(key))

0 commit comments

Comments
 (0)