Skip to content

Commit 31eceeb

Browse files
authored
Merge pull request carbonblack#82 from carbonblack/cb-defense-policy-api
Cb Defense policy API
2 parents 1ed36a1 + 7b04b40 commit 31eceeb

File tree

8 files changed

+310
-12
lines changed

8 files changed

+310
-12
lines changed

examples/defense/policy_operations.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#!/usr/bin/env python
2+
#
3+
4+
import sys
5+
from cbapi.defense import Policy
6+
from cbapi.example_helpers import build_cli_parser, get_cb_defense_object
7+
from cbapi.errors import ServerError
8+
import logging
9+
import json
10+
11+
log = logging.getLogger(__name__)
12+
13+
14+
def get_policy_by_name_or_id(cb, policy_id=None, name=None, return_all_if_none=False):
15+
policies = []
16+
17+
try:
18+
if policy_id:
19+
if isinstance(policy_id, list):
20+
attempted_to_find = "IDs of {0}".format(", ".join([str(pid) for pid in policy_id]))
21+
policies = [p for p in cb.select(Policy) if p.id in policy_id]
22+
else:
23+
attempted_to_find = "ID of {0}".format(policy_id)
24+
policies = [cb.select(Policy, policy_id, force_init=True)]
25+
elif name:
26+
if isinstance(name, list):
27+
attempted_to_find = "names of {0}".format(", ".join(name))
28+
policies = [p for p in cb.select(Policy) if p.name in name]
29+
else:
30+
attempted_to_find = "name {0}".format(name)
31+
policies = [p for p in cb.select(Policy) if p.name == name]
32+
elif return_all_if_none:
33+
attempted_to_find = "all policies"
34+
policies = list(cb.select(Policy))
35+
except Exception as e:
36+
print("Could not find policy with {0}: {1}".format(attempted_to_find, str(e)))
37+
38+
return policies
39+
40+
41+
def list_policies(cb, parser, args):
42+
for p in cb.select(Policy):
43+
print("Policy id {0}: {1} {2}".format(p.id, p.name, "({0})".format(p.description) if p.description else ""))
44+
print("Rules:")
45+
for r in p.rules.values():
46+
print(" {0}: {1} when {2} {3} is {4}".format(r.get('id'), r.get("action"),
47+
r.get("application", {}).get("type"),
48+
r.get("application", {}).get("value"), r.get("operation")))
49+
50+
51+
def import_policy(cb, parser, args):
52+
p = cb.create(Policy)
53+
54+
p.policy = json.load(open(args.policyfile, "r"))
55+
p.description = args.description
56+
p.name = args.name
57+
p.priorityLevel = args.prioritylevel
58+
p.version = 2
59+
60+
try:
61+
p.save()
62+
except ServerError as se:
63+
print("Could not add policy: {0}".format(str(se)))
64+
except Exception as e:
65+
print("Could not add policy: {0}".format(str(e)))
66+
else:
67+
print("Added policy. New policy ID is {0}".format(p.id))
68+
69+
70+
def delete_policy(cb, parser, args):
71+
policies = get_policy_by_name_or_id(cb, args.id, args.name)
72+
if len(policies) == 0:
73+
return
74+
75+
num_matching_policies = len(policies)
76+
if num_matching_policies > 1 and not args.force:
77+
print("{0:d} policies match and --force not specified. No action taken.".format(num_matching_policies))
78+
return
79+
80+
for p in policies:
81+
try:
82+
p.delete()
83+
except Exception as e:
84+
print("Could not delete policy: {0}".format(str(e)))
85+
else:
86+
print("Deleted policy id {0} with name {1}".format(p.id, p.name))
87+
88+
89+
def export_policy(cb, parser, args):
90+
policies = get_policy_by_name_or_id(cb, args.id, args.name, return_all_if_none=True)
91+
92+
for p in policies:
93+
json.dump(p.policy, open("policy-{0}.json".format(p.id), "w"), indent=2)
94+
print("Wrote policy {0} {1} to file policy-{0}.json".format(p.id, p.name))
95+
96+
97+
def add_rule(cb, parser, args):
98+
policies = get_policy_by_name_or_id(cb, args.id, args.name)
99+
100+
num_matching_policies = len(policies)
101+
if num_matching_policies < 1:
102+
print("No policies match. No action taken.".format(num_matching_policies))
103+
104+
for policy in policies:
105+
policy.add_rule(json.load(open(args.rulefile, "r")))
106+
print("Added rule from {0} to policy {1}.".format(args.rulefile, policy.name))
107+
108+
109+
def del_rule(cb, parser, args):
110+
policies = get_policy_by_name_or_id(cb, args.id, args.name)
111+
112+
num_matching_policies = len(policies)
113+
if num_matching_policies != 1:
114+
print("{0:d} policies match. No action taken.".format(num_matching_policies))
115+
116+
policy = policies[0]
117+
policy.delete_rule(args.ruleid)
118+
119+
print("Removed rule id {0} from policy {1}.".format(args.ruleid, policy.name))
120+
121+
122+
def replace_rule(cb, parser, args):
123+
policies = get_policy_by_name_or_id(cb, args.id, args.name)
124+
125+
num_matching_policies = len(policies)
126+
if num_matching_policies != 1:
127+
print("{0:d} policies match. No action taken.".format(num_matching_policies))
128+
129+
policy = policies[0]
130+
policy.replace_rule(args.ruleid, json.load(open(args.rulefile, "r")))
131+
132+
print("Replaced rule id {0} from policy {1} with rule from file {2}.".format(args.ruleid, policy.name,
133+
args.rulefile))
134+
135+
def main():
136+
parser = build_cli_parser("Policy operations")
137+
commands = parser.add_subparsers(help="Policy commands", dest="command_name")
138+
139+
list_command = commands.add_parser("list", help="List all configured policies")
140+
141+
import_policy_command = commands.add_parser("import", help="Import policy from JSON file")
142+
import_policy_command.add_argument("-N", "--name", help="Name of new policy", required=True)
143+
import_policy_command.add_argument("-d", "--description", help="Description of new policy", required=True)
144+
import_policy_command.add_argument("-p", "--prioritylevel", help="Priority level (HIGH, MEDIUM, LOW)",
145+
default="LOW")
146+
import_policy_command.add_argument("-f", "--policyfile", help="Filename containing the JSON policy description",
147+
required=True)
148+
149+
export_policy_command = commands.add_parser("export", help="Export policy to JSON file")
150+
export_policy_specifier = export_policy_command.add_mutually_exclusive_group(required=False)
151+
export_policy_specifier.add_argument("-i", "--id", type=int, help="ID of policy")
152+
export_policy_specifier.add_argument("-N", "--name", help="Name of policy")
153+
154+
del_command = commands.add_parser("delete", help="Delete policies")
155+
del_policy_specifier = del_command.add_mutually_exclusive_group(required=True)
156+
del_policy_specifier.add_argument("-i", "--id", type=int, help="ID of policy to delete")
157+
del_policy_specifier.add_argument("-N", "--name", help="Name of policy to delete. Specify --force to delete"
158+
" multiple policies that have the same name")
159+
del_command.add_argument("--force", help="If NAME matches multiple policies, delete all matching policies",
160+
action="store_true", default=False)
161+
162+
add_rule_command = commands.add_parser("add-rule", help="Add rule to existing policy from JSON rule file")
163+
add_rule_specifier = add_rule_command.add_mutually_exclusive_group(required=True)
164+
add_rule_specifier.add_argument("-i", "--id", type=int, help="ID of policy (can specify multiple)",
165+
action="append", metavar="POLICYID")
166+
add_rule_specifier.add_argument("-N", "--name", help="Name of policy (can specify multiple)",
167+
action="append", metavar="POLICYNAME")
168+
add_rule_command.add_argument("-f", "--rulefile", help="Filename containing the JSON rule", required=True)
169+
170+
del_rule_command = commands.add_parser("del-rule", help="Delete rule from existing policy")
171+
del_rule_specifier = del_rule_command.add_mutually_exclusive_group(required=True)
172+
del_rule_specifier.add_argument("-i", "--id", type=int, help="ID of policy")
173+
del_rule_specifier.add_argument("-N", "--name", help="Name of policy")
174+
del_rule_command.add_argument("-r", "--ruleid", type=int, help="ID of rule", required=True)
175+
176+
replace_rule_command = commands.add_parser("replace-rule", help="Replace existing rule with a new one")
177+
replace_rule_specifier = replace_rule_command.add_mutually_exclusive_group(required=True)
178+
replace_rule_specifier.add_argument("-i", "--id", type=int, help="ID of policy")
179+
replace_rule_specifier.add_argument("-N", "--name", help="Name of policy")
180+
replace_rule_command.add_argument("-r", "--ruleid", type=int, help="ID of rule", required=True)
181+
182+
args = parser.parse_args()
183+
cb = get_cb_defense_object(args)
184+
185+
if args.command_name == "list":
186+
return list_policies(cb, parser, args)
187+
elif args.command_name == "import":
188+
return import_policy(cb, parser, args)
189+
elif args.command_name == "export":
190+
return export_policy(cb, parser, args)
191+
elif args.command_name == "delete":
192+
return delete_policy(cb, parser, args)
193+
elif args.command_name == "add-rule":
194+
return add_rule(cb, parser, args)
195+
elif args.command_name == "del-rule":
196+
return del_rule(cb, parser, args)
197+
elif args.command_name == "replace-rule":
198+
return replace_rule(cb, parser, args)
199+
200+
201+
if __name__ == "__main__":
202+
sys.exit(main())

examples/response/watchlist_operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def main():
8383
list_actions_command = commands.add_parser("list-actions", help="List actions associated with a watchlist")
8484
list_actions_specifier = list_actions_command.add_mutually_exclusive_group(required=True)
8585
list_actions_specifier.add_argument("-i", "--id", type=int, help="ID of watchlist")
86-
list_actions_specifier.add_argument("-f", "--name", help="Name of watchlist")
86+
list_actions_specifier.add_argument("-N", "--name", help="Name of watchlist")
8787

8888
add_command = commands.add_parser("add", help="Add new watchlist")
8989
add_command.add_argument("-N", "--name", help="Name of watchlist", required=True)

src/cbapi/defense/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
from __future__ import absolute_import
44

55
from .rest_api import CbDefenseAPI
6-
from .models import Device, Event
6+
from .models import Device, Event, Policy

src/cbapi/defense/models.py

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,53 @@
11
from ..models import MutableBaseModel, CreatableModelMixin, NewBaseModel
22

3+
from copy import deepcopy
34
import logging
45
import json
6+
57
from ..errors import ServerError
68

79
log = logging.getLogger(__name__)
810

911

1012
class DefenseMutableModel(MutableBaseModel):
1113
_change_object_http_method = "PATCH"
14+
_change_object_key_name = None
15+
16+
def __init__(self, cb, model_unique_id=None, initial_data=None, force_init=False, full_doc=False):
17+
super(DefenseMutableModel, self).__init__(cb, model_unique_id=model_unique_id, initial_data=initial_data,
18+
force_init=force_init, full_doc=full_doc)
19+
if not self._change_object_key_name:
20+
self._change_object_key_name = self.primary_key
21+
22+
def _parse(self, obj):
23+
if type(obj) == dict and self.info_key in obj:
24+
return obj[self.info_key]
1225

1326
def _update_object(self):
27+
if self._change_object_http_method != "PATCH":
28+
return self._update_entire_object()
29+
else:
30+
return self._patch_object()
31+
32+
def _update_entire_object(self):
33+
if self.__class__.primary_key in self._dirty_attributes.keys() or self._model_unique_id is None:
34+
new_object_info = deepcopy(self._info)
35+
try:
36+
if not self._new_object_needs_primary_key:
37+
del(new_object_info[self.__class__.primary_key])
38+
except Exception:
39+
pass
40+
log.debug("Creating a new {0:s} object".format(self.__class__.__name__))
41+
ret = self._cb.api_json_request(self.__class__._new_object_http_method, self.urlobject,
42+
data={self.info_key: new_object_info})
43+
else:
44+
log.debug("Updating {0:s} with unique ID {1:s}".format(self.__class__.__name__, str(self._model_unique_id)))
45+
ret = self._cb.api_json_request(self.__class__._change_object_http_method,
46+
self._build_api_request_uri(), data={self.info_key: self._info})
47+
48+
return self._refresh_if_needed(ret)
49+
50+
def _patch_object(self):
1451
if self.__class__.primary_key in self._dirty_attributes.keys() or self._model_unique_id is None:
1552
log.debug("Creating a new {0:s} object".format(self.__class__.__name__))
1653
ret = self._cb.api_json_request(self.__class__._new_object_http_method, self.urlobject,
@@ -49,6 +86,12 @@ def _refresh_if_needed(self, request_ret):
4986
self._info = message.get(self.info_key)
5087
self._full_init = True
5188
refresh_required = False
89+
else:
90+
if self._change_object_key_name in message.keys():
91+
# if all we got back was an ID, try refreshing to get the entire record.
92+
log.debug("Only received an ID back from the server, forcing a refresh")
93+
self._info[self.primary_key] = message[self._change_object_key_name]
94+
refresh_required = True
5295
else:
5396
# "success" is False
5497
raise ServerError(request_ret.status_code, message.get("message", ""),
@@ -71,10 +114,6 @@ class Device(DefenseMutableModel):
71114
def __init__(self, cb, model_unique_id, initial_data=None):
72115
super(Device, self).__init__(cb, model_unique_id, initial_data)
73116

74-
def _parse(self, obj):
75-
if type(obj) == dict and "deviceInfo" in obj:
76-
return obj["deviceInfo"]
77-
78117
def lr_session(self):
79118
"""
80119
Retrieve a Live Response session object for this Device.
@@ -90,11 +129,36 @@ def lr_session(self):
90129
class Event(NewBaseModel):
91130
urlobject = "/integrationServices/v3/event"
92131
primary_key = "eventId"
132+
info_key = "eventInfo"
133+
134+
def _parse(self, obj):
135+
if type(obj) == dict and self.info_key in obj:
136+
return obj[self.info_key]
93137

94138
def __init__(self, cb, model_unique_id, initial_data=None):
95139
super(Event, self).__init__(cb, model_unique_id, initial_data)
96140

97-
def _parse(self, obj):
98-
if type(obj) == dict and "eventInfo" in obj:
99-
return obj["eventInfo"]
100141

142+
class Policy(DefenseMutableModel, CreatableModelMixin):
143+
urlobject = "/integrationServices/v3/policy"
144+
info_key = "policyInfo"
145+
swagger_meta_file = "defense/models/policyInfo.yaml"
146+
_change_object_http_method = "PUT"
147+
_change_object_key_name = "policyId"
148+
149+
@property
150+
def rules(self):
151+
return dict([(r.get("id"), r) for r in self.policy.get("rules", [])])
152+
153+
def add_rule(self, new_rule):
154+
self._cb.post_object("{0}/rule".format(self._build_api_request_uri()), {"ruleInfo": new_rule})
155+
self.refresh()
156+
157+
def delete_rule(self, rule_id):
158+
self._cb.delete_object("{0}/rule/{1}".format(self._build_api_request_uri(), rule_id))
159+
self.refresh()
160+
161+
def replace_rule(self, rule_id, new_rule):
162+
self._cb.put_object("{0}/rule/{1}".format(self._build_api_request_uri(), rule_id),
163+
{"ruleInfo": new_rule})
164+
self.refresh()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
type: object
2+
required:
3+
- description
4+
- name
5+
- policy
6+
- priorityLevel
7+
- version
8+
properties:
9+
description:
10+
type: string
11+
id:
12+
type: integer
13+
latestRevision:
14+
type: integer
15+
name:
16+
type: string
17+
policy:
18+
type: object
19+
priorityLevel:
20+
type: string
21+
systemPolicy:
22+
type: boolean
23+
version:
24+
type: integer
25+

0 commit comments

Comments
 (0)