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,93 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ check_access - Determines whether access is permitted by a given ACL.
5
+ """
6
+
7
+ __version__ = "1.2"
8
+
9
+ import optparse
10
+ import sys
11
+
12
+ from simpleparse.error import ParserSyntaxError
13
+
14
+ from trigger.acl.parser import TIP, Comment, Protocol, parse
15
+ from trigger.acl.tools import check_access, create_trigger_term
16
+
17
+
18
+ def main():
19
+ """Main entry point for the CLI tool."""
20
+ optp = optparse.OptionParser(
21
+ description="""\
22
+ Determine whether access is permitted by a given ACL. Exits 0 if permitted,
23
+ 1 if edits are needed. Lists the terms that apply and what edits are needed.
24
+ Note that in order for the suggested edits feature to work, your policy must
25
+ end with an explicit deny.""",
26
+ usage="%prog [opts] file source dest [protocol [port]]",
27
+ )
28
+ optp.add_option("-q", "--quiet", action="store_true", help="suppress output")
29
+ (opts, args) = optp.parse_args()
30
+
31
+ if not 3 <= len(args) <= 5:
32
+ optp.error("not enough arguments")
33
+
34
+ acl_file = args[0]
35
+
36
+ source = dest = protocol = port = []
37
+
38
+ if args[1] == "any":
39
+ source = []
40
+ else:
41
+ source = [TIP(args[1])]
42
+ if args[2] == "any":
43
+ dest = []
44
+ else:
45
+ dest = [TIP(args[2])]
46
+
47
+ if len(args) <= 3:
48
+ protocol = []
49
+ port = []
50
+ elif len(args) == 4:
51
+ try:
52
+ protocol = [Protocol("tcp")]
53
+ port = [int(args[3])]
54
+ except ValueError:
55
+ protocol = [Protocol(args[3])]
56
+ port = []
57
+ else:
58
+ protocol = [Protocol(args[3])]
59
+ port = [int(args[4])]
60
+
61
+ new_term = create_trigger_term(
62
+ source_ips=source,
63
+ dest_ips=dest,
64
+ source_ports=[],
65
+ dest_ports=port,
66
+ protocols=protocol,
67
+ name="sr_____",
68
+ )
69
+ new_term.modifiers["count"] = "sr_____"
70
+ new_term.comments.append(Comment("check_access: ADD THIS TERM"))
71
+
72
+ try:
73
+ acl = parse(open(acl_file))
74
+ permitted = None
75
+ except ParserSyntaxError as e:
76
+ etxt = str(e).split()
77
+ sys.exit(f"Cannot parse {acl_file}:" + " ".join(etxt[1:]))
78
+
79
+ permitted = check_access(
80
+ acl.terms, new_term, opts.quiet, format=acl.format, acl_name=acl.name
81
+ )
82
+
83
+ if permitted and not opts.quiet:
84
+ print("No edits needed.")
85
+
86
+ if permitted:
87
+ sys.exit(0)
88
+ else:
89
+ sys.exit(1)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ check_syntax - Determines if ACL passes parsing check
5
+ """
6
+
7
+ __version__ = "1.0"
8
+
9
+ import optparse
10
+ import os
11
+ import sys
12
+ import tempfile
13
+
14
+ from twisted.python import log
15
+
16
+ from trigger.acl.parser import parse as acl_parse
17
+
18
+ CONTEXT = 3
19
+
20
+
21
+ def parse_args(argv):
22
+ optp = optparse.OptionParser(
23
+ description="""\
24
+ Determine if ACL file passes trigger's parsing checks.""",
25
+ usage="%prog [opts] file",
26
+ )
27
+ optp.add_option("-q", "--quiet", action="store_true", help="suppress output")
28
+ (opts, args) = optp.parse_args(argv)
29
+
30
+ return opts, args
31
+
32
+
33
+ def main():
34
+ """Main entry point for the CLI tool."""
35
+ global opts
36
+
37
+ tmpfile = tempfile.mktemp() + "_parsing_check"
38
+ log.startLogging(open(tmpfile, "a"), setStdout=False)
39
+ log.msg(
40
+ 'User %s (uid:%d) executed "%s"'
41
+ % (os.environ["LOGNAME"], os.getuid(), " ".join(sys.argv))
42
+ )
43
+
44
+ opts, args = parse_args(sys.argv)
45
+
46
+ for file in args[1:]:
47
+ if not os.path.exists(file):
48
+ print(f"Moving on. File does not exist: {file}")
49
+ continue
50
+ if not os.path.isfile(file):
51
+ print(f"Moving on. Not a normal file: {file}")
52
+ continue
53
+ # Calling `read()` on the fd immediately closes it
54
+ file_contents = open(file).read()
55
+
56
+ try:
57
+ acl_parse(file_contents)
58
+ print(f"File {file} passes the syntax check.")
59
+ except Exception as e:
60
+ print(f"File {file} FAILED the syntax check. Here is the error:")
61
+ print(e)
62
+ print("")
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()
trigger/bin/fe.py ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ fe - the File Editor. Uses RCS to maintain ACL policy versioning.
5
+ """
6
+
7
+ __version__ = "1.2.1"
8
+
9
+
10
+ import os
11
+ import re
12
+ import sys
13
+
14
+ from simpleparse.error import ParserSyntaxError
15
+
16
+ from trigger import acl
17
+ from trigger.acl.parser import TIP
18
+ from trigger.utils.cli import yesno
19
+
20
+ psa_checks = (
21
+ (lambda t: "source-port" in t.match, "source port"),
22
+ (lambda t: "tos" in t.match, "ToS"),
23
+ (lambda t: "precedence" in t.match, "precedence"),
24
+ (lambda t: "protocol" in t.match and "igmp" in t.match["protocol"], "IGMP"),
25
+ )
26
+
27
+
28
+ def gsr_checks(a):
29
+ """PSA and SALSA checks, if the ACL is tagged appropriately."""
30
+ ok = True
31
+ max_lines, psa = None, False
32
+ if [c for c in a.comments if "SALSA" in c]:
33
+ max_lines = 128
34
+ elif [c for c in a.comments if "PSA-128" in c]:
35
+ max_lines, psa = 128, True
36
+ elif [c for c in a.comments if "PSA-448" in c]:
37
+ max_lines, psa = 448, True
38
+ if max_lines and len(a.terms) > max_lines:
39
+ print("ACL has %d lines, max %d (GSR limit)" % (len(a.terms), max_lines))
40
+ ok = False
41
+ if psa:
42
+ for t in a.terms:
43
+ for check, reason in psa_checks:
44
+ if check(t):
45
+ print(f"Match on {reason} not permitted in PSA mode")
46
+ for line in t.output_ios():
47
+ print(" " + line)
48
+ ok = False
49
+ return ok
50
+
51
+
52
+ def normalize(a):
53
+ """Fix up the ACL, and return False if there are problems."""
54
+
55
+ if isinstance(a, acl.PolicerGroup):
56
+ return True
57
+
58
+ ok = True
59
+
60
+ # JunOS counter policy and duplicate term names.
61
+ if a.format == "junos":
62
+ names = set()
63
+ for t in a.terms:
64
+ if t.name in names:
65
+ print("Duplicate term name", t.name)
66
+ ok = False
67
+ else:
68
+ names.add(t.name)
69
+
70
+ # GSR checks.
71
+ if a.format == "ios": # not ios_named
72
+ if not gsr_checks(a):
73
+ ok = False
74
+
75
+ # Check for 10/8.
76
+ for t in a.terms:
77
+ for addr in ("address", "source-address", "destination-address"):
78
+ for block in t.match.get(addr, []):
79
+ if block == TIP("10.0.0.0/8"):
80
+ print("Matching on 10.0.0.0/8 is never correct")
81
+ for line in t.output(a.format):
82
+ print(" " + line)
83
+ ok = False
84
+
85
+ # Here is the place to put other policy checks; for example, we could
86
+ # check blue to green HTTP and make sure all those terms have a comment
87
+ # in a standardized format saying it was approved.
88
+
89
+ return ok
90
+
91
+
92
+ def edit(editfile):
93
+ """
94
+ Edits the file and calls normalize(). Loops until it passes or the user
95
+ confirms they want to bypass failed normalization. Returns a file object of
96
+ the diff output.
97
+ """
98
+ editor = os.environ.get("EDITOR", "vim")
99
+ if editor.startswith("vi"):
100
+ os.spawnlp(
101
+ os.P_WAIT, editor, editor, "+set sw=4", "+set softtabstop=4", editfile
102
+ )
103
+ else:
104
+ os.spawnlp(os.P_WAIT, editor, editor, editfile)
105
+
106
+ if os.path.basename(editfile): # .startswith('acl.'):
107
+ print("Normalizing ACL...")
108
+ a = None
109
+ try:
110
+ a = acl.parse(open(editfile))
111
+ except ParserSyntaxError as e:
112
+ print(e)
113
+ except TypeError as e:
114
+ print(e)
115
+ except Exception as e:
116
+ print("ACL parse failed: ", sys.exc_info()[0], ":", e)
117
+ if a and normalize(a):
118
+ output = "\n".join(a.output(replace=True)) + "\n"
119
+ open(editfile, "w").write(output)
120
+ else:
121
+ if yesno("Re-edit?"):
122
+ return edit(editfile)
123
+
124
+ # should use a list version of popen
125
+ return os.popen("rcsdiff -u -b -q " + editfile).read()
126
+
127
+
128
+ def main():
129
+ """Main entry point for the CLI tool."""
130
+ if len(sys.argv) < 2:
131
+ print("usage: fe files...", file=sys.stderr)
132
+ sys.exit(2)
133
+
134
+ for editfile in sys.argv[1:]:
135
+ try:
136
+ stat = os.stat(editfile)
137
+ except OSError:
138
+ stat = None
139
+
140
+ if not stat:
141
+ if yesno(f"{editfile} does not exist; create?", False):
142
+ edit(editfile)
143
+ os.spawnlp(os.P_WAIT, "ci", "ci", "-u", editfile)
144
+
145
+ else:
146
+ rv = os.spawnlp(os.P_WAIT, "co", "co", "-f", "-l", editfile)
147
+ if rv:
148
+ print("couldn't check out " + editfile, file=sys.stderr)
149
+ continue
150
+
151
+ diff = edit(editfile)
152
+ if not diff:
153
+ print("No changes made.")
154
+ os.spawnlp(os.P_WAIT, "rcs", "rcs", "-u", editfile)
155
+ # When no changes are made, ci leaves the file writable.
156
+ os.chmod(editfile, stat.st_mode & 0o555)
157
+ continue
158
+
159
+ print("")
160
+ print(f'"{os.path.basename(editfile)}"')
161
+ print("BEGINNING OF CHANGES========================")
162
+ print(diff, end="")
163
+ print("END OF CHANGES==============================")
164
+ print("")
165
+ print("")
166
+
167
+ if not yesno("Do you want to save changes?"):
168
+ print("Restoring original file")
169
+ os.spawnlp(os.P_WAIT, "co", "co", "-f", "-u", editfile)
170
+ continue
171
+
172
+ # Try to auto-detect log message from comments.
173
+ log = ""
174
+ pats = (re.compile(r"^\+.*!+(.*)"), re.compile(r"^\+.*/\*(.*)\*/"))
175
+ for line in diff.split("\n"):
176
+ for pat in pats:
177
+ m = pat.match(line)
178
+ if m:
179
+ msg = m.group(1).strip()
180
+ if msg:
181
+ log += m.group(1).strip() + "\n"
182
+ break
183
+ if log:
184
+ print("Autodetected log message:")
185
+ print(log)
186
+ print("")
187
+ if not yesno("Use this?"):
188
+ log = ""
189
+
190
+ if log:
191
+ os.spawnlp(os.P_WAIT, "ci", "ci", "-u", "-m" + log, editfile)
192
+ else:
193
+ os.spawnlp(os.P_WAIT, "ci", "ci", "-u", editfile)
194
+
195
+
196
+ if __name__ == "__main__":
197
+ main()
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ find_access - Like check_access but reports on networks inside of networks.
5
+ """
6
+
7
+ __version__ = "1.6"
8
+
9
+ import sys
10
+ from optparse import OptionParser
11
+
12
+ from simpleparse.error import ParserSyntaxError
13
+
14
+ from trigger.acl.parser import TIP, parse
15
+
16
+
17
+ def parse_args(argv):
18
+ parser = OptionParser(
19
+ usage="%prog [options] [acls]",
20
+ add_help_option=False,
21
+ description="a more detailed varient of check_access.",
22
+ )
23
+ desc = """
24
+ This is used more for reporting purposes. Currently check_access will do the
25
+ right thing for finding access that is requested, but lacks the ability to
26
+ report on networks inside networks.
27
+
28
+ For example if an administrator ran the command:
29
+ check_access /netsec/firewalls/acl.131mj 172.16.0.0/12 any
30
+
31
+ This would only show terms where the source address was specifically
32
+ 172.168.0.0/12 but nothing smaller. This is due to the fact that if
33
+ an engineer wanted to add new access for this range - this wouldn't
34
+ actually be the correct access to add.
35
+
36
+ This script looks in a slighty more detailed manner. If the input
37
+ address is 172.16.1.0/24 and a term contains 172.16.1.5/32 that access
38
+ would be reported on.
39
+
40
+ This works in reverse, if the input is 172.16.1.0/24 and a term contains
41
+ 172.16.0.0/16 this access is reported.\n"""
42
+
43
+ parser.add_option("-h", "--help", action="store_true")
44
+ parser.add_option("-s", "--source-network", help="Supply a source network to find")
45
+ parser.add_option(
46
+ "-d", "--destination-network", help="Supply a destination network to find"
47
+ )
48
+ parser.add_option(
49
+ "-p",
50
+ "--ports",
51
+ help="Specify a set of ports comma seperated, allows for ranges",
52
+ )
53
+ parser.add_option(
54
+ "-S",
55
+ "--no-any-source",
56
+ action="store_true",
57
+ help='Do not include terms with source-address of "any"',
58
+ )
59
+ parser.add_option(
60
+ "-D",
61
+ "--no-any-destination",
62
+ action="store_true",
63
+ help='Do not include terms with destination-address of "any"',
64
+ )
65
+
66
+ opts, args = parser.parse_args(argv)
67
+
68
+ if opts.help:
69
+ parser.print_help()
70
+ sys.exit(desc)
71
+
72
+ return opts, args
73
+
74
+
75
+ def match_term(term, data, type, opts):
76
+ # If any source/dest, return False
77
+ if opts.no_any_source and any_source(term):
78
+ return False
79
+ if opts.no_any_destination and any_dest(term):
80
+ return False
81
+
82
+ # If no input data or term field src/dst is any...
83
+ if not data or not term.match.has_key(type):
84
+ return True
85
+
86
+ if "port" in type:
87
+ for port in data:
88
+ if port in term.match[type]:
89
+ return True
90
+ return False
91
+
92
+ for data_in_term in term.match[type]:
93
+ for data_entry in data:
94
+ if data_entry in data_in_term or data_in_term in data_entry:
95
+ return True
96
+
97
+ return False
98
+
99
+
100
+ def match_terms(acl, sources, dests, ports, opts):
101
+ matched = []
102
+
103
+ for term in acl.terms:
104
+ matched_sources = False
105
+ matched_dests = False
106
+ matched_ports = False
107
+
108
+ matched_sources = match_term(term, sources, "source-address", opts)
109
+ matched_dests = match_term(term, dests, "destination-address", opts)
110
+ matched_ports = match_term(term, ports, "destination-port", opts)
111
+
112
+ if matched_sources and matched_dests and matched_ports:
113
+ matched.append(term)
114
+
115
+ return matched
116
+
117
+
118
+ def permits_from_any(term):
119
+ """Returns True if action is "accept" and term has no 'source-address'"""
120
+ return term.action[0] == "accept" and not term.match.get("source-address")
121
+
122
+
123
+ def any_source(term):
124
+ """Returns True term has no 'source-address'"""
125
+ return not term.match.get("source-address")
126
+
127
+
128
+ def any_dest(term):
129
+ """Returns True term has no 'destination-address'"""
130
+ return not term.match.get("destination-address")
131
+
132
+
133
+ def do_work(acl_files, opts):
134
+ acl_file_data = {}
135
+ sources = []
136
+ dests = []
137
+ ports = []
138
+
139
+ if opts.source_network:
140
+ for x in opts.source_network.split(","):
141
+ sources.append(TIP(x))
142
+
143
+ if opts.destination_network:
144
+ for x in opts.destination_network.split(","):
145
+ dests.append(TIP(x))
146
+
147
+ if opts.ports:
148
+ for x in opts.ports.split(","):
149
+ ports.append(int(x))
150
+
151
+ for acl_file in acl_files:
152
+ try:
153
+ acl = parse(open(acl_file))
154
+ except ParserSyntaxError as e:
155
+ etxt = str(e).split()
156
+ sys.exit(etxt)
157
+
158
+ matching_terms = match_terms(acl, sources, dests, ports, opts)
159
+
160
+ acl.filename = acl_file # Store this for a hot minute
161
+ acl.terms = [] # Nuke this in case it's huge
162
+ acl_file_data[acl] = matching_terms
163
+
164
+ return acl_file_data
165
+
166
+
167
+ def print_report(data):
168
+ for aclobj, terms in data.items():
169
+ print(aclobj.filename)
170
+ print("=================================================")
171
+ for term in terms:
172
+ for o in term.output(format=aclobj.format, acl_name=aclobj.name):
173
+ print(o)
174
+ print("")
175
+
176
+
177
+ def main():
178
+ """Main entry point for the CLI tool."""
179
+ opts, args = parse_args(sys.argv)
180
+
181
+ acls_to_check = args[1:]
182
+
183
+ if not opts.source_network and not opts.destination_network or not acls_to_check:
184
+ sys.exit("ERROR: No source or destination networks defined. Try -h for help.")
185
+
186
+ data = do_work(acls_to_check, opts)
187
+ print_report(data)
188
+
189
+
190
+ if __name__ == "__main__":
191
+ main()