dust-deploy 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/changelog.md CHANGED
@@ -1,7 +1,27 @@
1
1
  Changelog
2
2
  =============
3
3
 
4
- 9.1.8
4
+ 0.2.0
5
+ ------------
6
+
7
+ heavily refactors iptables recipe. you HAVE to adapt your iptables settings. new usage:
8
+
9
+ recipe:
10
+ iptables:
11
+ input:
12
+ - input:
13
+ ssh: { dport: 22, match: state, state: NEW }
14
+ - output:
15
+ drop: { jump: DROP }
16
+
17
+ every iptables long option is allowed, it tries to automatically detect whether to use iptables, ip6tables or both.
18
+ known issues: --to-destination is not checked for ipv4/ipv6, because it might include port numbers.
19
+ default jump target ist ACCEPT
20
+
21
+ basic rules are added automatically, see iptables.rb for more information.
22
+
23
+
24
+ 0.1.8
5
25
  ------------
6
26
 
7
27
  adds recipe for making sure packages are _uninstalled_
data/dust.gemspec CHANGED
@@ -25,4 +25,5 @@ Gem::Specification.new do |s|
25
25
  s.add_runtime_dependency 'net-scp'
26
26
  s.add_runtime_dependency 'net-sftp'
27
27
  s.add_runtime_dependency 'thor'
28
+ s.add_runtime_dependency 'ipaddress'
28
29
  end
@@ -1,3 +1,5 @@
1
+ require 'ipaddress'
2
+
1
3
  class Iptables < Thor
2
4
  desc 'iptables:deploy', 'configures iptables firewall'
3
5
  def deploy node, rules, options
@@ -15,226 +17,114 @@ class Iptables < Thor
15
17
  return
16
18
  end
17
19
 
18
- # configure attributes (using empty arrays as default)
19
- rules['ports'] ||= []
20
- rules['ipv4-custom-input-rules'] ||= []
21
- rules['ipv6-custom-input-rules'] ||= []
22
- rules['ipv4-custom-output-rules'] ||= []
23
- rules['ipv6-custom-output-rules'] ||= []
24
- rules['ipv4-custom-forward-rules'] ||= []
25
- rules['ipv6-custom-forward-rules'] ||= []
26
- rules['ipv4-custom-prerouting-rules'] ||= []
27
- rules['ipv6-custom-prerouting-rules'] ||= []
28
- rules['ipv4-custom-postrouting-rules'] ||= []
29
- rules['ipv6-custom-postrouting-rules'] ||= []
30
-
31
- # convert ports: int to array if its just a single int so .each won't get hickups
32
- rules['ports'] = [ rules['ports'] ] if rules['ports'].class == Fixnum
33
20
 
34
21
  [ 'iptables', 'ip6tables' ].each do |iptables|
35
- ipv4 = iptables == 'iptables'
36
- ipv6 = iptables == 'ip6tables'
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
37
31
 
38
32
  ::Dust.print_msg "configuring and deploying ipv4 rules\n" if ipv4
39
33
  ::Dust.print_msg "configuring and deploying ipv6 rules\n" if ipv6
40
34
 
41
- rule_file = ''
35
+ iptables_script = ''
42
36
 
43
37
  # default policy for chains
44
38
  if node.uses_apt? true or node.uses_emerge? true
45
- rule_file += "-P INPUT DROP\n" +
39
+ iptables_script += "-P INPUT DROP\n" +
46
40
  "-P OUTPUT DROP\n" +
47
41
  "-P FORWARD DROP\n" +
48
42
  "-F\n"
49
- rule_file += "-F -t nat\n" if ipv4
50
- rule_file += "-X\n"
43
+ iptables_script += "-F -t nat\n" if ipv4
44
+ iptables_script += "-X\n"
51
45
 
52
46
  elsif node.uses_rpm? true
53
- rule_file += "*filter\n" +
47
+ iptables_script += "*filter\n" +
54
48
  ":INPUT DROP [0:0]\n" +
55
49
  ":FORWARD DROP [0:0]\n" +
56
50
  ":OUTPUT DROP [0:0]\n"
57
51
  end
58
52
 
59
53
  # allow localhost
60
- rule_file += "-A INPUT -i lo -j ACCEPT\n"
54
+ iptables_script += "-A INPUT -i lo -j ACCEPT\n"
61
55
 
62
56
  # drop invalid packets
63
- rule_file += "-A INPUT -m state --state INVALID -j DROP\n"
57
+ iptables_script += "-A INPUT -m state --state INVALID -j DROP\n"
64
58
 
65
59
  # allow related packets
66
- rule_file += "-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
60
+ iptables_script += "-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
67
61
 
68
62
  # drop tcp packets with the syn bit set if the tcp connection is already established
69
- rule_file += "-A INPUT -p tcp --tcp-flags SYN SYN -m state --state ESTABLISHED -j DROP\n" # if ipv4
63
+ iptables_script += "-A INPUT -p tcp --tcp-flags SYN SYN -m state --state ESTABLISHED -j DROP\n" # if ipv4
70
64
 
71
65
  # drop icmp timestamps
72
- rule_file += "-A INPUT -p icmp --icmp-type timestamp-request -j DROP\n" if ipv4
73
- rule_file += "-A INPUT -p icmp --icmp-type timestamp-reply -j DROP\n" if ipv4
66
+ iptables_script += "-A INPUT -p icmp --icmp-type timestamp-request -j DROP\n" if ipv4
67
+ iptables_script += "-A INPUT -p icmp --icmp-type timestamp-reply -j DROP\n" if ipv4
74
68
 
75
69
  # allow other icmp packets
76
- rule_file += "-A INPUT -p icmpv6 -j ACCEPT\n" if ipv6
77
- rule_file += "-A INPUT -p icmp -j ACCEPT\n"
78
-
79
- # open ports
80
- rules['ports'].each do |rule|
81
- # if config is something like
82
- # ports: 22
83
- # or
84
- # ports: [ 22, 443, 1000:2000 ]
85
- # generate a new hash, and set rule['port']
86
- unless rule.class == Hash
87
- port = rule
88
- rule = {}
89
- rule['port'] = port
90
- end
70
+ iptables_script += "-A INPUT -p icmpv6 -j ACCEPT\n" if ipv6
71
+ iptables_script += "-A INPUT -p icmp -j ACCEPT\n"
91
72
 
92
- # skip rule for other ipversion than specified
93
- rule['ip-version'] ||= 0 # default to 0, means both protocols
94
- next if rule['ip-version'].to_i == 4 and ipv6
95
- next if rule['ip-version'].to_i == 6 and ipv4
96
73
 
97
- # convert to string if port is a int
98
- rule['port'] = rule['port'].to_s if rule['port'].class == Fixnum
74
+ # drop invalid outgoing packets
75
+ iptables_script += "-A OUTPUT -m state --state INVALID -j DROP\n"
99
76
 
100
- # skip this port if no portnumber is specified
101
- unless rule['port']
102
- ::Dust.print_failed "no port specified: #{rule.inspect}", 2
103
- next
104
- end
77
+ # allow related outgoing packets
78
+ iptables_script += "-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
105
79
 
106
- # tcp is the default protocol
107
- rule['protocol'] ||= 'tcp'
80
+ # map rules to iptables strings
81
+ rules.each do |chain, chain_rules|
82
+ ::Dust.print_msg "#{::Dust.pink}#{chain.upcase}#{::Dust.none}\n", 2
83
+ chain_rules.each do |name, rule|
84
+ # set default variables
85
+ rule['jump'] ||= ['ACCEPT']
108
86
 
109
- # apply one rule for each port(range)
110
- rule['port'].each do |port|
111
- ::Dust.print_msg "allowing port #{port}:#{rule['protocol']}", 2
112
- rule_file += "-A INPUT -p #{rule['protocol']} --dport #{port} "
113
- if rule['interface']
114
- print " [dev: #{rule['interface']}]"
115
- rule_file += "-i #{rule['interface']} "
116
- end
117
- if rule['source']
118
- print " [source: #{rule['source']}]"
119
- rule_file += "--source #{rule['source']} "
120
- end
121
- rule_file += "-m state --state NEW "
122
- rule_file += "-j ACCEPT\n"
123
- ::Dust.print_ok
124
- end
125
- end
87
+ # if we want to use ports, we're going to need a protocol. defaulting to tcp
88
+ rule['protocol'] ||= ['tcp'] if rule['dport'] or rule['sport']
126
89
 
127
- # add custom ipv4 iput rules
128
- rules['ipv4-custom-input-rules'].each do |rule|
129
- ::Dust.print_msg "adding custom ipv4 input rule: #{rule}", 2
130
- rule_file += "-A INPUT #{rule}\n"
131
- ::Dust.print_ok
132
- end if ipv4
133
-
134
- # add custom ipv6 iput rules
135
- rules['ipv6-custom-input-rules'].each do |rule|
136
- ::Dust.print_msg "adding custom ipv6 input rule: #{rule}", 2
137
- rule_file += "-A INPUT #{rule}\n"
138
- ::Dust.print_ok
139
- end if ipv6
140
-
141
- # deny the rest
142
- rule_file += "-A INPUT -p tcp -j REJECT --reject-with tcp-reset\n"
143
- rule_file += "-A INPUT -j REJECT --reject-with icmp-port-unreachable\n" if ipv4
144
-
145
- # add custom ipv4 prerouting rules
146
- rules['ipv4-custom-prerouting-rules'].each do |rule|
147
- ::Dust.print_msg "adding custom ipv4 prerouting rule: #{rule}", 2
148
- rule_file += "-A PREROUTING #{rule}\n"
149
- ::Dust.print_ok
150
- end if ipv4
151
-
152
- # add custom ipv6 prerouting rules
153
- rules['ipv6-custom-prerouting-rules'].each do |rule|
154
- ::Dust.print_msg "adding custom ipv6 prerouting rule: #{rule}", 2
155
- rule_file += "-A PREROUTING #{rule}\n"
156
- ::Dust.print_ok
157
- end if ipv6
158
-
159
- # add custom ipv4 postrouting rules
160
- rules['ipv4-custom-postrouting-rules'].each do |rule|
161
- ::Dust.print_msg "adding custom ipv4 postrouting rule: #{rule}", 2
162
- rule_file += "-A POSTROUTING #{rule}\n"
163
- ::Dust.print_ok
164
- end if ipv4
165
-
166
- # add custom ipv6 postrouting rules
167
- rules['ipv6-custom-postrouting-rules'].each do |rule|
168
- ::Dust.print_msg "adding custom ipv6 postrouting rule: #{rule}", 2
169
- rule_file += "-A POSTROUTING #{rule}\n"
170
- ::Dust.print_ok
171
- end if ipv6
90
+ # convert non-array variables to array, so we won't get hickups when using .each and .combine
91
+ rule.each { |k, v| rule[k] = [ rule[k] ] if rule[k].class != Array }
172
92
 
93
+ next unless check_ipversion rule, ipv
173
94
 
174
- # drop invalid outgoing packets
175
- rule_file += "-A OUTPUT -m state --state INVALID -j DROP\n"
95
+ parse_rule(rule).each do |r|
96
+ # TODO: parse nicer output
97
+ ::Dust.print_msg "#{name}:#{::Dust.grey 0} '#{r.join ' ' }'#{::Dust.none}\n", 3
98
+ iptables_script += "-A #{chain.upcase} #{r.join ' '}\n"
99
+ end
100
+ end
101
+ end
176
102
 
177
- # allow related outgoing packets
178
- rule_file += "-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
179
-
180
- # add custom ipv4 output rules
181
- rules['ipv4-custom-output-rules'].each do |rule|
182
- ::Dust.print_msg "adding custom ipv4 outgoing rule: #{rule}", 2
183
- rule_file += "-A OUTPUT #{rule}\n"
184
- ::Dust.print_ok
185
- end if ipv4
186
-
187
- # add custom ipv6 output rules
188
- rules['ipv6-custom-output-rules'].each do |rule|
189
- ::Dust.print_msg "adding custom ipv6 outgoing rule: #{rule}", 2
190
- rule_file += "-A OUTPUT #{rule}\n"
191
- ::Dust.print_ok
192
- end if ipv6
103
+ # deny the rest incoming
104
+ iptables_script += "-A INPUT -p tcp -j REJECT --reject-with tcp-reset\n"
105
+ iptables_script += "-A INPUT -j REJECT --reject-with icmp-port-unreachable\n" if ipv4
193
106
 
194
107
  # allow everything out
195
- rule_file += "-A OUTPUT -j ACCEPT\n"
196
-
197
-
198
- # enable packet forwarding
199
- if rules['forward']
200
- ::Dust.print_msg 'enabling ipv4 forwarding', 2
201
- rule_file += "-A FORWARD -m state --state INVALID -j DROP\n"
202
- rule_file += "-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
203
- rule_file += "-A FORWARD -j ACCEPT\n"
204
- ::Dust.print_ok
205
- end if ipv4
206
-
207
- # add custom ipv4 forward rules
208
- rules['ipv4-custom-forward-rules'].each do |rule|
209
- ::Dust.print_msg "adding custom ipv4 forward rule: #{rule}", 2
210
- rule_file += "-A FORWARD #{rule}\n"
211
- ::Dust.print_ok
212
- end if ipv4
213
-
214
- # add custom ipv6 forward rules
215
- rules['ipv6-custom-forward-rules'].each do |rule|
216
- ::Dust.print_msg "adding custom ipv6 forward rule: #{rule}", 2
217
- rule_file += "-A FORWARD #{rule}\n"
218
- ::Dust.print_ok
219
- end if ipv6
220
-
221
- rule_file += "COMMIT\n" if node.uses_rpm? true
108
+ iptables_script += "-A OUTPUT -j ACCEPT\n"
109
+
110
+ # put commit statement for rpm machines
111
+ iptables_script += "COMMIT\n" if node.uses_rpm? true
222
112
 
223
113
  # prepend iptables command on non-centos-like machines
224
- rule_file = rule_file.map { |s| "#{iptables} #{s}" }.to_s if node.uses_apt? true or node.uses_emerge? true
114
+ iptables_script = iptables_script.map { |s| "#{iptables} #{s}" }.to_s if node.uses_apt? true or node.uses_emerge? true
225
115
 
226
116
  # set header
227
117
  header = ''
228
118
  header = "#!/bin/sh\n" if node.uses_apt? true or node.uses_emerge? true
229
119
  header += "# automatically generated by dust\n\n"
230
- rule_file = header + rule_file
120
+ iptables_script = header + iptables_script
231
121
 
232
122
  # set the target file depending on distribution
233
123
  target = "/etc/network/if-pre-up.d/#{iptables}" if node.uses_apt? true
234
124
  target = "/etc/#{iptables}" if node.uses_emerge? true
235
125
  target = "/etc/sysconfig/#{iptables}" if node.uses_rpm? true
236
126
 
237
- node.write target, rule_file, true
127
+ node.write target, iptables_script, true
238
128
 
239
129
  node.chmod '700', target if node.uses_apt? true or node.uses_emerge? true
240
130
  node.chmod '600', target if node.uses_rpm? true
@@ -263,5 +153,46 @@ class Iptables < Thor
263
153
  puts
264
154
  end
265
155
  end
156
+
157
+
158
+ private
159
+
160
+ # check if source and destination ip (if given)
161
+ # are valid ips for this ip version
162
+ def check_ipversion rule, ipv
163
+ ['source', 'destination', 'to-source'].each do |attr|
164
+ if rule[attr]
165
+ rule[attr].each do |addr|
166
+ return false unless IPAddress(addr).send "ipv#{ipv}?"
167
+ end
168
+ end
169
+ # return false if this ip version was manually disabled
170
+ return false unless rule['ip-version'].include? ipv if rule['ip-version']
171
+ end
172
+ true
173
+ end
174
+
175
+ # map iptables options
176
+ def parse_rule r
177
+ with_dashes = {}
178
+ result = []
179
+
180
+ r.each do |k, v|
181
+ # skip ip-version, since its not iptables option
182
+ with_dashes[k] = r[k].map { |value| "--#{k} #{value}" } unless k == 'ip-version'
183
+ end
184
+ with_dashes.values.each { |a| result = result.combine a }
185
+
186
+ # make sure the options are sorted in a way that works.
187
+ # protocol and match first, jump last
188
+ sorted = []
189
+ result.each do |r|
190
+ r = r.sort_by { |x| if x.include? '--match' then -1 else 1 end }
191
+ r = r.sort_by { |x| if x.include? '--protocol' then -1 else 1 end }
192
+ sorted.push(r.sort_by { |x| if x.include? '--jump' then 1 else -1 end })
193
+ end
194
+
195
+ sorted
196
+ end
266
197
  end
267
198
 
data/lib/dust/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dust
2
- VERSION = "0.1.8"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 8
9
- version: 0.1.8
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - kris kechagia
@@ -65,6 +65,18 @@ dependencies:
65
65
  version: "0"
66
66
  type: :runtime
67
67
  version_requirements: *id004
68
+ - !ruby/object:Gem::Dependency
69
+ name: ipaddress
70
+ prerelease: false
71
+ requirement: &id005 !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :runtime
79
+ version_requirements: *id005
68
80
  description: when puppet and chef suck because you want to be in control and sprinkle just cannot do enough for you
69
81
  email:
70
82
  - kk@rndsec.net