dust-deploy 0.1.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.
@@ -0,0 +1,13 @@
1
+ class Aliases < Thor
2
+ desc 'aliases:deploy', 'installs email aliases'
3
+ def deploy node, ingredients
4
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
5
+
6
+ return unless node.package_installed? 'postfix'
7
+ node.scp "#{template_path}/aliases", '/etc/aliases'
8
+
9
+ ::Dust.print_msg 'running newaliases', 1
10
+ ::Dust.print_result node.exec('newaliases')[:exit_code]
11
+ end
12
+ end
13
+
@@ -0,0 +1,35 @@
1
+ class BasicSetup < Thor
2
+ desc 'basic_setup:deploy', 'installs basic packages and config files'
3
+ def deploy node, ingredients, options
4
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
5
+
6
+ # install some basic packages
7
+ ::Dust.print_msg "installing basic packages\n"
8
+
9
+ node.install_package 'screen', false, 2
10
+ node.install_package 'rsync', false, 2
11
+ node.install_package 'psmisc', false, 2 if node.uses_apt? true
12
+
13
+ if node.uses_rpm? true
14
+ node.install_package 'vim-enhanced', false, 2
15
+ else
16
+ node.install_package 'vim', false, 2
17
+ end
18
+
19
+ if node.uses_apt? true
20
+ node.install_package 'git-core', false, 2
21
+ else
22
+ node.install_package 'git', false, 2
23
+ end
24
+ puts
25
+
26
+ # deploy basic configuration for root user
27
+ ::Dust.print_msg "deploying configuration files for root\n", 1
28
+ Dir["#{template_path}/.*"].each do |file|
29
+ next unless File.file?(file)
30
+ node.scp file, "/root/#{File.basename(file)}", false, 2
31
+ end
32
+
33
+ end
34
+ end
35
+
@@ -0,0 +1,44 @@
1
+ class Debsecan < Thor
2
+ desc 'debsecan:deploy', 'installs and configures debian security package "debsecan"'
3
+ def deploy node, config, options
4
+ if node.is_os? ['debian', 'ubuntu'], true
5
+ node.install_package 'debsecan'
6
+
7
+ ::Dust.print_msg 'configuring debsecan'
8
+
9
+ # if config is simply set to "true", use defaults
10
+ config = {} unless config.class == Hash
11
+
12
+ # setting default config variables (unless already set)
13
+ config['report'] ||= false
14
+ config['mailto'] ||= 'root'
15
+ config['source'] ||= ''
16
+
17
+ config_file = ''
18
+
19
+ # configures whether daily reports are sent
20
+ config_file += "# If true, enable daily reports, sent by email.\n" +
21
+ "REPORT=#{config['report'].to_s}\n\n"
22
+
23
+ # configures the suite
24
+ config_file += "# For better reporting, specify the correct suite here, using the code\n" +
25
+ "# name (that is, \"sid\" instead of \"unstable\").\n" +
26
+ "SUITE=#{node['lsbdistcodename']}\n\n"
27
+
28
+ # which user gets the reports?
29
+ config_file += "# Mail address to which reports are sent.\n" +
30
+ "MAILTO=#{config['mailto']}\n\n"
31
+
32
+ # set vulnerability source
33
+ config_file += "# The URL from which vulnerability data is downloaded. Empty for the\n" +
34
+ "# built-in default.\n" +
35
+ "SOURCE=#{config['source']}\n\n"
36
+
37
+ node.write '/etc/default/debsecan', config_file, true
38
+ ::Dust.print_ok
39
+ else
40
+ ::Dust.print_failed 'os not supported'
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,111 @@
1
+ require 'erb'
2
+
3
+ class Duplicity < Thor
4
+ desc 'duplicity:deploy', 'installs and configures duplicity backups'
5
+ def deploy node, scenarios, options
6
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
7
+
8
+ return unless node.install_package 'duplicity'
9
+
10
+ # clear all other duplicity cronjobs that might have been deployed earlier
11
+ remove_duplicity_cronjobs node
12
+
13
+ # return if config simply says 'remove'
14
+ return if scenarios == 'remove'
15
+
16
+ scenarios.each do |scenario, conf|
17
+ config = conf.clone
18
+
19
+ # if directory config options is not given, use hostname-scenario
20
+ config['directory'] ||= "#{node['hostname']}-#{scenario}"
21
+
22
+ # check whether backend is specified, skip to next scenario if not
23
+ unless config['backend'] and config['passphrase']
24
+ ::Dust.print_failed "scenario #{scenario}: backend or passphrase missing.", 1
25
+ next
26
+ end
27
+
28
+ # check if interval is correct
29
+ unless [ 'monthly', 'weekly', 'daily', 'hourly' ].include? config['interval']
30
+ return ::Dust.print_failed "invalid interval: '#{config['interval']}'"
31
+ end
32
+
33
+ # check whether we need ncftp
34
+ node.install_package 'ncftp' if config['backend'].include? 'ftp://'
35
+
36
+ # scp backend on centos needs python-pexpect
37
+ node.install_package 'python-pexpect' if config['backend'].include? 'scp://' and node.uses_rpm? true
38
+
39
+ # add hostkey to known_hosts
40
+ if config['hostkey']
41
+ ::Dust.print_msg 'checking if ssh key is in known_hosts'
42
+ unless ::Dust.print_result node.exec("grep -q '#{config['hostkey']}' ~/.ssh/known_hosts")[:exit_code] == 0
43
+ node.mkdir '~/.ssh', false, 2
44
+ node.append '~/.ssh/known_hosts', config['hostkey'], false, 2
45
+ end
46
+ end
47
+
48
+ # generate path for the cronjob
49
+ cronjob_path = "/etc/cron.#{config['interval']}/duplicity-#{scenario}"
50
+
51
+ # adjust and upload cronjob
52
+ template = ERB.new File.read("#{template_path}/cronjob.erb"), nil, '%<>'
53
+ ::Dust.print_msg "adjusting and deploying cronjob (scenario: #{scenario}, interval: #{config['interval']})\n"
54
+ config['options'].each { |option| ::Dust.print_ok "adding option: #{option}", 2 }
55
+ node.write cronjob_path, template.result(binding)
56
+
57
+ # making cronjob executeable
58
+ node.chmod '0700', cronjob_path
59
+ puts
60
+ end
61
+ end
62
+
63
+
64
+ # print duplicity-status
65
+ desc 'duplicity:status', 'displays current status of all duplicity backups'
66
+ def status node, scenarios, options
67
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
68
+
69
+ return unless node.package_installed? 'duplicity'
70
+
71
+ scenarios.each do |scenario, conf|
72
+ config = conf.clone
73
+
74
+ # if directory config option is not given, use hostname-scenario
75
+ config['directory'] ||= "#{node['hostname']}-#{scenario}"
76
+
77
+ # check whether backend is specified, skip to next scenario if not
78
+ return ::Dust.print_failed 'no backend specified.' unless config['backend']
79
+
80
+ ::Dust.print_msg "running collection-status for scenario '#{scenario}'"
81
+ cmd = "nice -n #{config['nice']} duplicity collection-status " +
82
+ "--archive-dir #{config['archive']} " +
83
+ "#{File.join(config['backend'], config['directory'])}"
84
+
85
+ cmd += " |tail -n3 |head -n1" unless options.long?
86
+
87
+ ret = node.exec cmd
88
+
89
+ # check exit code and stdout shouldn't be empty
90
+ ::Dust.print_result( (ret[:exit_code] == 0 and ret[:stdout].length > 0) )
91
+
92
+ if options.long?
93
+ ::Dust.print_msg "#{::Dust.black}#{ret[:stdout]}#{::Dust.none}", 0
94
+ else
95
+ ::Dust.print_msg "\t#{::Dust.black}#{ret[:stdout].sub(/^\s+([a-zA-Z]+)\s+(\w+\s+\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)\s+(\d+)$/, 'Last backup: \1 (\3 sets) on \2')}#{::Dust.none}", 0
96
+ end
97
+
98
+ puts
99
+ end
100
+ end
101
+
102
+ private
103
+ # removes all duplicity cronjobs
104
+ def remove_duplicity_cronjobs node
105
+ ::Dust.print_msg 'deleting old duplicity cronjobs'
106
+ node.rm '/etc/cron.*/duplicity*', true
107
+ ::Dust.print_ok
108
+ end
109
+
110
+ end
111
+
@@ -0,0 +1,13 @@
1
+ class EtcHosts < Thor
2
+ desc 'etc_hosts:deploy', 'deploys /etc/hosts'
3
+ def deploy node, daemon, options
4
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
5
+
6
+ return unless node.package_installed?('dnsmasq')
7
+ node.scp("#{template_path}/hosts", '/etc/hosts')
8
+
9
+ # restart dns service
10
+ node.restart_service daemon if options.restart?
11
+ end
12
+ end
13
+
@@ -0,0 +1,267 @@
1
+ class Iptables < Thor
2
+ desc 'iptables:deploy', 'configures iptables firewall'
3
+ def deploy node, rules, options
4
+ template_path = "./templates/#{ File.basename(__FILE__).chomp( File.extname(__FILE__) ) }"
5
+
6
+ # install iptables
7
+ if node.uses_apt? true or node.uses_emerge? true
8
+ node.install_package 'iptables'
9
+
10
+ elsif node.uses_rpm? true
11
+ node.install_package 'iptables-ipv6'
12
+
13
+ else
14
+ ::Dust.print_failed 'os not supported'
15
+ return
16
+ end
17
+
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
+
34
+ [ 'iptables', 'ip6tables' ].each do |iptables|
35
+ ipv4 = iptables == 'iptables'
36
+ ipv6 = iptables == 'ip6tables'
37
+
38
+ ::Dust.print_msg "configuring and deploying ipv4 rules\n" if ipv4
39
+ ::Dust.print_msg "configuring and deploying ipv6 rules\n" if ipv6
40
+
41
+ rule_file = ''
42
+
43
+ # default policy for chains
44
+ if node.uses_apt? true or node.uses_emerge? true
45
+ rule_file += "-P INPUT DROP\n" +
46
+ "-P OUTPUT DROP\n" +
47
+ "-P FORWARD DROP\n" +
48
+ "-F\n"
49
+ rule_file += "-F -t nat\n" if ipv4
50
+ rule_file += "-X\n"
51
+
52
+ elsif node.uses_rpm? true
53
+ rule_file += "*filter\n" +
54
+ ":INPUT DROP [0:0]\n" +
55
+ ":FORWARD DROP [0:0]\n" +
56
+ ":OUTPUT DROP [0:0]\n"
57
+ end
58
+
59
+ # allow localhost
60
+ rule_file += "-A INPUT -i lo -j ACCEPT\n"
61
+
62
+ # drop invalid packets
63
+ rule_file += "-A INPUT -m state --state INVALID -j DROP\n"
64
+
65
+ # allow related packets
66
+ rule_file += "-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
67
+
68
+ # 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
70
+
71
+ # 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
74
+
75
+ # 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
91
+
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
+
97
+ # convert to string if port is a int
98
+ rule['port'] = rule['port'].to_s if rule['port'].class == Fixnum
99
+
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
105
+
106
+ # tcp is the default protocol
107
+ rule['protocol'] ||= 'tcp'
108
+
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
126
+
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
172
+
173
+
174
+ # drop invalid outgoing packets
175
+ rule_file += "-A OUTPUT -m state --state INVALID -j DROP\n"
176
+
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
193
+
194
+ # 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
222
+
223
+ # 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
225
+
226
+ # set header
227
+ header = ''
228
+ header = "#!/bin/sh\n" if node.uses_apt? true or node.uses_emerge? true
229
+ header += "# automatically generated by dust\n\n"
230
+ rule_file = header + rule_file
231
+
232
+ # set the target file depending on distribution
233
+ target = "/etc/network/if-pre-up.d/#{iptables}" if node.uses_apt? true
234
+ target = "/etc/#{iptables}" if node.uses_emerge? true
235
+ target = "/etc/sysconfig/#{iptables}" if node.uses_rpm? true
236
+
237
+ node.write target, rule_file, true
238
+
239
+ node.chmod '700', target if node.uses_apt? true or node.uses_emerge? true
240
+ node.chmod '600', target if node.uses_rpm? true
241
+
242
+ if options.restart?
243
+ ::Dust.print_msg 'applying ipv4 rules' if ipv4
244
+ ::Dust.print_msg 'applying ipv6 rules' if ipv6
245
+
246
+ if node.uses_rpm? true
247
+ ::Dust.print_result node.exec("/etc/init.d/#{iptables} restart")[:exit_code]
248
+
249
+ elsif node.uses_apt? true or node.uses_emerge? true
250
+ ret = node.exec target
251
+ ::Dust.print_result( (ret[:exit_code] == 0 and ret[:stdout].empty? and ret[:stderr].empty?) )
252
+ end
253
+ end
254
+
255
+ # on gentoo, rules have to be saved using the init script,
256
+ # otherwise they won't get re-applied on next startup
257
+ if node.uses_emerge? true
258
+ ::Dust.print_msg 'saving ipv4 rules' if ipv4
259
+ ::Dust.print_msg 'saving ipv6 rules' if ipv6
260
+ ::Dust.print_result node.exec("/etc/init.d/#{iptables} save")[:exit_code]
261
+ end
262
+
263
+ puts
264
+ end
265
+ end
266
+ end
267
+