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.
Files changed (61) hide show
  1. trigger/__init__.py +7 -0
  2. trigger/acl/__init__.py +32 -0
  3. trigger/acl/autoacl.py +70 -0
  4. trigger/acl/db.py +324 -0
  5. trigger/acl/dicts.py +357 -0
  6. trigger/acl/grammar.py +112 -0
  7. trigger/acl/ios.py +222 -0
  8. trigger/acl/junos.py +422 -0
  9. trigger/acl/models.py +118 -0
  10. trigger/acl/parser.py +168 -0
  11. trigger/acl/queue.py +296 -0
  12. trigger/acl/support.py +1431 -0
  13. trigger/acl/tools.py +746 -0
  14. trigger/bin/__init__.py +0 -0
  15. trigger/bin/acl.py +233 -0
  16. trigger/bin/acl_script.py +574 -0
  17. trigger/bin/aclconv.py +82 -0
  18. trigger/bin/check_access.py +93 -0
  19. trigger/bin/check_syntax.py +66 -0
  20. trigger/bin/fe.py +197 -0
  21. trigger/bin/find_access.py +191 -0
  22. trigger/bin/gnng.py +434 -0
  23. trigger/bin/gong.py +86 -0
  24. trigger/bin/load_acl.py +841 -0
  25. trigger/bin/load_config.py +18 -0
  26. trigger/bin/netdev.py +317 -0
  27. trigger/bin/optimizer.py +638 -0
  28. trigger/bin/run_cmds.py +18 -0
  29. trigger/changemgmt/__init__.py +352 -0
  30. trigger/changemgmt/bounce.py +57 -0
  31. trigger/cmds.py +1217 -0
  32. trigger/conf/__init__.py +94 -0
  33. trigger/conf/global_settings.py +674 -0
  34. trigger/contrib/__init__.py +7 -0
  35. trigger/exceptions.py +307 -0
  36. trigger/gorc.py +172 -0
  37. trigger/netdevices/__init__.py +1288 -0
  38. trigger/netdevices/loader.py +174 -0
  39. trigger/netscreen.py +1030 -0
  40. trigger/packages/__init__.py +6 -0
  41. trigger/packages/peewee.py +8084 -0
  42. trigger/rancid.py +463 -0
  43. trigger/tacacsrc.py +584 -0
  44. trigger/twister.py +2203 -0
  45. trigger/twister2.py +745 -0
  46. trigger/utils/__init__.py +88 -0
  47. trigger/utils/cli.py +349 -0
  48. trigger/utils/importlib.py +77 -0
  49. trigger/utils/network.py +157 -0
  50. trigger/utils/rcs.py +178 -0
  51. trigger/utils/templates.py +81 -0
  52. trigger/utils/url.py +78 -0
  53. trigger/utils/xmltodict.py +298 -0
  54. trigger-2.0.0.dist-info/METADATA +146 -0
  55. trigger-2.0.0.dist-info/RECORD +61 -0
  56. trigger-2.0.0.dist-info/WHEEL +5 -0
  57. trigger-2.0.0.dist-info/entry_points.txt +15 -0
  58. trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
  59. trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
  60. trigger-2.0.0.dist-info/top_level.txt +2 -0
  61. twisted/plugins/trigger_xmlrpc.py +124 -0
@@ -0,0 +1,638 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ optimizer - ACL Optimizer
5
+
6
+ Optimizes filters (usually best on Juniper filters) using
7
+ various algorithms to determine which filters can be merged
8
+ and removed.
9
+ """
10
+
11
+ __version__ = "1.5"
12
+
13
+ import copy
14
+ import logging
15
+ import signal
16
+ import sys
17
+ import time
18
+ from optparse import OptionParser
19
+
20
+ from simpleparse.error import ParserSyntaxError
21
+
22
+ from trigger.acl.parser import ACL, TIP, Comment, parse
23
+
24
+ stop_all = False
25
+
26
+ # Logger
27
+ logging.basicConfig(
28
+ level=logging.INFO, format="%(asctime)s [%(levelname)s]: %(message)s"
29
+ )
30
+ log = logging.getLogger(__name__)
31
+
32
+
33
+ def sig_handler(s, d):
34
+ global stop_all
35
+ stop_all = True
36
+
37
+
38
+ def parse_args(argv):
39
+ parser = OptionParser(
40
+ usage="%prog [options] [acls]",
41
+ description="""ACL Optimizer
42
+
43
+ Optimizes filters (usually best on Juniper filters) using
44
+ various algorithms to determine which filters can be merged
45
+ and removed.
46
+
47
+ There are several phases of optimization which include source
48
+ address optimization, destination address optimization, and
49
+ destination port optimization.
50
+
51
+ By default the optimizer will continue to optimize until the
52
+ no more optimizations can be made.
53
+
54
+ This can be very time consuming if run on a very large acl.
55
+ It is suggested that the focus argument should be used if
56
+ such an acl is needed to be optimized.
57
+
58
+ This will output to a file: <original_acl_filename>.optimized
59
+ """,
60
+ )
61
+
62
+ parser.add_option(
63
+ "-p",
64
+ "--passes",
65
+ type="int",
66
+ default=0,
67
+ help="""The number of passes the optimizer should make.
68
+
69
+ By defaut the optimizer will continue to make more passes until
70
+ no more optimizations can be made. Specify this to limit this.
71
+ """,
72
+ )
73
+ parser.add_option(
74
+ "-f",
75
+ "--focus",
76
+ type="int",
77
+ default=0,
78
+ help="""Focus on a specific set of terms based on the
79
+ number of destination ports found. This will count the number
80
+ of destination ports, and if the port count is over X, the terms
81
+ in which this port was found will be accounted for in its optimization
82
+ checks. All other terms will be left alone. By default this feature
83
+ is set to 0 (or off).
84
+ """,
85
+ )
86
+
87
+ parser.add_option(
88
+ "",
89
+ "--no-source-ip",
90
+ action="store_true",
91
+ help="This will turn off the source-ip optimization",
92
+ )
93
+ parser.add_option(
94
+ "",
95
+ "--no-destination-ip",
96
+ action="store_true",
97
+ help="This will turn off the destination-ip optimization",
98
+ )
99
+ parser.add_option(
100
+ "",
101
+ "--no-destination-port",
102
+ action="store_true",
103
+ help="This will turn off the destination-port optimization",
104
+ )
105
+ parser.add_option(
106
+ "-v", "--verbose", action="store_true", help="Turn on verbose/debug output"
107
+ )
108
+ parser.add_option(
109
+ "",
110
+ "--no-expires",
111
+ action="store_true",
112
+ help="If a term includes an expire date, mark non-eligible for optimize",
113
+ )
114
+
115
+ parser.add_option(
116
+ "-d",
117
+ "--debug",
118
+ action="store_true",
119
+ help="Warning: this is very noisy. It will display every action"
120
+ "from the optimization process.",
121
+ )
122
+
123
+ opts, args = parser.parse_args(argv)
124
+
125
+ return opts, args
126
+
127
+
128
+ class ProgressMeter:
129
+ def __init__(self, **kw):
130
+ # What time do we start tracking our progress from?
131
+ self.timestamp = kw.get("timestamp", time.time())
132
+ # What kind of unit are we tracking?
133
+ self.unit = str(kw.get("unit", ""))
134
+ # Number of units to process
135
+ self.total = int(kw.get("total", 100))
136
+ # Number of units already processed
137
+ self.count = int(kw.get("count", 0))
138
+ # Refresh rate in seconds
139
+ self.rate_refresh = float(kw.get("rate_refresh", 0.5))
140
+ # Number of ticks in meter
141
+ self.meter_ticks = int(kw.get("ticks", 60))
142
+ self.meter_division = float(self.total) / self.meter_ticks
143
+ self.meter_value = int(self.count / self.meter_division)
144
+ self.last_update = None
145
+ self.rate_history_idx = 0
146
+ self.rate_history_len = 10
147
+ self.rate_history = [None] * self.rate_history_len
148
+ self.rate_current = 0.0
149
+ self.last_refresh = 0
150
+ self.prev_meter_len = 0
151
+
152
+ def update(self, count, **kw):
153
+ now = time.time()
154
+ # Caclulate rate of progress
155
+ rate = 0.0
156
+ # Add count to Total
157
+ self.count += count
158
+ self.count = min(self.count, self.total)
159
+ if self.last_update:
160
+ delta = now - float(self.last_update)
161
+ if delta:
162
+ rate = count / delta
163
+ else:
164
+ rate = count
165
+ self.rate_history[self.rate_history_idx] = rate
166
+ self.rate_history_idx += 1
167
+ self.rate_history_idx %= self.rate_history_len
168
+ cnt = 0
169
+ total = 0.0
170
+ # Average rate history
171
+ for rate in self.rate_history:
172
+ if rate is None:
173
+ continue
174
+ cnt += 1
175
+ total += rate
176
+ rate = total / cnt
177
+ self.rate_current = rate
178
+ self.last_update = now
179
+ # Device Total by meter division
180
+ value = int(self.count / self.meter_division)
181
+ if value > self.meter_value:
182
+ self.meter_value = value
183
+ if self.last_refresh:
184
+ if (now - self.last_refresh) > self.rate_refresh or (
185
+ self.count >= self.total
186
+ ):
187
+ self.refresh()
188
+ else:
189
+ self.refresh()
190
+
191
+ def get_meter(self, **kw):
192
+ bar = "-" * self.meter_value
193
+ pad = " " * (self.meter_ticks - self.meter_value)
194
+ perc = (float(self.count) / self.total) * 100
195
+ return "[%s>%s] %d%% %.1f/sec" % (bar, pad, perc, self.rate_current)
196
+
197
+ def refresh(self, **kw):
198
+ # Clear line and return cursor to start-of-line
199
+ sys.stderr.write(" " * self.prev_meter_len + "\x08" * self.prev_meter_len)
200
+ # Get meter text
201
+ meter_text = self.get_meter(**kw)
202
+ # Write meter and return cursor to start-of-line
203
+ sys.stderr.write(meter_text + "\x08" * len(meter_text))
204
+ self.prev_meter_len = len(meter_text)
205
+
206
+ # Are we finished?
207
+ if self.count >= self.total:
208
+ sys.stderr.write("\n")
209
+ sys.stderr.flush()
210
+ # Timestamp
211
+ self.last_refresh = time.time()
212
+
213
+
214
+ def focus_terms(pcount, terms):
215
+ """
216
+ Generates a list of term names that have a port count
217
+ greater than pcount for the optimizer to 'focus' in on.
218
+ """
219
+ focused = dict()
220
+ matched_ports = dict()
221
+ ports = dict()
222
+
223
+ for term in terms:
224
+ if "source-port" in term.match:
225
+ continue
226
+
227
+ if "destination-port" not in term.match:
228
+ continue
229
+
230
+ for port in term.match["destination-port"]:
231
+ if port == 0:
232
+ continue
233
+
234
+ if port in ports:
235
+ ports[port] += 1
236
+ else:
237
+ ports[port] = 1
238
+
239
+ for port, cnt in ports.items():
240
+ if cnt >= pcount:
241
+ log.info("port %s had a count of %d" % (str(port), cnt))
242
+ matched_ports[port] = 1
243
+
244
+ for term in terms:
245
+ if "destination-port" not in term.match:
246
+ continue
247
+ if "source-port" in term.match:
248
+ continue
249
+
250
+ for tport in term.match["destination-port"]:
251
+ if tport in matched_ports:
252
+ focused[term.name] = 1
253
+ break
254
+
255
+ log.info("%d focused terms" % len(focused))
256
+ return focused
257
+
258
+
259
+ chk_keys = ["protocol", "source-address", "destination-address", "destination-port"]
260
+
261
+ rej_keys = ["reject", "deny", "discard"]
262
+
263
+
264
+ def optimize_terms(terms, focused, which, opts):
265
+ global stop_all
266
+ to_delete = dict()
267
+ other = ""
268
+ total = 0
269
+
270
+ total = len(terms)
271
+
272
+ if which == "source-address":
273
+ other = ["destination-address"]
274
+ elif which == "destination-address":
275
+ other = ["source-address"]
276
+ else:
277
+ # this is used primarily for port optimization
278
+ other = ["source-address", "destination-address"]
279
+
280
+ meter = ProgressMeter(total=total)
281
+
282
+ for term1 in terms:
283
+ if stop_all:
284
+ break
285
+
286
+ meter.update(1)
287
+
288
+ if focused and term1.name not in focused:
289
+ continue
290
+
291
+ dont_merge = False
292
+ if opts.no_expires:
293
+ for c in term1.comments:
294
+ if "UNTIL" in c and "Never" not in c:
295
+ dont_merge = True
296
+
297
+ if dont_merge:
298
+ continue
299
+
300
+ # make sure that there are not any source-ports
301
+ # defined in term1
302
+ if (
303
+ "destination-port" not in term1.match
304
+ or "source-port" in term1.match
305
+ or term1.action[0] in rej_keys
306
+ or term1 in to_delete
307
+ ):
308
+ continue
309
+
310
+ for term2 in terms:
311
+ breaker = False
312
+
313
+ if stop_all:
314
+ break
315
+
316
+ log.debug(
317
+ "Comparing term %s to term %s [%d terms deleted]"
318
+ % (term1.name, term2.name, len(to_delete))
319
+ )
320
+ if focused and term2.name not in focused:
321
+ continue
322
+
323
+ if opts.no_expires:
324
+ for c in term2.comments:
325
+ if "UNTIL" in c and "Never" not in c:
326
+ dont_merge = True
327
+
328
+ if dont_merge:
329
+ continue
330
+
331
+ # check to make sure that neither term
332
+ # has been marked for deletion. Also
333
+ # check to make sure we're not comparing
334
+ # the same terms, and this is not a
335
+ # rejected action.
336
+ if (
337
+ term1.name in to_delete
338
+ or term2.name in to_delete
339
+ or term1.name == term2.name
340
+ or term2.action[0] in rej_keys
341
+ or "source-port" in term2.match
342
+ ):
343
+ continue
344
+
345
+ # check to make sure both terms include somethin
346
+ # that can be matched.
347
+ for key in chk_keys:
348
+ if key not in term1.match or key not in term2.match:
349
+ breaker = True
350
+ break
351
+
352
+ if breaker:
353
+ continue
354
+
355
+ # make sure that both protocols match up.
356
+ if len(term1.match["protocol"]) != len(term2.match["protocol"]):
357
+ continue
358
+
359
+ if "icmp" in term1.match["protocol"] or "icmp" in term2.match["protocol"]:
360
+ break
361
+
362
+ for proto in term1.match["protocol"]:
363
+ if proto not in term2.match["protocol"]:
364
+ breaker = True
365
+ break
366
+
367
+ if breaker:
368
+ continue
369
+
370
+ # we don't do this check if we are optimizing destination-ports
371
+ if which != "destination-port":
372
+ # make sure that both destination-ports match up.
373
+ if len(term1.match["destination-port"]) != len(
374
+ term2.match["destination-port"]
375
+ ):
376
+ breaker = True
377
+ continue
378
+
379
+ for port in term1.match["destination-port"]:
380
+ for port2 in term2.match["destination-port"]:
381
+ if port != port2:
382
+ breaker = True
383
+ if breaker:
384
+ break
385
+ if breaker:
386
+ break
387
+
388
+ if breaker:
389
+ continue
390
+
391
+ for ent in other:
392
+ # check to make sure that the other side
393
+ # has the IP's from term1 to term2
394
+ len1 = len(term1.match[ent])
395
+ len2 = len(term2.match[ent])
396
+
397
+ if len1 != len2:
398
+ breaker = True
399
+ break
400
+
401
+ matches = [x for x in term1.match[ent] if x in term2.match[ent]]
402
+
403
+ if len(matches) != len1:
404
+ breaker = True
405
+ break
406
+ matches = [x for x in term2.match[ent] if x in term1.match[ent]]
407
+
408
+ if len(matches) != len2:
409
+ breaker = True
410
+ break
411
+
412
+ if breaker:
413
+ continue
414
+
415
+ # append old comments
416
+ for comment in term1.comments:
417
+ term2.comments.append(comment)
418
+
419
+ ips = []
420
+ for to_add in term1.match[which]:
421
+ term2.match[which].append(to_add)
422
+ ips.append(str(to_add))
423
+
424
+ cmt = Comment(
425
+ "merged [({}) {}] from {}".format(which, ",".join(ips), term1.name)
426
+ )
427
+
428
+ term2.comments.append(cmt)
429
+
430
+ to_delete[term1.name] = 1
431
+
432
+ new_terms = []
433
+ for term in terms:
434
+ if term.name not in to_delete:
435
+ new_terms.append(term)
436
+
437
+ return new_terms
438
+
439
+
440
+ def terms_unchunk(chunks):
441
+ terms = []
442
+
443
+ for chunk in chunks:
444
+ for term in chunk:
445
+ terms.append(term)
446
+
447
+ return terms
448
+
449
+
450
+ def terms_chunker(terms):
451
+ """
452
+ In order to achieve true optimization we must break our filter up
453
+ into chunks that are the aggregate of the same modifier. What this
454
+ means is if we have the following term structure:
455
+
456
+ term1 { accept }
457
+ term2 { accept }
458
+ term3 { accept }
459
+ term4 { deny }
460
+ term5 { deny }
461
+ term6 { accept }
462
+ term7 { accept }
463
+ term8 { deny }
464
+
465
+ We would break it up as so:
466
+
467
+ chunks = [ [term1, term2, term3], [term4, term5], [term6, term7, term8] ]
468
+
469
+ We then only optimize on chunks of terms so that we don't accidently
470
+ optimize something accepted above a deny to an accept below the deny.
471
+ """
472
+ ret = []
473
+ current_chunk = []
474
+
475
+ total = len(terms)
476
+ meter = ProgressMeter(total=total)
477
+ current_modifier = None
478
+
479
+ for term in terms:
480
+ meter.update(1)
481
+ if current_modifier is None:
482
+ current_modifier = term.action[0]
483
+ if current_modifier != term.action[0]:
484
+ ret.append(copy.copy(current_chunk))
485
+ current_chunk = []
486
+ current_modifier = term.action[0]
487
+
488
+ current_chunk.append(term)
489
+
490
+ ret.append(copy.copy(current_chunk))
491
+
492
+ return ret
493
+
494
+
495
+ def optimize(opts, terms, focused):
496
+ global stop_all
497
+
498
+ terms_old = terms
499
+ mtypes = []
500
+
501
+ if not opts.no_source_ip:
502
+ mtypes.append("source-address")
503
+ if not opts.no_destination_ip:
504
+ mtypes.append("destination-address")
505
+ if not opts.no_destination_port:
506
+ mtypes.append("destination-port")
507
+
508
+ for term in terms:
509
+ for mtype in mtypes:
510
+ if mtype == "destination-port":
511
+ continue
512
+ if mtype not in term.match:
513
+ term.match[mtype] = [TIP("0.0.0.0/0")]
514
+
515
+ chunks = terms_chunker(terms)
516
+
517
+ for type in mtypes:
518
+ if stop_all:
519
+ return terms
520
+
521
+ chunk_count = 0
522
+
523
+ for chunk in chunks:
524
+ log.info("Optimizing %s [Chunk %d]" % (type, chunk_count))
525
+ chunks[chunk_count] = optimize_terms(chunk, focused, type, opts)
526
+ log.info("TCount: %d/%d" % (len(terms_old), len(terms)))
527
+ chunk_count += 1
528
+
529
+ terms = terms_unchunk(chunks)
530
+ return terms
531
+
532
+
533
+ def do_work(opts, files):
534
+ global stop_all
535
+ for acl_file in files:
536
+ focused = None
537
+ out_file = acl_file + ".optimized"
538
+
539
+ if stop_all:
540
+ return
541
+
542
+ log.info(f"Parsing {acl_file}")
543
+
544
+ try:
545
+ acl = parse(open(acl_file))
546
+ except ParserSyntaxError as e:
547
+ etxt = str(e).split()
548
+ log.error(etxt)
549
+ return
550
+
551
+ log.info("Done parsing")
552
+
553
+ len(acl.terms)
554
+
555
+ if opts.focus:
556
+ log.info("Finding focused terms")
557
+ focused = focus_terms(opts.focus, acl.terms)
558
+ if not focused:
559
+ log.warn(f"No focused terms could be found in acl {acl_file}")
560
+ continue
561
+
562
+ log.info("Done focused term")
563
+
564
+ passes = 1
565
+
566
+ terms_old = acl.terms
567
+
568
+ # destination ports should always be optimized LAST
569
+ real_port_optimize_opt = opts.no_destination_port
570
+ # first set to none
571
+ opts.no_destination_port = True
572
+
573
+ while True:
574
+ if stop_all:
575
+ break
576
+
577
+ log.info("Optimization pass %d" % passes)
578
+ terms = optimize(opts, terms_old, focused)
579
+
580
+ if opts.passes and passes >= opts.passes:
581
+ break
582
+
583
+ if len(terms_old) == len(terms):
584
+ break
585
+
586
+ terms_old = terms
587
+ passes += 1
588
+
589
+ terms_old = terms
590
+ if not real_port_optimize_opt:
591
+ passes = 1
592
+ opts.no_destination_port = False
593
+ opts.no_source_ip = True
594
+ opts.no_destination_ip = True
595
+ while True:
596
+ if stop_all:
597
+ break
598
+
599
+ log.info("PORT Optimization pass %d" % passes)
600
+ terms = optimize(opts, terms_old, focused)
601
+
602
+ if opts.passes and passes >= opts.passes:
603
+ break
604
+
605
+ if len(terms_old) == len(terms):
606
+ break
607
+
608
+ terms_old = terms
609
+ passes += 1
610
+
611
+ new_acl = ACL()
612
+ new_acl.policers = acl.policers
613
+ new_acl.format = acl.format
614
+ new_acl.comments = acl.comments
615
+ new_acl.name = acl.name
616
+ new_acl.terms = terms
617
+
618
+ out = open(out_file, "w")
619
+
620
+ for x in new_acl.output(replace=True):
621
+ print(x, file=out)
622
+
623
+
624
+ def main():
625
+ """Main entry point for the CLI tool."""
626
+ opts, args = parse_args(sys.argv)
627
+ if opts.debug:
628
+ log.setLevel(logging.DEBUG)
629
+
630
+ acl_files = args[1:]
631
+
632
+ signal.signal(signal.SIGINT, sig_handler)
633
+
634
+ do_work(opts, acl_files)
635
+
636
+
637
+ if __name__ == "__main__":
638
+ main()
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Uses Trigger libraries to run commands on network devices.
5
+
6
+ Please see `~trigger.contrib.docommand.CommandRunner` for details.
7
+ """
8
+
9
+ from trigger.contrib import docommand
10
+
11
+
12
+ def main():
13
+ """Main entry point for the CLI tool."""
14
+ docommand.main(action_class=docommand.CommandRunner)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ main()