backup 3.0.26 → 3.0.27

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,287 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../../spec_helper.rb', __FILE__)
4
+
5
+ describe 'Encryptor::GPG',
6
+ :if => Backup::SpecLive::CONFIG['encryptor']['gpg']['specs_enabled'] do
7
+
8
+ def archive_file_for(model)
9
+ File.join(
10
+ Backup::SpecLive::TMP_PATH,
11
+ "#{model.trigger}", model.time, "#{model.trigger}.tar.gpg"
12
+ )
13
+ end
14
+
15
+ # clear out, then load the encryptor.gpg_homedir with the keys for the
16
+ # given key_type (:public/:private) for the given identifiers.
17
+ #
18
+ def load_gpg_homedir(encryptor, key_type, identifiers)
19
+ # Clear out any files if the directory exists
20
+ dir = File.expand_path(encryptor.gpg_homedir)
21
+ FileUtils.rm(Dir[File.join(dir, '*')]) if File.exists?(dir)
22
+
23
+ # Make sure the directory exists with proper permissions.
24
+ # This will also initialize the keyring files, so this method can be
25
+ # called with no identifiers to simply reset the directory without
26
+ # importing any keys.
27
+ encryptor.send(:setup_gpg_homedir)
28
+
29
+ # Import the keys, making sure each import is successful.
30
+ # #import_key will log a warning for the identifier if the
31
+ # import fails, so we'll just abort if we get a failure here.
32
+ [identifiers].flatten.compact.each do |identifier|
33
+ ret_id = encryptor.send(:import_key,
34
+ identifier, Backup::SpecLive::GPGKeys[identifier][key_type]
35
+ )
36
+ unless ret_id == Backup::SpecLive::GPGKeys[identifier][:long_id]
37
+ abort("load_gpg_homedir failed")
38
+ end
39
+ end
40
+ end
41
+
42
+ # make sure the archive can be decrypted
43
+ def can_decrypt?(model, passphrase = nil)
44
+ enc = model.encryptor
45
+ archive = archive_file_for(model)
46
+ outfile = File.join(File.dirname(archive), 'outfile')
47
+
48
+ pass_opt = "--passphrase '#{ passphrase }'" if passphrase
49
+ enc.send(:run,
50
+ "#{ enc.send(:utility, :gpg) } #{ enc.send(:base_options) } " +
51
+ "#{ pass_opt } -o '#{ outfile }' -d '#{ archive }' 2>&1"
52
+ )
53
+
54
+ if File.exist?(outfile)
55
+ File.delete(outfile)
56
+ true
57
+ else
58
+ false
59
+ end
60
+ end
61
+
62
+ context 'using :asymmetric mode with some existing keys' do
63
+ let(:model) { h_set_trigger('encryptor_gpg_asymmetric') }
64
+
65
+ it 'should encrypt the archive' do
66
+ recipients = [:backup01, :backup02, :backup03, :backup04]
67
+ # Preload keys for backup01 and backup02.
68
+ # Keys for backup03 and backup04 are configured in :keys in the model
69
+ # and will be imported when the model is performed.
70
+ # The Model specifies all 4 as :recipients.
71
+ load_gpg_homedir(model.encryptor, :public, recipients[0..1])
72
+
73
+ model.perform!
74
+
75
+ Backup::Logger.has_warnings?.should be_false
76
+
77
+ File.exist?(archive_file_for(model)).should be_true
78
+
79
+ # make sure all 4 recipients can decrypt the archive
80
+ recipients.each do |recipient|
81
+ load_gpg_homedir(model.encryptor, :private, recipient)
82
+ can_decrypt?(model).should be_true
83
+ end
84
+ end
85
+ end # context 'using :asymmetric mode with some existing keys'
86
+
87
+ context 'using :asymmetric mode with a missing public key' do
88
+ let(:model) { h_set_trigger('encryptor_gpg_asymmetric_missing') }
89
+
90
+ # backup01 will be preloaded.
91
+ # backup02 will be imported from :keys
92
+ # backupfoo will be a missing recipient
93
+ it 'should encrypt the archive' do
94
+ load_gpg_homedir(model.encryptor, :public, :backup01)
95
+
96
+ model.perform!
97
+
98
+ Backup::Logger.has_warnings?.should be_true
99
+ Backup::Logger.messages.any? {|msg|
100
+ msg =~ /No public key was found in #keys for '<backupfoo@foo.com>'/
101
+ }.should be_true
102
+
103
+ File.exist?(archive_file_for(model)).should be_true
104
+
105
+ [:backup01, :backup02].each do |recipient|
106
+ load_gpg_homedir(model.encryptor, :private, recipient)
107
+ can_decrypt?(model).should be_true
108
+ end
109
+ end
110
+ end # context 'using :asymmetric mode with a missing public key'
111
+
112
+ context 'using :asymmetric mode with no valid public keys' do
113
+ let(:model) { h_set_trigger('encryptor_gpg_asymmetric_fail') }
114
+
115
+ it 'should abort the backup' do
116
+ model.perform!
117
+
118
+ # issues warnings about the missing keys
119
+ Backup::Logger.has_warnings?.should be_true
120
+ Backup::Logger.messages.any? {|msg|
121
+ msg =~ /No public key was found in #keys for '<backupfoo@foo.com>'/
122
+ }.should be_true
123
+ Backup::Logger.messages.any? {|msg|
124
+ msg =~ /No public key was found in #keys for '<backupfoo2@foo.com>'/
125
+ }.should be_true
126
+
127
+ # issues warning about not being able to perform asymmetric encryption
128
+ Backup::Logger.messages.any? {|msg|
129
+ msg =~ /No recipients available for asymmetric encryption/
130
+ }.should be_true
131
+
132
+ # Since there are no other options for encryption,
133
+ # the backup failes with an error.
134
+ Backup::Logger.messages.any? {|msg|
135
+ msg =~ /\[error\]\s+ModelError/
136
+ }.should be_true
137
+ Backup::Logger.messages.any? {|msg|
138
+ msg =~ /\[error\]\s+Reason: Encryptor::GPG::EncryptionError/
139
+ }.should be_true
140
+ Backup::Logger.messages.any? {|msg|
141
+ msg =~ /\[error\]\s+Encryption could not be performed for mode 'asymmetric'/
142
+ }.should be_true
143
+
144
+ # Although, any further backup models would be run, as this error
145
+ # is rescued in Backup::Model#perform
146
+ Backup::Logger.messages.any? {|msg|
147
+ msg =~ /Backup will now attempt to continue/
148
+ }.should be_true
149
+
150
+ File.exist?(archive_file_for(model)).should be_false
151
+ end
152
+ end # context 'using :asymmetric mode with no valid public keys'
153
+
154
+ context 'using :symmetric mode' do
155
+ let(:model) { h_set_trigger('encryptor_gpg_symmetric') }
156
+
157
+ it 'should encrypt the archive' do
158
+ model.perform!
159
+
160
+ Backup::Logger.has_warnings?.should be_false
161
+
162
+ File.exist?(archive_file_for(model)).should be_true
163
+
164
+ can_decrypt?(model, 'a secret').should be_true
165
+
166
+ # note that without specifying any preferences, the default
167
+ # algorithm used is CAST5
168
+ Backup::Logger.messages.any? {|msg|
169
+ msg =~ /gpg: CAST5 encrypted data/
170
+ }.should be_true
171
+ end
172
+ end # context 'using :symmetric mode'
173
+
174
+ # The #gpg_config preferences should also be able to override the algorithm
175
+ # preferences in the recipients' public keys, but the gpg output doesn't
176
+ # give us an easy way to check this. You'd have to inspect the leading bytes
177
+ # of the encrypted file per RFC4880, and I'm not going that far :)
178
+ context 'using :symmetric mode with given gpg_config' do
179
+ let(:model) { h_set_trigger('encryptor_gpg_symmetric_with_config') }
180
+
181
+ it 'should encrypt the archive using the proper algorithm preference' do
182
+ model.perform!
183
+
184
+ Backup::Logger.has_warnings?.should be_false
185
+
186
+ File.exist?(archive_file_for(model)).should be_true
187
+
188
+ can_decrypt?(model, 'a secret').should be_true
189
+
190
+ # preferences set in #gpg_config specified using AES256 before CAST5
191
+ Backup::Logger.messages.any? {|msg|
192
+ msg =~ /gpg: AES256 encrypted data/
193
+ }.should be_true
194
+ end
195
+ end # context 'using :symmetric mode with given gpg_config'
196
+
197
+ context 'using :both mode' do
198
+ let(:model) { h_set_trigger('encryptor_gpg_both') }
199
+
200
+ it 'should encrypt the archive' do
201
+ # Preload key for backup01.
202
+ # Key for backup03 will be imported when the model is performed.
203
+ load_gpg_homedir(model.encryptor, :public, :backup01)
204
+
205
+ model.perform!
206
+
207
+ Backup::Logger.has_warnings?.should be_false
208
+
209
+ File.exist?(archive_file_for(model)).should be_true
210
+
211
+ # make sure both recipients can decrypt the archive
212
+ [:backup01, :backup03].each do |recipient|
213
+ load_gpg_homedir(model.encryptor, :private, recipient)
214
+ can_decrypt?(model).should be_true
215
+ end
216
+
217
+ # with no private keys in the keyring,
218
+ # archive can be decrypted using the passphrase.
219
+ load_gpg_homedir(model.encryptor, :private, nil)
220
+ can_decrypt?(model, 'a secret').should be_true
221
+ end
222
+ end # context 'using :both mode'
223
+
224
+ context 'using :both mode with no valid asymmetric recipients' do
225
+ let(:model) { h_set_trigger('encryptor_gpg_both_no_asymmetric') }
226
+
227
+ it 'should encrypt the archive using only symmetric encryption' do
228
+ # we'll load backup02, but this isn't one of the :recipients
229
+ load_gpg_homedir(model.encryptor, :public, :backup02)
230
+
231
+ model.perform!
232
+
233
+ # issues warnings about the missing keys
234
+ Backup::Logger.has_warnings?.should be_true
235
+ Backup::Logger.messages.any? {|msg|
236
+ msg =~ /No public key was found in #keys for '16325C61'/
237
+ }.should be_true
238
+ Backup::Logger.messages.any? {|msg|
239
+ msg =~ /No public key was found in #keys for '<backup03@foo.com>'/
240
+ }.should be_true
241
+
242
+ # issues warning about not being able to perform asymmetric encryption
243
+ Backup::Logger.messages.any? {|msg|
244
+ msg =~ /No recipients available for asymmetric encryption/
245
+ }.should be_true
246
+
247
+ # backup proceeded, since symmetric encryption could still be performed
248
+ File.exist?(archive_file_for(model)).should be_true
249
+
250
+ # with no private keys in the keyring,
251
+ # archive can be decrypted using the passphrase.
252
+ load_gpg_homedir(model.encryptor, :private, nil)
253
+ can_decrypt?(model, 'a secret').should be_true
254
+ end
255
+ end # context 'using :both mode with no valid asymmetric recipients'
256
+
257
+ context 'when using the deprecated #key accessor' do
258
+ let(:model) {
259
+ # See notes in spec-live/spec_helper.rb
260
+ h_set_single_model do
261
+ Backup::Model.new(:encryptor_gpg_deprecate_key, 'test_label') do
262
+ archive :test_archive, &Backup::SpecLive::ARCHIVE_JOB
263
+ encrypt_with 'GPG' do |e|
264
+ e.key = Backup::SpecLive::GPGKeys[:backup03][:public]
265
+ end
266
+ store_with 'Local'
267
+ end
268
+ end
269
+ }
270
+
271
+ it 'should log a warning and store an encrypted archive' do
272
+ model.perform!
273
+
274
+ Backup::Logger.has_warnings?.should be_true
275
+ Backup::Logger.messages.any? {|msg|
276
+ msg =~ /GPG#key has been deprecated/
277
+ }.should be_true
278
+
279
+ File.exist?(archive_file_for(model)).should be_true
280
+
281
+ load_gpg_homedir(model.encryptor, :private, :backup03)
282
+
283
+ can_decrypt?(model).should be_true
284
+ end
285
+ end # context 'when using the deprecated #key accessor'
286
+
287
+ end
@@ -9,33 +9,49 @@ describe 'Notifier::Mail',
9
9
 
10
10
  it 'should send a success email' do
11
11
  model = h_set_trigger(trigger)
12
- expect do
13
- model.perform!
14
- end.not_to raise_error
12
+ model.perform!
13
+
14
+ Backup::Logger.has_warnings?.should be_false
15
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
15
16
  end
16
17
 
17
18
  it 'should send a warning email' do
18
19
  model = h_set_trigger(trigger)
19
20
  Backup::Logger.warn 'You have been warned!'
20
- expect do
21
- model.perform!
22
- end.not_to raise_error
21
+ model.perform!
22
+
23
+ Backup::Logger.has_warnings?.should be_true
24
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
23
25
  end
24
26
 
25
27
  it 'should send a failure email for non-fatal errors' do
26
28
  model = h_set_trigger(trigger)
27
29
  model.stubs(:databases).raises('A successful failure?')
28
- expect do
29
- model.perform!
30
- end.not_to raise_error
30
+ model.perform!
31
+
32
+ Backup::Logger.has_warnings?.should be_false
33
+ Backup::Logger.messages.any? {|msg|
34
+ msg =~ /\[error\]\s+A successful failure/
35
+ }.should be_true
36
+ Backup::Logger.messages.any? {|msg|
37
+ msg =~ /Backup will now attempt to continue/
38
+ }.should be_true
31
39
  end
32
40
 
33
- it 'should send a failure email fatal errors' do
41
+ it 'should send a failure email for fatal errors' do
34
42
  model = h_set_trigger(trigger)
35
43
  model.stubs(:databases).raises(NoMemoryError, 'with increasing frequency...')
36
44
  expect do
37
45
  model.perform!
38
- end.to raise_error
46
+ end.to raise_error(SystemExit)
47
+
48
+ Backup::Logger.has_warnings?.should be_false
49
+ Backup::Logger.messages.any? {|msg|
50
+ msg =~ /with increasing frequency/
51
+ }.should be_true
52
+ Backup::Logger.messages.any? {|msg|
53
+ msg =~ /Backup will now exit/
54
+ }.should be_true
39
55
  end
40
56
  end # describe 'Notifier::Mail :smtp'
41
57
 
@@ -45,9 +61,11 @@ describe 'Notifier::Mail',
45
61
 
46
62
  it 'should send a success email' do
47
63
  model = h_set_trigger(trigger)
48
- expect do
49
- model.perform!
50
- end.not_to raise_error
64
+ model.perform!
65
+
66
+ Backup::Logger.has_warnings?.should be_false
67
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
68
+
51
69
  File.exist?(test_email).should be_true
52
70
  File.read(test_email).should match(/without any errors/)
53
71
  end
@@ -55,9 +73,11 @@ describe 'Notifier::Mail',
55
73
  it 'should send a warning email' do
56
74
  model = h_set_trigger(trigger)
57
75
  Backup::Logger.warn 'You have been warned!'
58
- expect do
59
- model.perform!
60
- end.not_to raise_error
76
+ model.perform!
77
+
78
+ Backup::Logger.has_warnings?.should be_true
79
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
80
+
61
81
  File.exist?(test_email).should be_true
62
82
  File.read(test_email).should match(/You have been warned/)
63
83
  end
@@ -65,19 +85,35 @@ describe 'Notifier::Mail',
65
85
  it 'should send a failure email for non-fatal errors' do
66
86
  model = h_set_trigger(trigger)
67
87
  model.stubs(:databases).raises('A successful failure?')
68
- expect do
69
- model.perform!
70
- end.not_to raise_error
88
+ model.perform!
89
+
90
+ Backup::Logger.has_warnings?.should be_false
91
+ Backup::Logger.messages.any? {|msg|
92
+ msg =~ /\[error\]\s+A successful failure/
93
+ }.should be_true
94
+ Backup::Logger.messages.any? {|msg|
95
+ msg =~ /Backup will now attempt to continue/
96
+ }.should be_true
97
+
71
98
  File.exist?(test_email).should be_true
72
99
  File.read(test_email).should match(/successful failure/)
73
100
  end
74
101
 
75
- it 'should send a failure email fatal errors' do
102
+ it 'should send a failure email for fatal errors' do
76
103
  model = h_set_trigger(trigger)
77
104
  model.stubs(:databases).raises(NoMemoryError, 'with increasing frequency...')
78
105
  expect do
79
106
  model.perform!
80
- end.to raise_error
107
+ end.to raise_error(SystemExit)
108
+
109
+ Backup::Logger.has_warnings?.should be_false
110
+ Backup::Logger.messages.any? {|msg|
111
+ msg =~ /with increasing frequency/
112
+ }.should be_true
113
+ Backup::Logger.messages.any? {|msg|
114
+ msg =~ /Backup will now exit/
115
+ }.should be_true
116
+
81
117
  File.exist?(test_email).should be_true
82
118
  File.read(test_email).should match(/with increasing frequency/)
83
119
  end
@@ -8,6 +8,11 @@ require 'bundler/setup'
8
8
  # Load Backup
9
9
  require 'backup'
10
10
 
11
+ # Backup::SpecLive::GPGKeys
12
+ # Loaded here so these are available in backups/models.rb
13
+ # as well as within encryptor/gpg_spec.rb
14
+ require File.expand_path('../encryptor/gpg_keys.rb', __FILE__)
15
+
11
16
  module Backup
12
17
  module SpecLive
13
18
  PATH = File.expand_path('..', __FILE__)
@@ -15,6 +20,11 @@ module Backup
15
20
  TMP_PATH = PATH + '/tmp'
16
21
  SYNC_PATH = PATH + '/sync'
17
22
 
23
+ ARCHIVE_JOB = lambda do |archive|
24
+ archive.add File.expand_path('../../lib/backup', __FILE__)
25
+ archive.exclude File.expand_path('../../lib/backup/storage', __FILE__)
26
+ end
27
+
18
28
  config = PATH + '/backups/config.yml'
19
29
  if File.exist?(config)
20
30
  CONFIG = YAML.load_file(config)
@@ -24,14 +34,57 @@ module Backup
24
34
  exit!
25
35
  end
26
36
 
37
+ class << self
38
+ attr_accessor :load_models
39
+ end
40
+
27
41
  module ExampleHelpers
28
42
 
43
+ # This method loads all defaults in config.rb and all the Models
44
+ # in models.rb, then returns the Model for the given trigger.
29
45
  def h_set_trigger(trigger)
46
+ Backup::SpecLive.load_models = true
47
+ Backup::Logger.clear!
48
+ Backup::Model.all.clear
49
+ Backup::Config.load_config!
50
+ model = Backup::Model.find(trigger)
51
+ model.prepare!
52
+ model
53
+ end
54
+
55
+ # This method can be used to setup a test where you need to setup
56
+ # and perform a single Model that can not be setup in models.rb.
57
+ # This is primarily for Models used to test deprecations, since
58
+ # those warnings will be output when the Model is instantiated
59
+ # and will pollute the output of all other tests.
60
+ #
61
+ # Usage:
62
+ # model = h_set_single_model do
63
+ # Backup::Model.new(:test_trigger, 'test label') do
64
+ # ...setup model...
65
+ # end
66
+ # end
67
+ #
68
+ # The block doesn't have to return the model, as it will be retrieved
69
+ # from Model.all (since it will be the only one).
70
+ #
71
+ # Remember when defining the model that the DSL constants won't be
72
+ # available, as the block is not being evaluated in the context of
73
+ # the Backup::Config module. So, just use strings instead.
74
+ # e.g. store_with 'Local' vs store_with Local
75
+ #
76
+ # Note this will still load any defaults setup in config.rb, so don't
77
+ # do anything in config.rb that would generate a deprecation warning :)
78
+ #
79
+ def h_set_single_model(&block)
80
+ Backup::SpecLive.load_models = false
30
81
  Backup::Logger.clear!
31
82
  Backup::Model.all.clear
32
83
  Backup::Config.load_config!
33
- FileUtils.mkdir_p(File.join(Backup::Config.data_path, trigger))
34
- Backup::Model.find(trigger)
84
+ block.call
85
+ model = Backup::Model.all.first
86
+ model.prepare!
87
+ model
35
88
  end
36
89
 
37
90
  def h_clean_data_paths!
@@ -82,4 +135,17 @@ RSpec.configure do |config|
82
135
  end
83
136
  end
84
137
 
85
- puts "\n\nRuby version: #{RUBY_DESCRIPTION}\n\n"
138
+ puts "\nRuby version: #{ RUBY_DESCRIPTION }\n"
139
+
140
+ unless ENV['VERBOSE']
141
+ puts <<-EOS
142
+
143
+ Some of these tests can be slow, so be patient.
144
+ It's recommended you run these with:
145
+ $ VERBOSE=1 rspec spec-live/
146
+ For some tests, [error] and [warning] messages are normal.
147
+ Some could pass, but still have problems.
148
+ So, pay attention to the messages :)
149
+
150
+ EOS
151
+ end