dust-deploy 0.3.1 → 0.3.2

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.
@@ -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