edb 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
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