trigger 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trigger/__init__.py +7 -0
- trigger/acl/__init__.py +32 -0
- trigger/acl/autoacl.py +70 -0
- trigger/acl/db.py +324 -0
- trigger/acl/dicts.py +357 -0
- trigger/acl/grammar.py +112 -0
- trigger/acl/ios.py +222 -0
- trigger/acl/junos.py +422 -0
- trigger/acl/models.py +118 -0
- trigger/acl/parser.py +168 -0
- trigger/acl/queue.py +296 -0
- trigger/acl/support.py +1431 -0
- trigger/acl/tools.py +746 -0
- trigger/bin/__init__.py +0 -0
- trigger/bin/acl.py +233 -0
- trigger/bin/acl_script.py +574 -0
- trigger/bin/aclconv.py +82 -0
- trigger/bin/check_access.py +93 -0
- trigger/bin/check_syntax.py +66 -0
- trigger/bin/fe.py +197 -0
- trigger/bin/find_access.py +191 -0
- trigger/bin/gnng.py +434 -0
- trigger/bin/gong.py +86 -0
- trigger/bin/load_acl.py +841 -0
- trigger/bin/load_config.py +18 -0
- trigger/bin/netdev.py +317 -0
- trigger/bin/optimizer.py +638 -0
- trigger/bin/run_cmds.py +18 -0
- trigger/changemgmt/__init__.py +352 -0
- trigger/changemgmt/bounce.py +57 -0
- trigger/cmds.py +1217 -0
- trigger/conf/__init__.py +94 -0
- trigger/conf/global_settings.py +674 -0
- trigger/contrib/__init__.py +7 -0
- trigger/exceptions.py +307 -0
- trigger/gorc.py +172 -0
- trigger/netdevices/__init__.py +1288 -0
- trigger/netdevices/loader.py +174 -0
- trigger/netscreen.py +1030 -0
- trigger/packages/__init__.py +6 -0
- trigger/packages/peewee.py +8084 -0
- trigger/rancid.py +463 -0
- trigger/tacacsrc.py +584 -0
- trigger/twister.py +2203 -0
- trigger/twister2.py +745 -0
- trigger/utils/__init__.py +88 -0
- trigger/utils/cli.py +349 -0
- trigger/utils/importlib.py +77 -0
- trigger/utils/network.py +157 -0
- trigger/utils/rcs.py +178 -0
- trigger/utils/templates.py +81 -0
- trigger/utils/url.py +78 -0
- trigger/utils/xmltodict.py +298 -0
- trigger-2.0.0.dist-info/METADATA +146 -0
- trigger-2.0.0.dist-info/RECORD +61 -0
- trigger-2.0.0.dist-info/WHEEL +5 -0
- trigger-2.0.0.dist-info/entry_points.txt +15 -0
- trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
- trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
- trigger-2.0.0.dist-info/top_level.txt +2 -0
- twisted/plugins/trigger_xmlrpc.py +124 -0
trigger/acl/tools.py
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Various tools for use in scripts or other modules. Heavy lifting from tools
|
|
3
|
+
that have matured over time have been moved into this module.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__author__ = "Jathan McCollum, Eileen Tschetter"
|
|
7
|
+
__maintainer__ = "Jathan McCollum"
|
|
8
|
+
__email__ = "jathan.mccollum@teamaol.com"
|
|
9
|
+
__copyright__ = "Copyright 2010-2011, AOL Inc."
|
|
10
|
+
|
|
11
|
+
import datetime
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import tempfile
|
|
15
|
+
from collections import defaultdict
|
|
16
|
+
|
|
17
|
+
import IPy
|
|
18
|
+
|
|
19
|
+
from trigger.acl.parser import *
|
|
20
|
+
from trigger.conf import settings
|
|
21
|
+
|
|
22
|
+
# Defaults
|
|
23
|
+
DEBUG = False
|
|
24
|
+
DATE_FORMAT = "%Y-%m-%d"
|
|
25
|
+
DEFAULT_EXPIRE = 6 * 30 # 6 months
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Exports
|
|
29
|
+
__all__ = (
|
|
30
|
+
"create_trigger_term",
|
|
31
|
+
"create_access",
|
|
32
|
+
"check_access",
|
|
33
|
+
"ACLScript",
|
|
34
|
+
"process_bulk_loads",
|
|
35
|
+
"get_bulk_acls",
|
|
36
|
+
"get_comment_matches",
|
|
37
|
+
"write_tmpacl",
|
|
38
|
+
"diff_files",
|
|
39
|
+
"worklog",
|
|
40
|
+
"insert_term_into_acl",
|
|
41
|
+
"create_new_acl",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Functions
|
|
46
|
+
def create_trigger_term(
|
|
47
|
+
source_ips=[],
|
|
48
|
+
dest_ips=[],
|
|
49
|
+
source_ports=[],
|
|
50
|
+
dest_ports=[],
|
|
51
|
+
protocols=[],
|
|
52
|
+
action=["accept"],
|
|
53
|
+
name="generated_term",
|
|
54
|
+
):
|
|
55
|
+
"""Constructs & returns a Term object from constituent parts."""
|
|
56
|
+
term = Term()
|
|
57
|
+
term.action = action
|
|
58
|
+
term.name = name
|
|
59
|
+
for key, data in {
|
|
60
|
+
"source-address": source_ips,
|
|
61
|
+
"destination-address": dest_ips,
|
|
62
|
+
"source-port": source_ports,
|
|
63
|
+
"destination-port": dest_ports,
|
|
64
|
+
"protocol": protocols,
|
|
65
|
+
}.items():
|
|
66
|
+
for n in data:
|
|
67
|
+
if key in term.match:
|
|
68
|
+
term.match[key].append(n)
|
|
69
|
+
else:
|
|
70
|
+
term.match[key] = [n]
|
|
71
|
+
return term
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def check_access(terms_to_check, new_term, quiet=True, format="junos", acl_name=None):
|
|
75
|
+
"""
|
|
76
|
+
Determine whether access is permitted by a given ACL (list of terms).
|
|
77
|
+
|
|
78
|
+
Tests a new term against a list of terms. Return True if access in new term
|
|
79
|
+
is permitted, or False if not.
|
|
80
|
+
|
|
81
|
+
Optionally displays the terms that apply and what edits are needed.
|
|
82
|
+
|
|
83
|
+
:param terms_to_check:
|
|
84
|
+
A list of Term objects to check
|
|
85
|
+
|
|
86
|
+
:param new_term:
|
|
87
|
+
The Term object used for the access test
|
|
88
|
+
|
|
89
|
+
:param quiet:
|
|
90
|
+
Toggle whether output is displayed
|
|
91
|
+
|
|
92
|
+
:param format:
|
|
93
|
+
The ACL format to use for output display
|
|
94
|
+
|
|
95
|
+
:param acl_name:
|
|
96
|
+
The ACL name to use for output display
|
|
97
|
+
"""
|
|
98
|
+
permitted = None
|
|
99
|
+
matches = {
|
|
100
|
+
"source-address": new_term.match.get("source-address", []),
|
|
101
|
+
"destination-address": new_term.match.get("destination-address", []),
|
|
102
|
+
"protocol": new_term.match.get("protocol", []),
|
|
103
|
+
"destination-port": new_term.match.get("destination-port", []),
|
|
104
|
+
"source-port": new_term.match.get("source-port", []),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def _permitted_in_term(term, comment=" check_access: PERMITTED HERE"):
|
|
108
|
+
"""
|
|
109
|
+
A little closure to re-use internally that returns a Boolean based
|
|
110
|
+
on the given Term object's action.
|
|
111
|
+
"""
|
|
112
|
+
action = term.action[0]
|
|
113
|
+
if action == "accept":
|
|
114
|
+
is_permitted = True
|
|
115
|
+
if not quiet:
|
|
116
|
+
term.comments.append(Comment(comment))
|
|
117
|
+
|
|
118
|
+
elif action in ("discard", "reject"):
|
|
119
|
+
is_permitted = False
|
|
120
|
+
if not quiet:
|
|
121
|
+
print("\n".join(new_term.output(format, acl_name=acl_name)))
|
|
122
|
+
else:
|
|
123
|
+
is_permitted = None
|
|
124
|
+
|
|
125
|
+
return is_permitted
|
|
126
|
+
|
|
127
|
+
for t in terms_to_check:
|
|
128
|
+
hit = True
|
|
129
|
+
complicated = False
|
|
130
|
+
|
|
131
|
+
for comment in t.comments:
|
|
132
|
+
if "trigger: make discard" in comment:
|
|
133
|
+
t.setaction("discard") # .action[0] = 'discard'
|
|
134
|
+
t.makediscard = True # set 'make discard' flag
|
|
135
|
+
|
|
136
|
+
for k, v in t.match.items():
|
|
137
|
+
if k not in matches or not matches[k]:
|
|
138
|
+
complicated = True
|
|
139
|
+
|
|
140
|
+
else:
|
|
141
|
+
for test in matches[k]:
|
|
142
|
+
if test not in v:
|
|
143
|
+
hit = False
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
if hit and not t.inactive:
|
|
147
|
+
# Simple access check. Elegant!
|
|
148
|
+
if not complicated and permitted is None:
|
|
149
|
+
permitted = _permitted_in_term(t)
|
|
150
|
+
|
|
151
|
+
# Complicated checks should set hit=False unless you want
|
|
152
|
+
# them to display and potentially confuse end-users
|
|
153
|
+
# TODO (jathan): Factor this into a "better way"
|
|
154
|
+
else:
|
|
155
|
+
# Does the term have 'port' defined?
|
|
156
|
+
if "port" in t.match:
|
|
157
|
+
port_match = t.match.get("port")
|
|
158
|
+
match_fields = (matches["destination-port"], matches["source-port"])
|
|
159
|
+
|
|
160
|
+
# Iterate the fields, and then the ports for each field. If
|
|
161
|
+
# one of the port numbers is within port_match, check if
|
|
162
|
+
# the action permits/denies and set the permitted flag.
|
|
163
|
+
for field in match_fields:
|
|
164
|
+
for portnum in field:
|
|
165
|
+
if portnum in port_match:
|
|
166
|
+
permitted = _permitted_in_term(t)
|
|
167
|
+
else:
|
|
168
|
+
hit = False
|
|
169
|
+
|
|
170
|
+
# Other complicated checks would go here...
|
|
171
|
+
|
|
172
|
+
# If a complicated check happened and was not a hit, skip to the
|
|
173
|
+
# next term
|
|
174
|
+
if complicated and not hit:
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
if not quiet:
|
|
178
|
+
print("\n".join(t.output(format, acl_name=acl_name)))
|
|
179
|
+
|
|
180
|
+
return permitted
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def create_access(terms_to_check, new_term):
|
|
184
|
+
"""
|
|
185
|
+
Breaks a new_term up into separate constituent parts so that they can be
|
|
186
|
+
compared in a check_access test.
|
|
187
|
+
|
|
188
|
+
Returns a list of terms that should be inserted.
|
|
189
|
+
"""
|
|
190
|
+
protos = new_term.match.get("protocol", ["any"])
|
|
191
|
+
sources = new_term.match.get("source-address", ["any"])
|
|
192
|
+
dests = new_term.match.get("destination-address", ["any"])
|
|
193
|
+
sourceports = new_term.match.get("source-port", ["any"])
|
|
194
|
+
destports = new_term.match.get("destination-port", ["any"])
|
|
195
|
+
|
|
196
|
+
ret = []
|
|
197
|
+
for proto in protos:
|
|
198
|
+
for source in sources:
|
|
199
|
+
for sourceport in sourceports:
|
|
200
|
+
for dest in dests:
|
|
201
|
+
for destport in destports:
|
|
202
|
+
t = Term()
|
|
203
|
+
if str(proto) != "any":
|
|
204
|
+
t.match["protocol"] = [proto]
|
|
205
|
+
if str(source) != "any":
|
|
206
|
+
t.match["source-address"] = [source]
|
|
207
|
+
if str(dest) != "any":
|
|
208
|
+
t.match["destination-address"] = [dest]
|
|
209
|
+
if str(sourceport) != "any":
|
|
210
|
+
t.match["source-port"] = [sourceport]
|
|
211
|
+
if str(destport) != "any":
|
|
212
|
+
t.match["destination-port"] = [destport]
|
|
213
|
+
if not check_access(terms_to_check, t):
|
|
214
|
+
ret.append(t)
|
|
215
|
+
|
|
216
|
+
return ret
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# note, following code is -not currently used-
|
|
220
|
+
def insert_term_into_acl(new_term, aclobj, debug=False):
|
|
221
|
+
"""
|
|
222
|
+
Return a new ACL object with the new_term added in the proper place based
|
|
223
|
+
on the aclobj. Intended to recursively append to an interim ACL object
|
|
224
|
+
based on a list of Term objects.
|
|
225
|
+
|
|
226
|
+
It's safe to assume that this function is incomplete pending better
|
|
227
|
+
documentation and examples.
|
|
228
|
+
|
|
229
|
+
:param new_term:
|
|
230
|
+
The Term object to use for comparison against aclobj
|
|
231
|
+
|
|
232
|
+
:param aclobj:
|
|
233
|
+
The original ACL object to use for creation of new_acl
|
|
234
|
+
|
|
235
|
+
Example::
|
|
236
|
+
|
|
237
|
+
import copy
|
|
238
|
+
# terms_to_be_added is a list of Term objects that is to be added in
|
|
239
|
+
# the "right place" into new_acl based on the contents of aclobj
|
|
240
|
+
original_acl = parse(open('acl.original'))
|
|
241
|
+
new_acl = copy.deepcopy(original_acl) # Dupe the original
|
|
242
|
+
for term in terms_to_be_added:
|
|
243
|
+
new_acl = generate_new_acl(term, new_acl)
|
|
244
|
+
"""
|
|
245
|
+
new_acl = ACL() # ACL comes from trigger.acl.parser
|
|
246
|
+
new_acl.policers = aclobj.policers
|
|
247
|
+
new_acl.format = aclobj.format
|
|
248
|
+
new_acl.name = aclobj.name
|
|
249
|
+
already_added = False
|
|
250
|
+
|
|
251
|
+
for c in aclobj.comments:
|
|
252
|
+
new_acl.comments.append(c)
|
|
253
|
+
|
|
254
|
+
# The following logic is almost identical to that of check_access() except
|
|
255
|
+
# that it tracks already_added and knows how to handle insertion of terms
|
|
256
|
+
# before or after Terms with an action of 'discard' or 'reject'.
|
|
257
|
+
for t in aclobj.terms:
|
|
258
|
+
hit = True
|
|
259
|
+
complicated = False
|
|
260
|
+
permitted = None
|
|
261
|
+
for k, v in t.match.items():
|
|
262
|
+
if debug:
|
|
263
|
+
print("generate_new_acl(): k,v==", k, "and", v)
|
|
264
|
+
if k == "protocol" and k not in new_term.match:
|
|
265
|
+
continue
|
|
266
|
+
if k not in new_term.match:
|
|
267
|
+
complicated = True
|
|
268
|
+
continue
|
|
269
|
+
else:
|
|
270
|
+
for test in new_term.match[k]:
|
|
271
|
+
if test not in v:
|
|
272
|
+
hit = False
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
if not hit and k in (
|
|
276
|
+
"source-port",
|
|
277
|
+
"destination-port",
|
|
278
|
+
"source-address",
|
|
279
|
+
"destination-address",
|
|
280
|
+
):
|
|
281
|
+
# Here is where it gets odd: If we have multiple IPs in this
|
|
282
|
+
# new term, and one of them matches in a deny, we must set hit
|
|
283
|
+
# to True.
|
|
284
|
+
if t.action[0] in ("discard", "reject"):
|
|
285
|
+
for test in new_term.match[k]:
|
|
286
|
+
if test in v:
|
|
287
|
+
hit = True
|
|
288
|
+
|
|
289
|
+
# Check whether access in new_term is permitted (a la check_access(),
|
|
290
|
+
# track whether it's already been added into new_acl, and then add it
|
|
291
|
+
# in the "right place".
|
|
292
|
+
if hit and not t.inactive and not already_added:
|
|
293
|
+
if not complicated and permitted is None:
|
|
294
|
+
for comment in t.comments:
|
|
295
|
+
if (
|
|
296
|
+
"trigger: make discard" in comment
|
|
297
|
+
and new_term.action[0] == "accept"
|
|
298
|
+
):
|
|
299
|
+
new_acl.terms.append(new_term)
|
|
300
|
+
already_added = True
|
|
301
|
+
permitted = True
|
|
302
|
+
if t.action[0] in ("discard", "reject") and new_term.action[0] in (
|
|
303
|
+
"discard",
|
|
304
|
+
"reject",
|
|
305
|
+
):
|
|
306
|
+
permitted = False
|
|
307
|
+
elif t.action[0] in ("discard", "reject"):
|
|
308
|
+
permitted = False
|
|
309
|
+
new_acl.terms.append(new_term)
|
|
310
|
+
already_added = True
|
|
311
|
+
elif t.action[0] == "accept" and new_term.action[0] in (
|
|
312
|
+
"discard",
|
|
313
|
+
"reject",
|
|
314
|
+
):
|
|
315
|
+
permitted = False
|
|
316
|
+
new_acl.terms.append(new_term)
|
|
317
|
+
already_added = True
|
|
318
|
+
elif t.action[0] == "accept" and new_term.action[0] == "accept":
|
|
319
|
+
permitted = True
|
|
320
|
+
if debug:
|
|
321
|
+
print("PERMITTED?", permitted)
|
|
322
|
+
|
|
323
|
+
# Original term is always appended as we move on
|
|
324
|
+
new_acl.terms.append(t)
|
|
325
|
+
|
|
326
|
+
return new_acl
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def create_new_acl(old_file, terms_to_be_added):
|
|
330
|
+
"""Given a list of Term objects call insert_term_into_acl() to determine
|
|
331
|
+
what needs to be added in based on the contents of old_file. Returns a new
|
|
332
|
+
ACL object."""
|
|
333
|
+
aclobj = parse(open(old_file)) # Start with the original ACL contents
|
|
334
|
+
new_acl = None
|
|
335
|
+
for new_term in terms_to_be_added:
|
|
336
|
+
new_acl = insert_term_into_acl(new_term, aclobj)
|
|
337
|
+
|
|
338
|
+
return new_acl
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_bulk_acls():
|
|
342
|
+
"""
|
|
343
|
+
Returns a dict of acls with an applied count over settings.AUTOLOAD_BULK_THRESH
|
|
344
|
+
"""
|
|
345
|
+
from trigger.netdevices import NetDevices
|
|
346
|
+
|
|
347
|
+
nd = NetDevices()
|
|
348
|
+
all_acls = defaultdict(int)
|
|
349
|
+
for dev in nd.all():
|
|
350
|
+
for acl in dev.acls:
|
|
351
|
+
all_acls[acl] += 1
|
|
352
|
+
|
|
353
|
+
bulk_acls = {}
|
|
354
|
+
for acl, count in all_acls.items():
|
|
355
|
+
if count >= settings.AUTOLOAD_BULK_THRESH and acl != "":
|
|
356
|
+
bulk_acls[acl] = count
|
|
357
|
+
|
|
358
|
+
return bulk_acls
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def process_bulk_loads(work, max_hits=settings.BULK_MAX_HITS_DEFAULT, force_bulk=False):
|
|
362
|
+
"""
|
|
363
|
+
Formerly "process --ones".
|
|
364
|
+
|
|
365
|
+
Processes work dict and determines tuple of (prefix, site) for each device. Stores
|
|
366
|
+
tuple as a dict key in prefix_hits. If prefix_hits[(prefix, site)] is greater than max_hits,
|
|
367
|
+
remove all further matching devices from work dict.
|
|
368
|
+
|
|
369
|
+
By default if a device has no acls flagged as bulk_acls, it is not removed from the work dict.
|
|
370
|
+
|
|
371
|
+
Example:
|
|
372
|
+
* Device 'foo1-xyz.example.com' returns ('foo', 'xyz') as tuple.
|
|
373
|
+
* This is stored as prefix_hits[('foo', 'xyz')] = 1
|
|
374
|
+
* All further devices matching that tuple increment the hits for that tuple
|
|
375
|
+
* Any devices matching hit counter exceeds max_hits is removed from work dict
|
|
376
|
+
|
|
377
|
+
You may override max_hits to increase the num. of devices on which to load a bulk acl.
|
|
378
|
+
You may pass force_bulk=True to treat all loads as bulk loads.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
prefix_pat = re.compile(r"^([a-z]+)\d{0,2}-([a-z0-9]+)")
|
|
382
|
+
prefix_hits = defaultdict(int)
|
|
383
|
+
import trigger.acl.db as adb
|
|
384
|
+
|
|
385
|
+
adb.get_bulk_acls()
|
|
386
|
+
adb.get_netdevices()
|
|
387
|
+
|
|
388
|
+
if DEBUG:
|
|
389
|
+
print("DEVLIST:", sorted(work))
|
|
390
|
+
|
|
391
|
+
# Sort devices numerically
|
|
392
|
+
for dev in sorted(work):
|
|
393
|
+
if DEBUG:
|
|
394
|
+
print("Doing", dev)
|
|
395
|
+
|
|
396
|
+
# testacls = dev.bulk_acls
|
|
397
|
+
# if force_bulk:
|
|
398
|
+
# testacls = dev.acls
|
|
399
|
+
testacls = dev.acls if force_bulk else dev.bulk_acls
|
|
400
|
+
|
|
401
|
+
for acl in (
|
|
402
|
+
testacls
|
|
403
|
+
): # only look at each acl once, but look at all acls if bulk load forced
|
|
404
|
+
if acl in work[dev]:
|
|
405
|
+
# if acl in work[router]:
|
|
406
|
+
if DEBUG:
|
|
407
|
+
(
|
|
408
|
+
print("Determining threshold for acl "),
|
|
409
|
+
acl,
|
|
410
|
+
" on device ",
|
|
411
|
+
dev,
|
|
412
|
+
"\n",
|
|
413
|
+
)
|
|
414
|
+
if acl in settings.BULK_MAX_HITS:
|
|
415
|
+
max_hits = settings.BULK_MAX_HITS[acl]
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
prefix_site = prefix_pat.findall(dev.nodeName)[0]
|
|
419
|
+
except IndexError:
|
|
420
|
+
continue
|
|
421
|
+
|
|
422
|
+
# Mark a hit for this tuple, and dump remaining matches
|
|
423
|
+
prefix_hits[prefix_site] += 1
|
|
424
|
+
|
|
425
|
+
if DEBUG:
|
|
426
|
+
print(prefix_site), prefix_hits[prefix_site]
|
|
427
|
+
if prefix_hits[prefix_site] > max_hits:
|
|
428
|
+
msg = (
|
|
429
|
+
"Removing %s on %s from job queue: threshold of %d exceeded for "
|
|
430
|
+
"'%s' devices in '%s'"
|
|
431
|
+
% (acl, dev, max_hits, prefix_site[0], prefix_site[1])
|
|
432
|
+
)
|
|
433
|
+
print(msg)
|
|
434
|
+
if "log" in globals():
|
|
435
|
+
log.msg(msg)
|
|
436
|
+
|
|
437
|
+
# Remove that acl from being loaded, but still load on that device
|
|
438
|
+
work[dev].remove(acl)
|
|
439
|
+
# work[router].remove(acl)
|
|
440
|
+
|
|
441
|
+
# done with all the devices
|
|
442
|
+
return work
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def get_comment_matches(aclobj, requests):
|
|
446
|
+
"""Given an ACL object and a list of ticket numbers return a list of matching comments."""
|
|
447
|
+
matches = set()
|
|
448
|
+
for t in aclobj.terms:
|
|
449
|
+
for req in requests:
|
|
450
|
+
for c in t.comments:
|
|
451
|
+
if req in c:
|
|
452
|
+
matches.add(t)
|
|
453
|
+
# [matches.add(t) for c in t.comments if req in c]
|
|
454
|
+
|
|
455
|
+
return matches
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def update_expirations(matches, numdays=DEFAULT_EXPIRE):
|
|
459
|
+
"""Update expiration dates on matching terms. This modifies mutable objects, so use cautiously."""
|
|
460
|
+
print("matching terms:", [term.name for term in matches])
|
|
461
|
+
for term in matches:
|
|
462
|
+
date = None
|
|
463
|
+
for comment in term.comments:
|
|
464
|
+
try:
|
|
465
|
+
date = re.search(r"(\d{4}\-\d\d\-\d\d)", comment.data).group()
|
|
466
|
+
except AttributeError:
|
|
467
|
+
# print 'No date match in term: %s, comment: %s' % (term.name, comment)
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
dstamp = datetime.datetime.strptime(date, DATE_FORMAT)
|
|
472
|
+
except ValueError as err:
|
|
473
|
+
print("BAD DATE FOR THIS COMMENT:")
|
|
474
|
+
print("comment:", comment.data)
|
|
475
|
+
print("bad date:", date)
|
|
476
|
+
print(err)
|
|
477
|
+
print("Fix the date and start the job again!")
|
|
478
|
+
import sys
|
|
479
|
+
|
|
480
|
+
sys.exit()
|
|
481
|
+
|
|
482
|
+
new_date = dstamp + datetime.timedelta(days=numdays)
|
|
483
|
+
# print 'Before:\n' + comment.data + '\n'
|
|
484
|
+
print(f"Updated date for term: {term.name}")
|
|
485
|
+
comment.data = comment.data.replace(
|
|
486
|
+
date, datetime.datetime.strftime(new_date, DATE_FORMAT)
|
|
487
|
+
)
|
|
488
|
+
# print 'After:\n' + comment.data
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def write_tmpacl(acl, process_name="_tmpacl"):
|
|
492
|
+
"""Write a temporary file to disk from an Trigger acl.ACL object & return the filename"""
|
|
493
|
+
tmpfile = tempfile.mktemp() + process_name
|
|
494
|
+
f = open(tmpfile, "w")
|
|
495
|
+
for x in acl.output(acl.format, replace=True):
|
|
496
|
+
f.write(x)
|
|
497
|
+
f.write("\n")
|
|
498
|
+
f.close()
|
|
499
|
+
|
|
500
|
+
return tmpfile
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def diff_files(old, new):
|
|
504
|
+
"""Return a unified diff between two files"""
|
|
505
|
+
return os.popen(f"diff -Naur {old} {new}").read()
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def worklog(title, diff, log_string="updated by express-gen"):
|
|
509
|
+
"""Save a diff to the ACL worklog"""
|
|
510
|
+
from time import localtime, strftime
|
|
511
|
+
|
|
512
|
+
from trigger.utils.rcs import RCS
|
|
513
|
+
|
|
514
|
+
date = strftime("%Y%m%d", localtime())
|
|
515
|
+
file = os.path.join(settings.FIREWALL_DIR, "workdocs", "workdoc." + date)
|
|
516
|
+
rcs = RCS(file)
|
|
517
|
+
|
|
518
|
+
if not os.path.isfile(file):
|
|
519
|
+
print(f"Creating new worklog {file}")
|
|
520
|
+
f = open(file, "w")
|
|
521
|
+
f.write("# vi:noai:\n\n")
|
|
522
|
+
f.close()
|
|
523
|
+
rcs.checkin(".")
|
|
524
|
+
|
|
525
|
+
print(f"inserting the diff into the worklog {file}")
|
|
526
|
+
rcs.lock_loop()
|
|
527
|
+
fd = open(file, "a")
|
|
528
|
+
fd.write(f'"{title}"\n')
|
|
529
|
+
fd.write(diff)
|
|
530
|
+
fd.close()
|
|
531
|
+
|
|
532
|
+
print(f"inserting {title} into the load queue")
|
|
533
|
+
rcs.checkin(log_string)
|
|
534
|
+
|
|
535
|
+
# Use acl to insert into queue, should be replaced with API call
|
|
536
|
+
os.spawnlp(os.P_WAIT, "acl", "acl", "-i", title)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# Classes
|
|
540
|
+
class ACLScript:
|
|
541
|
+
"""
|
|
542
|
+
Interface to generating or modifying access-lists. Intended for use in
|
|
543
|
+
creating command-line utilities using the ACL API.
|
|
544
|
+
"""
|
|
545
|
+
|
|
546
|
+
def __init__(
|
|
547
|
+
self,
|
|
548
|
+
acl=None,
|
|
549
|
+
mode="insert",
|
|
550
|
+
cmd="acl_script",
|
|
551
|
+
show_mods=True,
|
|
552
|
+
no_worklog=False,
|
|
553
|
+
no_changes=False,
|
|
554
|
+
):
|
|
555
|
+
self.source_ips = []
|
|
556
|
+
self.dest_ips = []
|
|
557
|
+
self.protocol = []
|
|
558
|
+
self.source_ports = []
|
|
559
|
+
self.dest_ports = []
|
|
560
|
+
self.modify_terms = []
|
|
561
|
+
self.bcomments = []
|
|
562
|
+
self.tempfiles = []
|
|
563
|
+
self.acl = acl
|
|
564
|
+
self.cmd = cmd
|
|
565
|
+
self.mode = mode
|
|
566
|
+
self.show_mods = show_mods
|
|
567
|
+
self.no_worklog = no_worklog
|
|
568
|
+
self.no_changes = no_changes
|
|
569
|
+
|
|
570
|
+
def cleanup(self):
|
|
571
|
+
for file in self.tempfiles:
|
|
572
|
+
os.remove(file)
|
|
573
|
+
|
|
574
|
+
def genargs(self, interactive=False):
|
|
575
|
+
if not self.acl:
|
|
576
|
+
raise "need acl defined"
|
|
577
|
+
|
|
578
|
+
argz = []
|
|
579
|
+
argz.append(f"-a {self.acl}")
|
|
580
|
+
|
|
581
|
+
if self.show_mods:
|
|
582
|
+
argz.append("--show-mods")
|
|
583
|
+
|
|
584
|
+
if self.no_worklog:
|
|
585
|
+
argz.append("--no-worklog")
|
|
586
|
+
|
|
587
|
+
if self.no_changes:
|
|
588
|
+
argz.append("--no-changes")
|
|
589
|
+
|
|
590
|
+
if not interactive:
|
|
591
|
+
argz.append("--no-input")
|
|
592
|
+
|
|
593
|
+
if self.mode == "insert":
|
|
594
|
+
argz.append("--insert-defined")
|
|
595
|
+
|
|
596
|
+
elif self.mode == "replace":
|
|
597
|
+
argz.append("--replace-defined")
|
|
598
|
+
|
|
599
|
+
else:
|
|
600
|
+
raise "invalid mode"
|
|
601
|
+
|
|
602
|
+
for k, v in {
|
|
603
|
+
"--source-address-from-file": self.source_ips,
|
|
604
|
+
"--destination-address-from-file": self.dest_ips,
|
|
605
|
+
}.items():
|
|
606
|
+
if len(v) == 0:
|
|
607
|
+
continue
|
|
608
|
+
tmpf = tempfile.mktemp() + "_genacl"
|
|
609
|
+
self.tempfiles.append(tmpf)
|
|
610
|
+
try:
|
|
611
|
+
f = open(tmpf, "w")
|
|
612
|
+
except:
|
|
613
|
+
print("UNABLE TO OPEN TMPFILE")
|
|
614
|
+
raise "YIKES!"
|
|
615
|
+
for x in v:
|
|
616
|
+
f.write(f"{x.strNormal()}\n")
|
|
617
|
+
f.close()
|
|
618
|
+
|
|
619
|
+
argz.append(f"{k} {tmpf}")
|
|
620
|
+
|
|
621
|
+
for k, v in {"-p": self.source_ports, "-P": self.dest_ports}.items():
|
|
622
|
+
if not len(v):
|
|
623
|
+
continue
|
|
624
|
+
|
|
625
|
+
for x in v:
|
|
626
|
+
argz.append("%s %d" % (k, x))
|
|
627
|
+
|
|
628
|
+
if len(self.modify_terms) and len(self.bcomments):
|
|
629
|
+
print("Can only define either modify_terms or between comments")
|
|
630
|
+
raise "Can only define either modify_terms or between comments"
|
|
631
|
+
|
|
632
|
+
if self.modify_terms:
|
|
633
|
+
for x in self.modify_terms:
|
|
634
|
+
argz.append(f"-t {x}")
|
|
635
|
+
else:
|
|
636
|
+
for x in self.bcomments:
|
|
637
|
+
b, e = x
|
|
638
|
+
argz.append(f'-c "{b}" "{e}"')
|
|
639
|
+
|
|
640
|
+
for proto in self.protocol:
|
|
641
|
+
argz.append(f"--protocol {proto}")
|
|
642
|
+
|
|
643
|
+
return argz
|
|
644
|
+
|
|
645
|
+
def parselog(self, log):
|
|
646
|
+
return log
|
|
647
|
+
|
|
648
|
+
def run(self, interactive=False):
|
|
649
|
+
args = self.genargs(interactive=interactive)
|
|
650
|
+
log = []
|
|
651
|
+
# print self.cmd + ' ' + ' '.join(args)
|
|
652
|
+
if interactive:
|
|
653
|
+
os.system(self.cmd + " " + " ".join(args))
|
|
654
|
+
else:
|
|
655
|
+
f = os.popen(self.cmd + " " + " ".join(args))
|
|
656
|
+
line = f.readline()
|
|
657
|
+
while line:
|
|
658
|
+
line = line.rstrip()
|
|
659
|
+
log.append(line)
|
|
660
|
+
line = f.readline()
|
|
661
|
+
return log
|
|
662
|
+
|
|
663
|
+
def errors_from_log(self, log):
|
|
664
|
+
errors = ""
|
|
665
|
+
for l in log:
|
|
666
|
+
if "%%ERROR%%" in l:
|
|
667
|
+
l = l.spit("%%ERROR%%")[1]
|
|
668
|
+
errors += l[1:] + "\n"
|
|
669
|
+
return errors
|
|
670
|
+
|
|
671
|
+
def diff_from_log(self, log):
|
|
672
|
+
diff = ""
|
|
673
|
+
for l in log:
|
|
674
|
+
if "%%DIFF%%" in l:
|
|
675
|
+
l = l.split("%%DIFF%%")[1]
|
|
676
|
+
diff += l[1:] + "\n"
|
|
677
|
+
return diff
|
|
678
|
+
|
|
679
|
+
def set_acl(self, acl):
|
|
680
|
+
self.acl = acl
|
|
681
|
+
|
|
682
|
+
def _add_addr(self, to, src):
|
|
683
|
+
if isinstance(src, list):
|
|
684
|
+
for x in src:
|
|
685
|
+
if IPy.IP(x) not in to:
|
|
686
|
+
to.append(IPy.IP(x))
|
|
687
|
+
else:
|
|
688
|
+
if IPy.IP(src) not in to:
|
|
689
|
+
to.append(IPy.IP(src))
|
|
690
|
+
|
|
691
|
+
def _add_port(self, to, src):
|
|
692
|
+
if isinstance(src, list):
|
|
693
|
+
for x in src:
|
|
694
|
+
if x not in to:
|
|
695
|
+
to.append(int(x))
|
|
696
|
+
else:
|
|
697
|
+
if int(src) not in to:
|
|
698
|
+
to.append(int(src))
|
|
699
|
+
|
|
700
|
+
def add_protocol(self, src):
|
|
701
|
+
to = self.protocol
|
|
702
|
+
if isinstance(src, list):
|
|
703
|
+
for x in src:
|
|
704
|
+
if x not in to:
|
|
705
|
+
to.append(x)
|
|
706
|
+
else:
|
|
707
|
+
if src not in to:
|
|
708
|
+
to.append(src)
|
|
709
|
+
|
|
710
|
+
def add_src_host(self, data):
|
|
711
|
+
self._add_addr(self.source_ips, data)
|
|
712
|
+
|
|
713
|
+
def add_dst_host(self, data):
|
|
714
|
+
self._add_addr(self.dest_ips, data)
|
|
715
|
+
|
|
716
|
+
def add_src_port(self, data):
|
|
717
|
+
self._add_port(self.source_ports, data)
|
|
718
|
+
|
|
719
|
+
def add_dst_port(self, data):
|
|
720
|
+
self._add_port(self.dest_ports, data)
|
|
721
|
+
|
|
722
|
+
def add_modify_between_comments(self, begin, end):
|
|
723
|
+
del self.modify_terms
|
|
724
|
+
self.modify_terms = []
|
|
725
|
+
self.bcomments.append((begin, end))
|
|
726
|
+
|
|
727
|
+
def add_modify_term(self, term):
|
|
728
|
+
del self.bcomments
|
|
729
|
+
self.bcomments = []
|
|
730
|
+
if term not in self.modify_terms:
|
|
731
|
+
self.modify_terms.append(term)
|
|
732
|
+
|
|
733
|
+
def get_protocols(self):
|
|
734
|
+
return self.protocol
|
|
735
|
+
|
|
736
|
+
def get_src_hosts(self):
|
|
737
|
+
return self.source_ips
|
|
738
|
+
|
|
739
|
+
def get_dst_hosts(self):
|
|
740
|
+
return self.dest_ips
|
|
741
|
+
|
|
742
|
+
def get_src_ports(self):
|
|
743
|
+
return self.source_ports
|
|
744
|
+
|
|
745
|
+
def get_dst_ports(self):
|
|
746
|
+
return self.dest_ports
|