dust-deploy 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,14 @@
1
1
  Changelog
2
2
  =============
3
3
 
4
+ 0.3.2
5
+ ------------
6
+
7
+ - huge refactoring was done to the iptables recipe, no config changes needed on your side though.
8
+ - iptables now supports table support for rpm machines
9
+ - reduced verbosity for iptables recipe
10
+
11
+
4
12
  0.3.1
5
13
  ------------
6
14
 
@@ -1,188 +1,181 @@
1
1
  require 'ipaddress'
2
2
 
3
3
  class Iptables < Thor
4
+
4
5
  desc 'iptables:deploy', 'configures iptables firewall'
5
6
  def deploy node, rules, options
6
7
  template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
7
-
8
- # install iptables
9
- if node.uses_apt? or node.uses_emerge?
10
- node.install_package 'iptables'
11
-
12
- elsif node.uses_rpm?
13
- node.install_package 'iptables-ipv6'
14
-
15
- else
16
- ::Dust.print_failed 'os not supported'
17
- return
8
+ @node = node
9
+ @rules = rules
10
+ @options = options
11
+
12
+ # list of all tables and chains
13
+ @tables = {}
14
+ @tables['ipv4'] = {}
15
+ @tables['ipv4']['filter'] = [ 'INPUT', 'OUTPUT', 'FORWARD' ]
16
+ @tables['ipv4']['nat'] = [ 'OUTPUT', 'PREROUTING', 'POSTROUTING' ]
17
+ @tables['ipv4']['mangle'] = [ 'INPUT', 'OUTPUT', 'FORWARD', 'PREROUTING', 'POSTROUTING' ]
18
+ @tables['ipv4']['raw'] = [ 'OUTPUT', 'PREROUTING' ]
19
+
20
+ @tables['ipv6'] = {}
21
+ @tables['ipv6']['filter'] = [ 'INPUT', 'OUTPUT', 'FORWARD' ]
22
+ @tables['ipv6']['mangle'] = [ 'INPUT', 'OUTPUT', 'FORWARD', 'PREROUTING', 'POSTROUTING' ]
23
+ @tables['ipv6']['raw'] = [ 'OUTPUT', 'PREROUTING' ]
24
+
25
+
26
+ return unless install
27
+
28
+ [4, 6].each do |v|
29
+ @script = ''
30
+ @ip_version = v
31
+
32
+ ::Dust.print_msg "generating ipv#{@ip_version} rules\n"
33
+
34
+ clear_all_tables
35
+ populate_rule_defaults
36
+ generate_all_rules
37
+
38
+ deploy_script
39
+ apply_rules
40
+
41
+ puts
18
42
  end
43
+ end
19
44
 
20
45
 
21
- [ 'iptables', 'ip6tables' ].each do |iptables|
22
- if iptables == 'iptables'
23
- ipv = 4
24
- ipv4 = true
25
- ipv6 = false
26
- elsif iptables == 'ip6tables'
27
- ipv = 6
28
- ipv4 = false
29
- ipv6 = true
30
- end
31
-
32
- ::Dust.print_msg "configuring and deploying ipv4 rules\n" if ipv4
33
- ::Dust.print_msg "configuring and deploying ipv6 rules\n" if ipv6
34
-
35
- iptables_filter = ''
36
- iptables_nat = '' if node.uses_rpm?
37
-
38
- # default policy for chains
39
- if node.uses_apt? or node.uses_emerge?
40
- iptables_filter += rules['input'] ? "-P INPUT DROP\n" : "-P INPUT ACCEPT\n"
41
- iptables_filter += rules['output'] ? "-P OUTPUT DROP\n" : "-P OUTPUT ACCEPT\n"
42
- iptables_filter += rules['forward'] ? "-P FORWARD DROP\n" : "-P FORWARD ACCEPT\n"
43
-
44
- iptables_filter += "-F\n"
45
- iptables_filter += "-F -t nat\n" if ipv4
46
- iptables_filter += "-X\n"
47
-
48
- elsif node.uses_rpm?
49
- iptables_filter += "*filter\n"
50
-
51
- iptables_filter += rules['input'] ? ":INPUT DROP [0:0]\n" : ":INPUT ACCEPT [0:0]\n"
52
- iptables_filter += rules['output'] ? ":OUTPUT DROP [0:0]\n" : ":OUTPUT ACCEPT [0:0]\n"
53
- iptables_filter += rules['forward'] ? ":FORWARD DROP [0:0]\n" : ":FORWARD ACCEPT [0:0]\n"
54
-
55
- # also create a *nat element, centos-like systems need that.
56
- if ipv4
57
- iptables_nat += "*nat\n"
58
- iptables_nat += ":PREROUTING ACCEPT [0:0]\n"
59
- iptables_nat += ":POSTROUTING ACCEPT [0:0]\n"
60
- iptables_nat += ":OUTPUT ACCEPT [0:0]\n"
61
- end
62
- end
63
-
64
- # map rules to iptables strings
65
- rules.each do |chain, chain_rules|
66
- ::Dust.print_msg "#{::Dust.pink}#{chain.upcase}#{::Dust.none}\n", :indent => 2
67
- chain_rules.sort.each do |name, rule|
68
- # set default variables
69
- rule['jump'] ||= ['ACCEPT']
70
-
71
- # if we want to use ports, we're going to need a protocol. defaulting to tcp
72
- rule['protocol'] ||= ['tcp'] if rule['dport'] or rule['sport']
73
-
74
- # convert non-array variables to array, so we won't get hickups when using .each and .combine
75
- rule.each { |k, v| rule[k] = [ rule[k] ] unless rule[k].is_a? Array }
76
-
77
- next unless check_ipversion rule, ipv
78
-
79
- # on centos-like machines, nat tables are handled differently
80
- # remove --table argument and
81
- is_nat = false
82
- if node.uses_rpm? and rule['table']
83
- rule.delete 'table'
84
- is_nat = true
85
- end
86
-
87
- parse_rule(rule).each do |r|
88
- # TODO: parse nicer output
89
- ::Dust.print_msg "#{name}:#{::Dust.grey 0} '#{r.join ' ' }'#{::Dust.none}\n", :indent => 3
90
-
91
- if is_nat
92
- # handle centos special case
93
- iptables_nat += "-A #{chain.upcase} #{r.join ' '}\n"
94
- else
95
- iptables_filter += "-A #{chain.upcase} #{r.join ' '}\n"
96
- end
97
- end
98
- end
99
- end
100
-
101
- # put commit statement for rpm machines
102
- if node.uses_rpm?
103
- iptables_filter += "COMMIT\n"
104
- iptables_nat += "COMMIT\n" if ipv4
105
- end
106
-
107
- # prepend iptables command on non-centos-like machines
108
- if node.uses_apt? or node.uses_emerge?
109
- iptables_filter = iptables_filter.map { |s| "#{iptables} #{s}" }.to_s
110
- end
46
+ private
111
47
 
112
- # set header
113
- header = ''
114
- if node.uses_apt? or node.uses_emerge?
115
- header = "#!/bin/sh\n"
48
+ # install iptables
49
+ def install
50
+ return false unless @node.install_package 'iptables'
51
+ return false unless @node.install_package 'iptables-ipv6' if @node.uses_rpm?
52
+ true
53
+ end
54
+
55
+ # deletes all rules/chains
56
+ def clear_all_tables
57
+ return if @node.uses_rpm?
58
+
59
+ # clear all rules
60
+ @tables['ipv' + @ip_version.to_s].keys.each { |table| @script.concat "--flush --table #{table}\n" }
61
+
62
+ # delete all custom chains
63
+ @script.concat "--delete-chain \n" unless @node.uses_rpm?
64
+ end
65
+
66
+ # inserts default values to chains, if not given
67
+ # table defaults to filter
68
+ # jump target to ACCEPT
69
+ # protocol to tcp (if port is given)
70
+ # and converts non-arrays to arrays, so .each and .combine won't cause hickups
71
+ def populate_rule_defaults
72
+ @rules.values.each do |chain_rules|
73
+ chain_rules.values.each do |rule|
74
+ rule['table'] ||= ['filter']
75
+ rule['jump'] ||= ['ACCEPT']
76
+ rule['protocol'] ||= ['tcp'] if rule['dport'] or rule['sport']
77
+ rule.each { |k, v| rule[k] = [ rule[k] ] unless rule[k].is_a? Array }
116
78
  end
117
- header += "# automatically generated by dust\n\n"
118
- iptables_filter = header + iptables_filter
119
-
120
- # append nat table to filter
121
- iptables_filter = iptables_filter + iptables_nat if node.uses_rpm? and ipv4
122
-
123
- # set the target file depending on distribution
124
- target = "/etc/network/if-pre-up.d/#{iptables}" if node.uses_apt?
125
- target = "/etc/#{iptables}" if node.uses_emerge?
126
- target = "/etc/sysconfig/#{iptables}" if node.uses_rpm?
127
-
128
- node.write target, iptables_filter, :quiet => true
79
+ end
80
+ end
81
+
82
+ # generates all iptables rules
83
+ def generate_all_rules
84
+ @tables['ipv' + @ip_version.to_s].each do |table, chains|
85
+ @script.concat "*#{table}\n" if @node.uses_rpm?
86
+ set_chain_policies table
87
+ generate_rules_for_table table
88
+ end
89
+ end
129
90
 
130
- if node.uses_apt? or node.uses_emerge?
131
- node.chmod '700', target
132
- elsif node.uses_rpm?
133
- node.chmod '600', target
91
+ # set the chain default policies to DROP/ACCEPT
92
+ # according to whether chain is specified in config file
93
+ def set_chain_policies table
94
+ #::Dust.print_msg "#{::Dust.pink}#{table}#{Dust.none} table\n", :indent => 2
95
+ #::Dust.print_msg "setting default policies\n", :indent => 3
96
+
97
+ @tables['ipv' + @ip_version.to_s][table].each do |chain|
98
+ policy = get_chain_policy table, chain
99
+ #::Dust.print_msg "#{table}/#{chain} -> #{policy}", :indent => 4
100
+
101
+ if @node.uses_rpm?
102
+ @script.concat ":#{chain.upcase} #{policy} [0:0]\n"
103
+ else
104
+ @script.concat "--table #{table} --policy #{chain.upcase} #{policy}\n"
134
105
  end
106
+
107
+ #::Dust.print_ok
108
+ end
109
+ end
135
110
 
136
- if options.restart?
137
- ::Dust.print_msg 'applying ipv4 rules' if ipv4
138
- ::Dust.print_msg 'applying ipv6 rules' if ipv6
111
+ # returns DROP if chain and table is specified in config file
112
+ # ACCEPT if not
113
+ def get_chain_policy table, chain
114
+ return 'ACCEPT' unless @rules[chain.downcase]
139
115
 
140
- if node.uses_rpm?
141
- ::Dust.print_result node.exec("/etc/init.d/#{iptables} restart")[:exit_code]
116
+ @rules[chain.downcase].values.each do |rule|
117
+ return 'DROP' if rule['table'].include? table
118
+ end
142
119
 
143
- elsif node.uses_apt? or node.uses_emerge?
144
- ret = node.exec target
145
- ::Dust.print_result( (ret[:exit_code] == 0 and ret[:stdout].empty? and ret[:stderr].empty?) )
146
- end
147
- end
120
+ return 'ACCEPT'
121
+ end
148
122
 
149
- # on gentoo, rules have to be saved using the init script,
150
- # otherwise they won't get re-applied on next startup
151
- if node.uses_emerge?
152
- ::Dust.print_msg 'saving ipv4 rules' if ipv4
153
- ::Dust.print_msg 'saving ipv6 rules' if ipv6
154
- ::Dust.print_result node.exec("/etc/init.d/#{iptables} save")[:exit_code]
123
+ # generate iptables rules for table 'table'
124
+ def generate_rules_for_table table
125
+ @rules.each do |chain, chain_rules|
126
+ rules = get_rules_for_table chain_rules, table
127
+ next if rules.empty?
128
+
129
+ #::Dust.print_msg "#{::Dust.pink}#{chain}#{::Dust.none} rules\n", :indent => 3
130
+ rules.sort.each do |name, rule|
131
+ next unless rule['table'].include? table
132
+ next unless check_ip_version rule
133
+
134
+ ::Dust.print_msg "adding rule: #{name}", :indent => 2
135
+ generate_iptables_string chain, rule
136
+ ::Dust.print_ok
155
137
  end
156
-
157
- puts
158
138
  end
139
+ @script.concat "COMMIT\n" if @node.uses_rpm?
159
140
  end
160
-
161
-
162
- private
163
-
141
+
142
+ def get_rules_for_table rules, table
143
+ rules.select { |name, rule| rule['table'].include? table }
144
+ end
145
+
164
146
  # check if source and destination ip (if given)
165
147
  # are valid ips for this ip version
166
- def check_ipversion rule, ipv
148
+ def check_ip_version rule
167
149
  ['source', 'src', 'destination', 'dest', 'to-source'].each do |attr|
168
150
  if rule[attr]
169
151
  rule[attr].each do |addr|
170
- return false unless IPAddress(addr).send "ipv#{ipv}?"
152
+ return false unless IPAddress(addr).send "ipv#{@ip_version}?"
171
153
  end
172
154
  end
173
155
  # return false if this ip version was manually disabled
174
- return false unless rule['ip-version'].include? ipv if rule['ip-version']
156
+ return false unless rule['ip-version'].include? @ip_version if rule['ip-version']
175
157
  end
176
158
  true
159
+ end
160
+
161
+ # generates the iptables string out of a rule
162
+ def generate_iptables_string chain, rule
163
+ parse_rule(rule).each do |r|
164
+ #::Dust.print_msg "#{::Dust.grey}#{r.join ' '}#{::Dust.none}\n", :indent => 5
165
+ @script.concat "--append #{chain.upcase} #{r.join ' '}\n"
166
+ end
177
167
  end
178
168
 
179
169
  # map iptables options
180
170
  def parse_rule r
181
171
  with_dashes = {}
182
172
  result = []
183
-
173
+
174
+ # map r[key] = value to '--key value'
184
175
  r.each do |k, v|
185
176
  next if k == 'ip-version' # skip ip-version, since its not iptables option
177
+ next if k == 'table' if @node.uses_rpm? # rpm-firewall takes table argument with *table
178
+
186
179
  with_dashes[k] = r[k].map do |v|
187
180
  value = v.to_s
188
181
  if value.start_with? '!', '! '
@@ -190,44 +183,110 @@ class Iptables < Thor
190
183
  value.slice! '!'
191
184
  value.lstrip!
192
185
  "! --#{k} #{value}"
193
- else
186
+ else
194
187
  "--#{k} #{value}"
195
188
  end
196
189
  end
197
190
  end
198
191
  with_dashes.values.each { |a| result = result.combine a }
199
-
200
- # make sure the options are sorted in a way that works.
192
+
193
+ sort_rule_options result
194
+ end
195
+
196
+ # make sure the options are sorted in a way that works.
197
+ def sort_rule_options rule
201
198
  sorted = []
202
- result.each do |r|
199
+ rule.each do |r|
203
200
  # sort rules so it makes sense
204
201
  r = r.sort_by do |x|
205
202
  if x.include? '--match'
206
203
  -1
207
- elsif x.include? '--protocol'
204
+ elsif x.include? '--protocol'
208
205
  -2
209
- elsif x.include? '--jump'
206
+ elsif x.include? '--jump'
210
207
  1
211
- elsif x.include? '--to-port'
208
+ elsif x.include? '--to-port'
212
209
  2
213
- elsif x.include? '--to-destination'
210
+ elsif x.include? '--to-destination'
214
211
  3
215
- elsif x.include? '--to-source'
212
+ elsif x.include? '--to-source'
216
213
  4
217
- elsif x.include? '--ttl-set'
214
+ elsif x.include? '--ttl-set'
218
215
  5
219
- elsif x.include? '--clamp-mss-to-pmtu'
216
+ elsif x.include? '--clamp-mss-to-pmtu'
220
217
  6
221
- elsif x.include? '--reject-with'
218
+ elsif x.include? '--reject-with'
222
219
  7
223
- else
220
+ else
224
221
  0
225
222
  end
226
223
  end
227
224
  sorted.push r
228
225
  end
226
+
227
+ sorted
228
+ end
229
+
230
+ def deploy_script
231
+ target = get_target
232
+
233
+ prepend_cmd
234
+ prepend_header
235
+
236
+ @node.write target, @script, :quiet => true
237
+
238
+ if @node.uses_rpm?
239
+ @node.chmod '600', target
240
+ else
241
+ @node.chmod '700', target
242
+ end
243
+ end
229
244
 
230
- sorted
245
+ # put dust comment at the beginning of the file
246
+ def prepend_header
247
+ @script.insert 0, "#!/bin/sh\n" unless @node.uses_rpm?
248
+ @script.insert 0, "# automatically generated by dust\n\n"
249
+ end
250
+
251
+ # prepend iptables command on non-centos-like machines
252
+ def prepend_cmd
253
+ @script = @script.map { |s| "#{cmd} #{s}" }.to_s unless @node.uses_rpm?
231
254
  end
255
+
256
+ # apply newly pushed rules
257
+ def apply_rules
258
+ if @options.restart?
259
+ ::Dust.print_msg "applying ipv#{@ip_version} rules"
260
+
261
+ if @node.uses_rpm?
262
+ ::Dust.print_result @node.exec("/etc/init.d/#{cmd} restart")[:exit_code]
263
+
264
+ else
265
+ ret = @node.exec get_target
266
+ ::Dust.print_result( (ret[:exit_code] == 0 and ret[:stdout].empty? and ret[:stderr].empty?) )
267
+ end
268
+ end
269
+
270
+ # on gentoo, rules have to be saved using the init script,
271
+ # otherwise they won't get re-applied on next startup
272
+ if @node.uses_emerge?
273
+ ::Dust.print_msg "saving ipv#{@ip_version} rules"
274
+ ::Dust.print_result @node.exec("/etc/init.d/#{cmd} save")[:exit_code]
275
+ end
276
+ end
277
+
278
+ # set the target file depending on distribution
279
+ def get_target
280
+ target = "/etc/#{cmd}"
281
+ target = "/etc/network/if-pre-up.d/#{cmd}" if @node.uses_apt?
282
+ target = "/etc/sysconfig/#{cmd}" if @node.uses_rpm?
283
+ target
284
+ end
285
+
286
+ def cmd
287
+ return 'iptables' if @ip_version == 4
288
+ return 'ip6tables' if @ip_version == 6
289
+ end
290
+
232
291
  end
233
292
 
@@ -1,3 +1,3 @@
1
1
  module Dust
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 1
9
- version: 0.3.1
8
+ - 2
9
+ version: 0.3.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - kris kechagia
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-01-12 00:00:00 +01:00
17
+ date: 2012-01-14 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency