Skip to content

Commit 68735e7

Browse files
author
Todd Radel
authored
Merge pull request #10 from banyansecurity/events-v2-controller
[WIP] adding controller for events API v2
2 parents d88b69f + 3c38afb commit 68735e7

File tree

7 files changed

+159
-18
lines changed

7 files changed

+159
-18
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: python
22
python:
3-
- '3.6'
43
- '3.7'
54
- '3.8'
65
install:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66

77
## Prerequisites
8-
Python 3.6, 3.7, or 3.8 must be installed.
8+
Python 3.7 or 3.8 must be installed.
99

1010
## Installation
1111
### Installing the easy way

banyan/api/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from banyan.api.attachment import AttachmentAPI
1414
from banyan.api.device import DeviceAPI
15+
from banyan.api.event_v2 import EventV2API
1516
from banyan.api.netagent import NetagentAPI
1617
from banyan.api.policy import PolicyAPI
1718
from banyan.api.role import RoleAPI
@@ -94,6 +95,7 @@ def __init__(self, api_server_url: str = None, refresh_token: str = None, debug:
9495
self._agents = NetagentAPI(self)
9596
self._users = UserAPI(self)
9697
self._devices = DeviceAPI(self)
98+
self._events = EventV2API(self)
9799

98100
# noinspection PyMethodMayBeStatic
99101
def _normalize_url(self, url: str) -> str:
@@ -138,6 +140,9 @@ def _request(self, method: str, url: str, params: Dict[str, str] = None, data: A
138140
if 'Message' in content:
139141
raise BanyanError(f'{response.status_code} Client Error: {response.reason} for '
140142
f'url: {response.url}: {content["Message"]}')
143+
elif 'error' in content:
144+
raise BanyanError(f'{response.status_code} Client Error: {response.reason} for '
145+
f'url: {response.url}: {content["error_code"]}: {content["error"]}')
141146
except ValueError:
142147
raise BanyanError(f'{response.status_code} Client Error: {response.reason} for url: {response.url}')
143148
return response
@@ -295,6 +300,10 @@ def devices(self) -> DeviceAPI:
295300
"""
296301
return self._devices
297302

303+
@property
304+
def events(self) -> EventV2API:
305+
return self._events
306+
298307

299308
if __name__ == '__main__':
300309
logging.basicConfig(level=logging.DEBUG)

banyan/api/event_v2.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sys
2+
import logging
3+
from typing import List
4+
from datetime import datetime
5+
6+
from banyan.api.base import ServiceBase, Resource
7+
from banyan.model import BanyanApiObject
8+
from banyan.model.event_v2 import EventV2, EventOrID
9+
10+
11+
class EventV2API(ServiceBase):
12+
class Meta:
13+
data_class = EventV2
14+
info_class = EventV2
15+
arg_type = EventOrID
16+
list_uri = '/events'
17+
uri_param = 'EventID'
18+
obj_name = 'event'
19+
supports_paging = False
20+
21+
def create(self, obj: BanyanApiObject) -> str:
22+
raise NotImplementedError('The Banyan API does not support this operation')
23+
24+
def update(self, obj: BanyanApiObject) -> str:
25+
raise NotImplementedError('The Banyan API does not support this operation')
26+
27+
def delete(self, obj: BanyanApiObject) -> str:
28+
raise NotImplementedError('The Banyan API does not support this operation')
29+
30+
def list(self, before_dt: datetime = None, after_dt: datetime = None, order: str = None,
31+
event_type: str = None, subtype: str = None, action: str = None,
32+
email_address: str = None, device_id: str = None, device_serial: str = None,
33+
container_id: str = None, service_name: str = None, event_id: str = None) -> list:
34+
args = [
35+
(before_dt, 'before', lambda: int(before_dt.timestamp() * 1000)),
36+
(after_dt, 'after', lambda: int(after_dt.timestamp() * 1000)),
37+
(order, 'order', order),
38+
(event_type, 'type', event_type),
39+
(subtype, 'sub_type', subtype),
40+
(action, 'action', action),
41+
(email_address, 'user_email', email_address),
42+
(device_id, 'device_id', device_id),
43+
(device_serial, 'serialnumber', device_serial),
44+
(container_id, 'workload_container_id', container_id),
45+
(service_name, 'service_name', service_name),
46+
(event_id, 'id', event_id),
47+
]
48+
params = dict()
49+
for arg, key, val in args:
50+
if arg:
51+
params[key] = val
52+
53+
print(params, file=sys.stderr)
54+
response_json = self._client.api_request('GET', self.Meta.list_uri, params=params)
55+
response_json = response_json['data']
56+
data: List[Resource] = self.Meta.info_class.Schema().load(response_json, many=True)
57+
self._build_cache(data)
58+
return data

banyan/controllers/event.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,90 @@
1+
from datetime import datetime
2+
from typing import List
3+
14
from cement import Controller, ex
25

6+
from banyan.api.event_v2 import EventV2API
7+
from banyan.model.event_v2 import EventV2, EventV2Type, EventV2Subtype
8+
39

4-
class EventV1Controller(Controller):
10+
class EventV2Controller(Controller):
511
class Meta:
612
label = 'event'
713
stacked_type = 'nested'
814
stacked_on = 'base'
915
help = 'report on security and audit events'
1016

11-
# @property
12-
# def _client(self) -> RoleAPI:
13-
# return self.app.client.roles
17+
@property
18+
def _client(self) -> EventV2API:
19+
return self.app.client.events
1420

15-
# TODO: implement /security_events
16-
@ex(help='list events')
21+
@ex(help='list events',
22+
arguments=[
23+
(['--type'],
24+
{
25+
'help': 'Filters events of one event type.',
26+
'choices': EventV2Type.choices(),
27+
}),
28+
(['--subtype'],
29+
{
30+
'help': 'Filters events of one subtype (conditionally depends on type).',
31+
'choices': EventV2Subtype.choices(),
32+
}),
33+
(['--action'],
34+
{
35+
'help': 'Filters events by one of the possible event action values (e.g. "grant", "deny").',
36+
}),
37+
(['--email'],
38+
{
39+
'help': 'Filters events associated with a single user, based on their email address.',
40+
}),
41+
(['--device-id'],
42+
{
43+
'help': 'Filters events associated with a single device, based on its device ID.',
44+
}),
45+
(['--serial-number'],
46+
{
47+
'help': 'Filters events associated with a single device, based on its serial number.',
48+
}),
49+
(['--container-id'],
50+
{
51+
'help': 'Filters events associated with a single workload, based on its container ID.',
52+
}),
53+
(['--service-name'],
54+
{
55+
'help': 'Filters events associated with a single service, based on its service name.',
56+
}),
57+
(['--event-id'],
58+
{
59+
'help': 'Retrieve a single event based on its event ID.',
60+
}),
61+
(['--before'],
62+
{
63+
'help': 'Filters events that occurred before a specific time.',
64+
'type': datetime.fromisoformat,
65+
}),
66+
(['--after'],
67+
{
68+
'help': 'Filters events that occurred after a specific time.',
69+
'type': datetime.fromisoformat,
70+
}),
71+
(['--order'],
72+
{
73+
'help': 'Sets the order for returned events based on created_at timestamp. Supported values '
74+
'ASC, DESC. Default is DESC.',
75+
'choices': ('ASC', 'DESC')
76+
}),
77+
]
78+
)
1779
def list(self):
18-
pass
19-
20-
# TODO: implement /security_events_type_count
21-
@ex(help='show summary of events in database')
22-
def summary(self):
23-
pass
80+
events: List[EventV2] = self._client.list(before_dt=self.app.pargs.before, after_dt=self.app.pargs.after,
81+
order=self.app.pargs.order, event_type=self.app.pargs.type,
82+
subtype=self.app.pargs.subtype,
83+
action=self.app.pargs.action, email_address=self.app.pargs.email,
84+
device_id=self.app.pargs.device_id,
85+
device_serial=self.app.pargs.serial_number,
86+
container_id=self.app.pargs.container_id,
87+
service_name=self.app.pargs.service_name,
88+
event_id=self.app.pargs.event_id)
89+
event_json = EventV2.Schema().dump(events, many=True)
90+
self.app.render(event_json, handler='json', indent=2, sort_keys=True)

banyan/main.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import logging
2+
13
from cement import App, TestApp, init_defaults
24
from cement.core.exc import CaughtSignal
35

46
from banyan.api import BanyanApiClient
57
from banyan.controllers.admin import AdminController
68
from banyan.controllers.base import Base
79
from banyan.controllers.device import DeviceController
8-
from banyan.controllers.event import EventV1Controller
10+
from banyan.controllers.event import EventV2Controller
911
from banyan.controllers.netagent import NetagentController
1012
from banyan.controllers.policy import PolicyController
1113
from banyan.controllers.role import RoleController
@@ -27,6 +29,11 @@ def extend_client(app: App) -> None:
2729
app.extend('client', client)
2830

2931

32+
def set_logging(app: App) -> None:
33+
if app.debug:
34+
logging.basicConfig(level=logging.DEBUG)
35+
36+
3037
class MyApp(App):
3138
"""Banyan CLI primary application."""
3239

@@ -51,7 +58,8 @@ class Meta:
5158
]
5259

5360
hooks = [
54-
('post_setup', extend_client)
61+
('post_setup', extend_client),
62+
('post_setup', set_logging)
5563
]
5664

5765
# configuration handler
@@ -77,7 +85,7 @@ class Meta:
7785
UserController,
7886
DeviceController,
7987
AdminController,
80-
EventV1Controller
88+
EventV2Controller
8189
]
8290

8391

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
packages=find_packages(exclude=['ez_setup', 'tests*']),
2222
package_data={'banyan': ['templates/*']},
2323
include_package_data=True,
24-
python_requires='~=3.6',
24+
python_requires='~=3.7',
2525
install_requires=[
2626
'dnspython',
2727
'marshmallow',

0 commit comments

Comments
 (0)