yaig

yaig Commit Details


Date:2015-06-08 21:46:46 (9 years 6 months ago)
Author:Natalie Adams
Branch:master
Commit:90ea3a8c5eba8a61595e34badde53fd85def39e9
Message:initial commit

Changes:

File differences

README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# yaig - yet another iptables generator
yaig is a tool to make managing iptables rules easier. yaig is NOT a replacement for
iptables or a wrapper for iptables. It is designed to take a config file that has simple
allow/deny rule sets and emit iptables rules. You will need to decide what and where you
want to put the rules.
In my environment I wrote a script that takes the iptables rules and plugs them into
the related iptables config files that are managed by a service. The rules are stored
in a git repository which I have another script in crontab perform a pull every X
minutes and run yaig against the ruleset that was generated from git. This way I
maintain a file in git and the servers get the new iptables in X minutes.
yaig config files must start with:
version 1
The config file syntax is loosely based on Zyxel firewall config.
For example - you must define everything as an object:
object local127.0.0.1
But then you can create a group out of those objects (and this is where the power of yaig is):
group shodan
object shodan1
object shodan-io2
object shodan-io3
If you were writing iptables rules you would have to write:
-A INPUT -s x.x.x.x -j DROP
several times. With yaig to block a group you simple write:
server group shodan drop
Of course you can write a rule for a specific object like this:
server object local accept
If you run yaig on the sample config file it will emit this:
-A INPUT -s 198.20.69.96/29 -j DROP -m comment --comment "server - Group: shodan - drop"
-A INPUT -s 66.240.192.0/18 -j DROP -m comment --comment "server - Group: shodan - drop"
-A INPUT -s 71.6.128.0/17 -j DROP -m comment --comment "server - Group: shodan - drop"
-A INPUT -s 127.0.0.1 -j ACCEPT -m comment --comment "server - Object: local - accept"
In my opinion it's much easier for a human to maintain a group rather than individual rules.
(Arguably that's how organizations are laid out - you have an HR department, web development
department, desktop support etc. With yaig you can grant/deny each department with the change
of a single word rather than using vi and using search and replace.)
You also get the added benefit that the comment shows some information on what the rule is.
The comments will also appear when you run:
iptables --list
There is also some feature creep I added that I will document later.
## Other information
You can start a line with # and it will be treated as a comment
sample.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version 1
object local127.0.0.1
object shodan1 198.20.69.96/29
object shodan-io2 66.240.192.0/18
object shodan-io3 71.6.128.0/17
group shodan
object shodan1
object shodan-io2
object shodan-io3
server group shodan drop
server object local accept
yaig.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import re
import sys
import smtplib
import socket
# Pythonic way to do enums:
# http://stackoverflow.com/a/1695250/195722
def enum(*sequential, **named):
enums = dict(zip(sequential, range(len(sequential))), **named)
reverse = dict((value, key) for key, value in enums.iteritems())
enums['val'] = reverse
return type('Enum', (), enums)
# http://stackoverflow.com/a/36061/195722
class Struct:
def __init__ (self, *argv, **argd):
if len(argd):
# Update by dictionary
self.__dict__.update (argd)
else:
# Update by position
attrs = filter (lambda x: x[0:2] != "__", dir(self))
for n in range(len(argv)):
setattr(self, attrs[n], argv[n])
def type(self):
return self.__class__.__name__
def __repr__(self):
return str(self)
NET_DIRECTION = enum("SERVER", "CLIENT")
FIREWALL_ACTION = enum("ACCEPT", "DROP", "REJECT")
NET_SOURCE = enum("ADDRESS", "GROUP")
TYPES = enum("GROUP", "ADDR", "PROTO")
class RuleStruct(Struct):
direction = None
source = None
action = FIREWALL_ACTION.DROP
def __str__(self):
if self.direction == NET_DIRECTION.SERVER:
dir = "server"
if self.action == FIREWALL_ACTION.DROP:
act = "drop"
elif self.action == FIREWALL_ACTION.ACCEPT:
act = "accept"
elif self.action == FIREWALL_ACTION.REJECT:
act = "reject"
return "%s - %s - %s" % (dir, self.source, act)
class ObjectType(Struct):
object_type = None
value = None
def __str__(self):
if self.object_type == TYPES.GROUP:
return "Group: %s" % (self.value)
elif self.object_type == TYPES.ADDR:
return "Object: %s" % (self.value)
else:
return "Proto: %s" % (self.value)
# based on FireHOL and Zyxel Zywall Firewall
try:
f = open(sys.argv[1])
except:
f = open("firewall.txt")
lines = f.readlines()
f.close()
versioninfo = lines[0].split(' ')[1]
if versioninfo.strip() != "1":
raise Exception("I don't understand this version " + versioninfo)
PARSER_STATES = enum("GLOBAL", "GROUP_DEF", "IFACE_DEF")
CURRENT_STATE = PARSER_STATES.GLOBAL
object_defs = {}
group_defs = {}
iface_defs = {}
global_defs = []
generated_ruleset = []
current_group = ""
current_iface = ""
def getIPsInGroup(group):
global group_defs
global object_defs
returnlst = []
for i in group_defs[group]:
if i.object_type == TYPES.GROUP:
returnlst.extend(getIPsInGroup(i.value))
elif i.object_type == TYPES.ADDR:
returnlst.append(object_defs[i.value])
return returnlst
try:
for line in lines[1:]:
if len(line) > 0 and line[0] == "#": # comment
continue
parts = re.split("\s+", line.strip())
if (len(parts) == 1): # blank line
CURRENT_STATE = PARSER_STATES.GLOBAL
continue
if parts[0] == "object":
if CURRENT_STATE == PARSER_STATES.GLOBAL:
object_defs[parts[1]] = parts[2]
elif CURRENT_STATE == PARSER_STATES.GROUP_DEF:
group_defs[current_group].append(ObjectType(object_type=TYPES.ADDR, value=parts[1])) # initilize the dct on group entry...
elif parts[0] == "group":
if CURRENT_STATE == PARSER_STATES.GROUP_DEF:
group_defs[current_group].append(ObjectType(object_type=TYPES.GROUP, value=parts[1]))
else:
CURRENT_STATE = PARSER_STATES.GROUP_DEF
current_group = parts[1]
if current_group not in group_defs:
group_defs[current_group] = []
elif parts[0] == "iface":
CURRENT_STATE = PARSER_STATES.IFACE_DEF
current_iface = parts[1]
if current_iface not in iface_defs:
iface_defs[current_iface] = []
elif parts[0] == "server":
source = None
if parts[1] == "group":
source = ObjectType(object_type=TYPES.GROUP, value=parts[2])
elif parts[1] == "object":
source = ObjectType(object_type=TYPES.ADDR, value=parts[2])
elif parts[1] == "proto":
source = ObjectType(object_type=TYPES.PROTO, value=parts[2])
if CURRENT_STATE == PARSER_STATES.GLOBAL:
if parts[3] == "drop":
global_defs.append(RuleStruct(direction=NET_DIRECTION.SERVER, source=source, action=FIREWALL_ACTION.DROP))
elif parts[3] == "accept":
global_defs.append(RuleStruct(direction=NET_DIRECTION.SERVER, source=source, action=FIREWALL_ACTION.ACCEPT))
elif parts[3] == "reject":
global_defs.append(RuleStruct(direction=NET_DIRECTION.SERVER, source=source, action=FIREWALL_ACTION.REJECT))
elif CURRENT_STATE == PARSER_STATES.IFACE_DEF:
if parts[3] == "drop":
iface_defs[current_iface].append(RuleStruct(direction=NET_DIRECTION.SERVER, source=source, action=FIREWALL_ACTION.DROP))
elif parts[3] == "accept":
iface_defs[current_iface].append(RuleStruct(direction=NET_DIRECTION.SERVER, source=source, action=FIREWALL_ACTION.ACCEPT))
elif parts[3] == "reject":
iface_defs[current_iface].append(RuleStruct(direction=NET_DIRECTION.SERVER, source=source, action=FIREWALL_ACTION.REJECT))
for iface,rules in iface_defs.iteritems():
for rule in rules:
if rule.direction == NET_DIRECTION.SERVER:
ruletpl = "-A INPUT -i %s -s %s -j %s -m comment --comment \"%s\""
action = ""
if rule.action == FIREWALL_ACTION.DROP:
action = "DROP"
elif rule.action == FIREWALL_ACTION.ACCEPT:
action = "ACCEPT"
elif rule.action == FIREWALL_ACTION.REJECT:
action = "REJECT"
if rule.source.object_type == TYPES.ADDR:
generated_ruleset.append(ruletpl % (iface, object_defs[rule.source.value], action, rule))
elif rule.source.object_type == TYPES.GROUP:
for ip in getIPsInGroup(rule.source.value):
generated_ruleset.append(ruletpl % (iface, ip, action, rule))
elif rule.source.object_type == TYPES.PROTO:
generated_ruleset.append("-A INPUT -i %s -p %s -j %s -m comment --comment \"%s\"" % (iface, rule.source.value, action, rule))
for rule in global_defs:
if rule.direction == NET_DIRECTION.SERVER:
ruletpl = "-A INPUT -s %s -j %s -m comment --comment \"%s\""
action = ""
if rule.action == FIREWALL_ACTION.DROP:
action = "DROP"
elif rule.action == FIREWALL_ACTION.ACCEPT:
action = "ACCEPT"
elif rule.action == FIREWALL_ACTION.REJECT:
action = "REJECT"
if rule.source.object_type == TYPES.ADDR:
generated_ruleset.append(ruletpl % (object_defs[rule.source.value], action, rule))
elif rule.source.object_type == TYPES.GROUP:
for ip in getIPsInGroup(rule.source.value):
generated_ruleset.append(ruletpl % (ip, action, rule))
elif rule.source.object_type == TYPES.PROTO:
generated_ruleset.append("-A INPUT -i %s -p %s -j %s -m comment --comment \"%s\"" % (rule.source.value, action, rule))
for rule in generated_ruleset:
print rule
except Exception, e:
s = smtplib.SMTP('localhost')
s.sendmail(socket.gethostname() + "@example.com", ["YOUREMAIL@example.com"], "To: YOUREMAIL@example.com\r\nSubject: Error: iptables error\r\nError when parsing iptables rules..." + str(e))
s.close()

Archive Download the corresponding diff file

Branches

Number of commits:
Page rendered in 0.06297s using 14 queries.