Skip to content

Commit c63cda1

Browse files
arbor-acairdarbor-dwatson
authored andcommitted
Added example of creating a combined managed object using the API (#13)
A simple, slightly dangerous, example of creating a combined managed object from two pre-existing MOs
1 parent e6d7436 commit c63cda1

File tree

2 files changed

+534
-1
lines changed

2 files changed

+534
-1
lines changed

code-examples/combine-v4-v6-MOs.py

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
#!/usr/bin/env python
2+
3+
"""show how to use the new keys / functionality provided by CoMO
4+
5+
Because MOs are a licensed quantity in SP, it can be helpful to make the best
6+
possible use of the MOs, and with the Combined IPv4/IPv6 Managed Objects
7+
available in SP8.4 MOs that were previously separate to support IPv4 and IPv6
8+
match criteria can now be combined.
9+
10+
Look for IPv6 matches, determine that no functionality is lost, and produce the
11+
JSON for a PATCH to cust02 to move the IPv6 stuff into it. (e.g., if cust02_v6
12+
and cusot02 both exist, can they safely be merged? What would be new MO look
13+
like?) Take a list of MOs that looks like:
14+
15+
[
16+
{
17+
"v4": "cust01_v4",
18+
"v6": "cust01_v6"
19+
},
20+
{
21+
"v4": "cust02",
22+
"v6": "cust02_v6"
23+
},
24+
{
25+
"v4": "cust03-4",
26+
"v6": "cust04"
27+
},
28+
{
29+
"v4": "cust-04",
30+
"v6": "cust6-04"
31+
},
32+
{
33+
"v4": "cust4-05",
34+
"v6": "cust6-05"
35+
}
36+
]
37+
38+
and see if the MOs tagged "v6" can be merged into the MO tagged v4 """
39+
40+
from __future__ import print_function
41+
import json
42+
import os
43+
import sys
44+
import requests
45+
from copy import deepcopy
46+
47+
48+
def get_mos(leader, apikey, certfile, page=1, mos=[]):
49+
"""gather up all of the MOs on the deployment and return them"""
50+
51+
url = "https://{}/api/sp/managed_objects/?page={}".format(
52+
leader, page)
53+
54+
response = requests.get(
55+
url,
56+
headers={"X-Arbux-APIToken": apikey,
57+
"Content-Type": "application/vnd.json+api"},
58+
verify=certfile)
59+
60+
if response.status_code is not requests.codes.ok:
61+
print("API request for alerts returned {} ({})".format(
62+
response.reason, response.status_code),
63+
file=sys.stderr)
64+
return None
65+
66+
response = response.json()
67+
68+
mos = response['data']
69+
70+
if 'next' in response['links']:
71+
print ("Getting page {} from the "
72+
"managed_objects endpoint".format(page+1))
73+
mos += get_mos(leader, apikey, certfile, page+1, mos)
74+
75+
return mos
76+
77+
78+
def get_mos_to_check(MOS_FILE):
79+
"""Load the MOs to check from a file or print MO names
80+
81+
Try to load the MOs from a JSON file called "./mos_to_check.json";
82+
if that file doesn't exist, then just print a list of MOs and
83+
exit.
84+
"""
85+
if not os.path.isfile(MOS_FILE):
86+
msg = """
87+
Please create a file called %s and populate it with
88+
a JSON-formatted list of names of MOs to evaluate for
89+
combining.
90+
The list should look like:
91+
[
92+
{
93+
"v4": "mo1_name_v4",
94+
"v6": "mo1_name_v6"
95+
},
96+
{
97+
"v4": "mo2_name_v4",
98+
"v6": "mo2_name_v6"
99+
}
100+
]"""
101+
print (msg % (MOS_FILE))
102+
print ("Your MOs are:")
103+
for (t, n) in sorted(type_and_name):
104+
print ("{:<15} {}".format(t, n))
105+
106+
sys.exit(0)
107+
108+
# no error checking. live fast, leave a beautiful traceback
109+
with open(MOS_FILE, "rb") as f:
110+
mos_to_check = json.load(f)
111+
112+
return mos_to_check
113+
114+
115+
def get_type_and_name(mos):
116+
"""parse the MO json from SP and return type and name for each one
117+
118+
This is just for reporting to the console what is happening, and for
119+
some basic error checking
120+
"""
121+
type_and_name = []
122+
for mo in mos:
123+
if 'match_type' in mo['attributes']:
124+
match_type = mo['attributes']['match_type']
125+
if (match_type == 'cidr_blocks' or
126+
match_type == 'cidr_v6_blocks'):
127+
type_and_name.append([mo['attributes']['match_type'],
128+
mo['attributes']['name']])
129+
130+
num_v6 = 0
131+
for (t, n) in type_and_name:
132+
if t == 'cidr_v6_blocks':
133+
num_v6 += 1
134+
135+
return type_and_name, num_v6
136+
137+
138+
def check_mos(mos, mos_to_check):
139+
"""Make sure the MOs to check from the file actually exist"""
140+
mo_names = [mo['attributes']['name'] for mo in mos]
141+
142+
matched_mos = []
143+
for mo in mos_to_check:
144+
if mo['v4'] in mo_names and mo['v6'] in mo_names:
145+
matched_mos.append(mo)
146+
else:
147+
print ('<'*70)
148+
print ("The following MO will not be evaluated "
149+
"for combining because one ")
150+
print ("or both of the MO names is not configured "
151+
"on the SP system.")
152+
print (json.dumps(mo, indent=4))
153+
print ('>'*70)
154+
155+
return matched_mos
156+
157+
158+
def index_mos_by_name(mos):
159+
"""It is useful to have MOs indexed by name, so do that"""
160+
mos_by_name = {}
161+
for mo in mos:
162+
mos_by_name[mo['attributes']['name']] = mo
163+
164+
return mos_by_name
165+
166+
167+
def diff_mos(mos_by_name, mos_to_check):
168+
"""Look for differences in pairs of v4 and v6 MOs
169+
170+
The differences that will be highlighted are: 1. Different shared
171+
host detection sets; 2. Different family types
172+
173+
You should be very careful to add other things to check here; this
174+
function is probably incomplete for your network and should be
175+
augmented to compare things between MOs that you need to make sure
176+
are OK to combine
177+
"""
178+
179+
results = {}
180+
for mo_name in mos_to_check:
181+
# Store results keys by combined MO names
182+
key = (mo_name['v4'], mo_name['v6'])
183+
if key not in results:
184+
results[key] = []
185+
# Check for matching family types
186+
if (mos_by_name[mo_name['v4']]['attributes']['family'] !=
187+
mos_by_name[mo_name['v6']]['attributes']['family']):
188+
results[key].append("Family types do not match")
189+
# Check for matching shared host detection sets
190+
v4_shds = mos_by_name[mo_name['v4']]['relationships'][
191+
'shared_host_detection_settings']['data']['id']
192+
v6_shds = mos_by_name[mo_name['v6']]['relationships'][
193+
'shared_host_detection_settings']['data']['id']
194+
if (v4_shds != v6_shds):
195+
results[key].append("Shared Host Detection Sets do not match")
196+
#
197+
# You will want to add other relevant checks here, to make sure that
198+
# your combined managed object makes sense in your network
199+
#
200+
return results
201+
202+
203+
def combine_mos(mov4, mov6):
204+
"""put v6 match values and v6 mit templs into cidr_block-match MO
205+
206+
take the two MOs that can be combined and return the JSON for an MO
207+
that can be combined; create a name, comment, and tag that
208+
represents this, set the match values to all of the match values
209+
from both MOs, and set the v4/v6 auto/manual mitigation templates to
210+
the values from each of the initial MOs
211+
212+
You should be very careful to add other things to combine here; this
213+
function is probably incomplete for your network and should be
214+
augmented to combine things between MOs that you need to make sure
215+
are needed in a combined managed object
216+
"""
217+
mov4['attributes']['match'] += " " + mov6['attributes']['match']
218+
mov4['attributes']['name'] = "{} + {}".format(
219+
mov4['attributes']['name'],
220+
mov6['attributes']['name'])
221+
mov4['attributes']['description'] += " ---Combined Managed Object--- "
222+
mov4['attributes']['tags'].append("auto-combined")
223+
mov4['relationships']['mitigation_templates_manual_ipv6'] = (
224+
mov6['relationships']['mitigation_templates_manual_ipv6'])
225+
mov4['relationships']['mitigation_templates_auto_ipv6'] = (
226+
mov6['relationships']['mitigation_templates_auto_ipv6'])
227+
#
228+
# You will want to add other relevant combinaitions here, to make sure that
229+
# your combined managed object makes sense in your network
230+
#
231+
232+
mo = {}
233+
mo['data'] = deepcopy(mov4)
234+
mo['data'].pop("links", None)
235+
mo['data'].pop("id", None)
236+
237+
return mo
238+
239+
240+
if __name__ == '__main__':
241+
242+
LEADER = os.getenv('SPLEADER')
243+
APIKEY = os.getenv('SPAPIKEY')
244+
CERTFILE = './certfile'
245+
MOS_FILE = 'mos_to_merge.json'
246+
247+
print("Collecting information on managed "
248+
"objects from {}.".format(LEADER))
249+
250+
mos = get_mos(LEADER, APIKEY, CERTFILE)
251+
if mos is None:
252+
sys.exit(1)
253+
254+
print ("There are {} MOs.".format(len(mos)))
255+
256+
type_and_name, num_v6 = get_type_and_name(mos)
257+
258+
if num_v6 == 0:
259+
print ("There aren't any IPv6 matches, so "
260+
"there is nothing to combine.")
261+
sys.exit(0)
262+
else:
263+
print ("There are {} out of {} MOs with CIDR matches "
264+
"that are IPv6 matches.".format(
265+
num_v6, len(type_and_name)))
266+
267+
mos_by_name = index_mos_by_name(mos)
268+
269+
mos_to_check = get_mos_to_check(MOS_FILE)
270+
271+
mos_to_check = check_mos(mos, mos_to_check)
272+
273+
differences = diff_mos(mos_by_name, mos_to_check)
274+
275+
uncombinable_mos = [mo for mo in differences if len(differences[mo]) > 0]
276+
for mo in uncombinable_mos:
277+
print ("{}:".format(mo))
278+
for diff in differences[mo]:
279+
print (" - {}".format(diff))
280+
281+
mos_to_combine = [mo for mo in differences if len(differences[mo]) == 0]
282+
283+
print ("MOs that can be combined: {}".format(mos_to_combine))
284+
285+
combined_mo = {}
286+
for mo in mos_to_combine:
287+
combined_mo[mo] = combine_mos(
288+
mos_by_name[mo[0]],
289+
mos_by_name[mo[1]])
290+
for name in mo:
291+
print ('DELETE http://{}/api/sp/managed_objects/{}'.format(
292+
LEADER,
293+
mos_by_name[name]['id']
294+
))
295+
print ('POST http://{}/api/sp/managed_objects << \n{}'.format(
296+
LEADER,
297+
json.dumps(combined_mo[mo], indent=4)))

0 commit comments

Comments
 (0)