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
|
@@ -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()
|