dust-deploy 0.1.8 → 0.2.0

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