edb 0.5 → 0.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 114baabf1be97194abcb44cd5378b818242be763
4
- data.tar.gz: c4dd125d24d41450cb99f1ab771d5bcec94be42c
3
+ metadata.gz: cf3bbfcc12b3ea9bf8b8d2da7e5acce50e69295f
4
+ data.tar.gz: cdd15ff978c970706a69c1dc0b7f3ca1fd20a9a1
5
5
  SHA512:
6
- metadata.gz: 02bc9d1c1d8d974109f43b33295918bdbafafd0843954b264a2aaa949d95a3149c108437dbad3a7c0e80bb68e8c5a6a2ac1aa280c7936ee37e2366fe533e6139
7
- data.tar.gz: 4391116b96b895acc789149fd42391618f96ef409cae31c38455a5d9ce3ad670d68f0fc35154092cc9093dc40ded9f90e40a5cfe38db574e576c1b8db4947799
6
+ metadata.gz: 427fafb18b6e04e331d689b1efc4a0fa7d3cd4b29afa9f380d5695d3ee6ae54066e27c95ce6d1aef430a90d187d8fb65a23dd1b62f182c4e824ed9fe93d78f1f
7
+ data.tar.gz: 14fbd57f7b9989d8ff27e8035aa3ebd79335b03bbcfcf46f6a82b324bf27b561c9873eddd60a1ac07d4983f4331f856cace405bf47c3aea773618d87a78d75a9
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
+ .ruby-gemset
1
2
  *.gem
2
3
  *2015
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- edb (0.5)
4
+ edb (0.6)
5
5
  aws-sdk (= 1.59.1)
6
+ fast_secure_compare (~> 1.0)
6
7
  ftp_sync (~> 0.4)
7
8
  hkdf (~> 0.2)
8
9
 
@@ -15,6 +16,7 @@ GEM
15
16
  json (~> 1.4)
16
17
  nokogiri (>= 1.4.4)
17
18
  diff-lcs (1.2.5)
19
+ fast_secure_compare (1.0.1)
18
20
  ftp_sync (0.4.3)
19
21
  net-ftp-list (>= 2.1.1)
20
22
  hkdf (0.2.0)
data/README.md CHANGED
@@ -11,10 +11,14 @@ The first one deals with the actual backup process of your database. The second
11
11
  `$ gem install edb`
12
12
 
13
13
  ## Run
14
- Setup and customize `example/edb.yml` (remember also to change the `secret` by running `edb -k`) and then:
14
+ Customize `example/edb.yml` (remember also to change the `secret` by running `$ edb -k`) and then:
15
15
 
16
16
  `$ edb example/edb.yml`
17
17
 
18
+ To decrypt the encrypted files:
19
+
20
+ `$ edb example/edb.yml -d [FILE]`
21
+
18
22
  Consider also to add *EDB* to your cronjobs.
19
23
 
20
24
  ## Available modules
@@ -24,7 +28,7 @@ Consider also to add *EDB* to your cronjobs.
24
28
 
25
29
  ## FAQ
26
30
  **Q:** What if I want to dump two or three MySQL databases?
27
- *A:* Just add a `:MySQL:` block for every database you need to dump.
31
+ *A:* Just add a `:MySQL:` block for every database you need to dump.
28
32
 
29
33
 
30
34
  **Q:** What if I want to save a database to S3 and another one into my local filesystem?
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ end
13
13
 
14
14
  task :test do
15
15
  FileUtils.cd 'specs' do
16
- Dir['*_spec.rb'].each do |spec|
16
+ Dir['./**/*_spec.rb'].each do |spec|
17
17
  sh "rspec #{spec} --backtrace --color --format doc"
18
18
  end
19
19
  end
data/bin/edb CHANGED
@@ -1,4 +1,4 @@
1
- #! /usr/bin/env ruby
1
+ #!/usr/bin/env ruby
2
2
  #--
3
3
  # Copyright(C) 2015 Giovanni Capuano <webmaster@giovannicapuano.net>
4
4
  #
@@ -26,9 +26,11 @@ require 'edb'
26
26
  require 'yaml'
27
27
  require 'optparse'
28
28
 
29
+ @tasks = {}
30
+
29
31
  OptionParser.new do |opts|
30
32
  opts.version = EDB::VERSION
31
- opts.banner = "Usage: edb [-k] [file]"
33
+ opts.banner = "Usage: edb [-kd] [CONFIG_FILE]"
32
34
 
33
35
  opts.on('-k', '--generate-key', 'Generate a random key of 32 characters') do
34
36
  require 'securerandom'
@@ -36,6 +38,10 @@ OptionParser.new do |opts|
36
38
  puts SecureRandom.hex(32)
37
39
  exit
38
40
  end
41
+
42
+ opts.on('-d', '--decrypt [FILE]', 'Decrypt given file') do |file|
43
+ @tasks[:decrypt] = { file_to_decrypt: File.expand_path(file) }
44
+ end
39
45
  end.parse!
40
46
 
41
47
  config_file = ARGV[0]
@@ -43,5 +49,11 @@ if !config_file || !config_file.end_with?('.yml') || !File.exists?(config_file)
43
49
  abort 'Please provide a valid configuration file.'
44
50
  end
45
51
 
46
- opts = YAML.load_file(config_file)
47
- EDB::Dumper.new(opts).run
52
+ EDB.opts = YAML.load_file(config_file)
53
+
54
+ if @tasks.has_key?(:decrypt)
55
+ ciphering_method = EDB.opts[:CRYPTOGRAPHY].first[0]
56
+ EDB::Cryptography.decrypt ciphering_method, @tasks[:decrypt][:file_to_decrypt]
57
+ else
58
+ EDB::Dumper.new.run
59
+ end
data/edb.gemspec CHANGED
@@ -18,4 +18,5 @@ Gem::Specification.new { |s|
18
18
  s.add_dependency 'aws-sdk', '1.59.1'
19
19
  s.add_dependency 'ftp_sync', '~> 0.4'
20
20
  s.add_dependency 'hkdf', '~> 0.2'
21
+ s.add_dependency 'fast_secure_compare', '~> 1.0'
21
22
  }
@@ -28,14 +28,34 @@ module EDB
28
28
  class << self
29
29
  include ::EDB::IsModuleSupported
30
30
 
31
- def encrypt(method, file)
32
- this_module = to_module(method)
33
- this_module.encrypt(file)
31
+ def encrypt(method, filename)
32
+ ::EDB::Logger.log(:info, "Encrypting #{filename}...")
33
+
34
+ ciphered_data = File.open(filename, 'rb') do |file|
35
+ data = file.read
36
+
37
+ this_module = to_module(method)
38
+ ciphered_data = this_module.encrypt(data)
39
+ end
40
+
41
+ File.open(filename, 'wb') do |file|
42
+ file.write(ciphered_data)
43
+ end
34
44
  end
35
45
 
36
- def decrypt(method, file)
37
- this_module = to_module(method)
38
- this_module.decrypt(file)
46
+ def decrypt(method, filename)
47
+ ::EDB::Logger.log(:info, "Decrypting #{filename}...")
48
+
49
+ data = File.open(filename, 'rb') do |file|
50
+ ciphered_data = file.read
51
+
52
+ this_module = to_module(method)
53
+ data = this_module.decrypt(ciphered_data)
54
+ end
55
+
56
+ File.open("#{filename}.dec", 'wb') do |file|
57
+ file.write(data)
58
+ end
39
59
  end
40
60
 
41
61
  private
@@ -23,52 +23,54 @@
23
23
  #++
24
24
  require 'openssl'
25
25
  require 'hkdf'
26
+ require 'fast_secure_compare/fast_secure_compare'
26
27
 
27
28
  module EDB
28
29
  module Cryptography
29
30
  module AES_256_CBC
30
31
  class << self
31
- def encrypt(source)
32
- ::EDB::Logger.log(:info, "Encrypting #{source}...")
32
+ def encrypt(data)
33
+ raise "Cannot encrypt #{filename}: It's empty" if data.empty?
33
34
 
34
35
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
35
36
  cipher.encrypt
36
- cipher.key = hash_key(::EDB.opts[:CRYPTOGRAPHY][:AES_256_CBC][:secret])
37
- cipher.iv = iv = cipher.random_iv
38
37
 
39
- contents = File.read(source)
40
- raise "Cannot encrypt #{source}: It's empty" if contents.empty?
38
+ cipher.key, authentication_key = hash_keychain(::EDB.opts[:CRYPTOGRAPHY][:AES_256_CBC][:secret])
39
+ cipher.iv = iv = cipher.random_iv
41
40
 
42
- File.open(source, 'wb') do |file|
43
- ciphered_content = cipher.update(contents) + cipher.final
44
- ciphered_content << iv
45
- file.write(ciphered_content)
46
- end
47
- end
41
+ ciphered_data = cipher.update(data) + cipher.final
42
+ ciphered_data << iv
48
43
 
49
- def decrypt(source, new_file = true)
50
- ::EDB::Logger.log(:info, "Decrypting #{source}...")
44
+ authentication = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), authentication_key, ciphered_data)
45
+ ciphered_data << authentication
46
+ end
51
47
 
52
- ciphered_content = File.read(source)
53
- raise "Cannot decrypt #{source}: It's empty" if ciphered_content.empty?
54
- ciphered_content_length = ciphered_content.length
48
+ def decrypt(ciphered_data)
49
+ raise "Cannot decrypt #{filename}: It's empty" if ciphered_data.length < 64
55
50
 
56
51
  decipher = OpenSSL::Cipher.new('AES-256-CBC')
57
52
  decipher.decrypt
58
53
 
59
- decipher.key = hash_key(::EDB.opts[:CRYPTOGRAPHY][:AES_256_CBC][:secret])
60
- decipher.iv = iv = ciphered_content.slice!(ciphered_content_length - 16, ciphered_content_length)
54
+ authentication = slice_str!(ciphered_data, 32)
55
+ decipher.key, authentication_key = hash_keychain(::EDB.opts[:CRYPTOGRAPHY][:AES_256_CBC][:secret])
56
+
57
+ new_authentication = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), authentication_key, ciphered_data)
58
+ raise 'Authentication failed.' unless FastSecureCompare.compare(authentication, new_authentication)
61
59
 
62
- new_source = new_file ? "#{source}.dec" : source
63
- File.open(new_source, 'wb') do |file|
64
- deciphered_content = decipher.update(ciphered_content) + decipher.final
65
- file.write(deciphered_content)
66
- end
60
+ decipher.iv = slice_str!(ciphered_data, 16)
61
+
62
+ deciphered_data = decipher.update(ciphered_data) + decipher.final
67
63
  end
68
64
 
69
65
  private
70
- def hash_key(s)
71
- HKDF.new(s).next_bytes(32)
66
+ def slice_str!(str, n)
67
+ len = str.length
68
+ str.slice!(len - n, len)
69
+ end
70
+
71
+ def hash_keychain(s)
72
+ hkdf = HKDF.new(s)
73
+ [ hkdf.next_bytes(64), hkdf.next_bytes(64) ]
72
74
  end
73
75
  end
74
76
  end
@@ -43,7 +43,7 @@ module EDB
43
43
  #{db[:database]} > #{files[:dump]}
44
44
  }.join(' ')
45
45
 
46
- system "#{mysqldump} #{args}"
46
+ Kernel.system "#{mysqldump} #{args}"
47
47
 
48
48
  files.values
49
49
  end
@@ -35,11 +35,11 @@ module EDB
35
35
 
36
36
  ::EDB::Logger.log(:info, "Dumping #{db[:database]}...")
37
37
  pg_dump = db[:binpath] && !db[:binpath].empty? ? File.join(db[:binpath], 'pg_dump') : 'pg_dump'
38
- system "PGPASSWORD='#{db[:password]}' #{pg_dump} -h #{db[:host]} -p #{db[:port]} -U #{db[:username]} -F c -b -f '#{files[:dump]}' #{db[:database]}"
38
+ Kernel.system "PGPASSWORD='#{db[:password]}' #{pg_dump} -h #{db[:host]} -p #{db[:port]} -U #{db[:username]} -F c -b -f '#{files[:dump]}' #{db[:database]}"
39
39
 
40
40
  ::EDB::Logger.log(:info, 'Dumping the cluster...')
41
41
  pg_dumpall = db[:binpath] && !db[:binpath].empty? ? File.join(db[:binpath], 'pg_dumpall') : 'pg_dumpall'
42
- system "PGPASSWORD='#{db[:password]}' #{pg_dumpall} -h #{db[:host]} -p #{db[:port]} -U #{db[:username]} -f '#{files[:cluster]}'"
42
+ Kernel.system "PGPASSWORD='#{db[:password]}' #{pg_dumpall} -h #{db[:host]} -p #{db[:port]} -U #{db[:username]} -f '#{files[:cluster]}'"
43
43
 
44
44
  files.values
45
45
  end
data/lib/edb/dumper.rb CHANGED
@@ -26,8 +26,8 @@ module EDB
26
26
  class Dumper
27
27
  PATTERN = ->(time) { "#{time.day}_#{time.month}_#{time.year}" }
28
28
 
29
- def initialize(opts)
30
- ::EDB.opts = opts
29
+ def initialize(opts = nil)
30
+ ::EDB.opts ||= opts if opts
31
31
  end
32
32
 
33
33
  def run
data/lib/edb/version.rb CHANGED
@@ -23,5 +23,5 @@
23
23
  #++
24
24
 
25
25
  module EDB
26
- VERSION = '0.5'
26
+ VERSION = '0.6'
27
27
  end
@@ -0,0 +1,57 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe EDB::Cryptography::AES_256_CBC do
4
+ describe '#encrypt' do
5
+ context 'given string is empty' do
6
+ it 'raises an exception' do
7
+ expect { EDB::Cryptography::AES_256_CBC.encrypt('') }.to raise_error
8
+ end
9
+ end
10
+
11
+ context 'non-empty string is given' do
12
+ let(:data) { 'kanbaru is mai waifu' }
13
+ let(:ciphered_data) { EDB::Cryptography::AES_256_CBC.encrypt(data) }
14
+
15
+ it 'returns a valid AES-256-CBC ciphered string' do
16
+ expect(ciphered_data.length).to be 80
17
+ end
18
+ end
19
+ end
20
+
21
+ describe '#decrypt' do
22
+ context 'given data is less than 64 chars long' do
23
+ it 'raises an exception' do
24
+ expect { EDB::Cryptography::AES_256_CBC.decrypt('H' * 63) }.to raise_error
25
+ end
26
+ end
27
+
28
+ context 'valid data is given' do
29
+ let(:real_data) { 'kanbaru is mai waifu' }
30
+ let(:ciphered_data) { EDB::Cryptography::AES_256_CBC.encrypt(real_data) }
31
+ let(:data) { EDB::Cryptography::AES_256_CBC.decrypt(ciphered_data) }
32
+
33
+ it 'returns a string matching the original given one' do
34
+ expect(data).to eq real_data
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#slice_str!' do
40
+ let(:str) { 'senjougahara' }
41
+ let(:sliced) { EDB::Cryptography::AES_256_CBC.instance_exec(str) { |str| slice_str!(str, 6) } }
42
+
43
+ it 'returns and remove the last n characters from given string' do
44
+ expect(sliced).to eq 'gahara'
45
+ expect(str).to eq 'senjou'
46
+ end
47
+ end
48
+
49
+ describe '#hash_keychain' do
50
+ let(:keychain) { EDB::Cryptography::AES_256_CBC.instance_eval { hash_keychain('hitagi') } }
51
+
52
+ it 'returns an array with two 64 bytes long keys' do
53
+ expect(keychain[0].length).to be 64
54
+ expect(keychain[1].length).to be 64
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe EDB::DBMS::MySQL do
4
+ describe '#backup' do
5
+ let(:dump) { '/Applications/MAMP/Library/bin/mysqldump --user=username --password=password --single-transaction sample_database > ./sample_database.sql' }
6
+
7
+ it 'calls mysqldump correctly' do
8
+ allow(Kernel).to receive(:system).and_wrap_original do |m, *args|
9
+ expect(args[0]).to eq dump
10
+ end
11
+
12
+ EDB::DBMS::MySQL.backup('.')
13
+ end
14
+
15
+ it 'returns the the files that have been created' do
16
+ allow(Kernel).to receive(:system).and_wrap_original {}
17
+
18
+ results = EDB::DBMS::MySQL.backup('.')
19
+ expect(results).to eq ['./sample_database.sql']
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe EDB::DBMS::PostgreSQL do
4
+ describe '#backup' do
5
+ let(:dump) { "PGPASSWORD='password' /Applications/Postgres.app/Contents/Versions/9.3/bin/pg_dump -h localhost -p 5432 -U username -F c -b -f './sample_database.sql' sample_database" }
6
+ let(:cluster) { "PGPASSWORD='password' /Applications/Postgres.app/Contents/Versions/9.3/bin/pg_dumpall -h localhost -p 5432 -U username -f './cluster.sql'" }
7
+
8
+ it 'calls pg_dump correctly' do
9
+ allow(Kernel).to receive(:system).and_wrap_original do |m, *args|
10
+ expect(args[0]).to satisfy { |c| [dump, cluster].include?(c) }
11
+ end
12
+
13
+ EDB::DBMS::PostgreSQL.backup('.')
14
+ end
15
+
16
+ it 'returns the the files that have been created' do
17
+ allow(Kernel).to receive(:system).and_wrap_original {}
18
+
19
+ results = EDB::DBMS::PostgreSQL.backup('.')
20
+ expect(results).to eq ['./sample_database.sql', './cluster.sql']
21
+ end
22
+ end
23
+ end
data/specs/dbms_spec.rb CHANGED
@@ -1,16 +1,16 @@
1
1
  require_relative 'spec_helper'
2
2
 
3
- describe EDB::Storage do
3
+ describe EDB::DBMS do
4
4
  describe '#supports?' do
5
- context 'given storage device is supported' do
5
+ context 'given DBMS device is supported' do
6
6
  it 'returns true' do
7
- expect(EDB::Storage.supports?('S3')).to be_truthy
7
+ expect(EDB::DBMS.supports?('MySQL')).to be_truthy
8
8
  end
9
9
  end
10
10
 
11
- context 'given storage device is not supported' do
11
+ context 'given DBMS device is not supported' do
12
12
  it 'returns false' do
13
- expect(EDB::Storage.supports?('CappellaAmbrosiana')).to be_falsy
13
+ expect(EDB::DBMS.supports?('CongoDB')).to be_falsy
14
14
  end
15
15
  end
16
16
  end
data/specs/spec_helper.rb CHANGED
@@ -1 +1,4 @@
1
1
  require 'edb'
2
+ require 'yaml'
3
+
4
+ EDB.opts = YAML.load_file('../example/edb.yml')
@@ -2,15 +2,15 @@ require_relative 'spec_helper'
2
2
 
3
3
  describe EDB::Cryptography do
4
4
  describe '#supports?' do
5
- context 'given algorithm is supported' do
5
+ context 'given storage device is supported' do
6
6
  it 'returns true' do
7
- expect(EDB::Cryptography.supports?('AES_256_CBC')).to be_truthy
7
+ expect(EDB::Storage.supports?('S3')).to be_truthy
8
8
  end
9
9
  end
10
10
 
11
- context 'given algorithm is not supported' do
11
+ context 'given storage device is not supported' do
12
12
  it 'returns false' do
13
- expect(EDB::Cryptography.supports?('LOL_WUT_128')).to be_falsy
13
+ expect(EDB::Storage.supports?('CappellaAmbrosiana')).to be_falsy
14
14
  end
15
15
  end
16
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: edb
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: '0.6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giovanni Capuano
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-16 00:00:00.000000000 Z
11
+ date: 2015-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fast_secure_compare
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
55
69
  description: EDB aims to be a framework to make and manage backups of your database.
56
70
  email: webmaster@giovannicapuano.net
57
71
  executables:
@@ -83,7 +97,10 @@ files:
83
97
  - lib/edb/storage/ftp.rb
84
98
  - lib/edb/storage/s3.rb
85
99
  - lib/edb/version.rb
100
+ - specs/cryptography/aes_256_cbc_spec.rb
86
101
  - specs/cryptography_spec.rb
102
+ - specs/dbms/mysql_spec.rb
103
+ - specs/dbms/postgresql_spec.rb
87
104
  - specs/dbms_spec.rb
88
105
  - specs/spec_helper.rb
89
106
  - specs/storage_spec.rb