edb 0.5 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +3 -1
- data/README.md +6 -2
- data/Rakefile +1 -1
- data/bin/edb +16 -4
- data/edb.gemspec +1 -0
- data/lib/edb/cryptography.rb +26 -6
- data/lib/edb/cryptography/aes_256_cbc.rb +28 -26
- data/lib/edb/dbms/mysql.rb +1 -1
- data/lib/edb/dbms/postgresql.rb +2 -2
- data/lib/edb/dumper.rb +2 -2
- data/lib/edb/version.rb +1 -1
- data/specs/cryptography/aes_256_cbc_spec.rb +57 -0
- data/specs/dbms/mysql_spec.rb +22 -0
- data/specs/dbms/postgresql_spec.rb +23 -0
- data/specs/dbms_spec.rb +5 -5
- data/specs/spec_helper.rb +3 -0
- data/specs/storage_spec.rb +4 -4
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf3bbfcc12b3ea9bf8b8d2da7e5acce50e69295f
|
4
|
+
data.tar.gz: cdd15ff978c970706a69c1dc0b7f3ca1fd20a9a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 427fafb18b6e04e331d689b1efc4a0fa7d3cd4b29afa9f380d5695d3ee6ae54066e27c95ce6d1aef430a90d187d8fb65a23dd1b62f182c4e824ed9fe93d78f1f
|
7
|
+
data.tar.gz: 14fbd57f7b9989d8ff27e8035aa3ebd79335b03bbcfcf46f6a82b324bf27b561c9873eddd60a1ac07d4983f4331f856cace405bf47c3aea773618d87a78d75a9
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
edb (0.
|
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
|
-
|
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
data/bin/edb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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 [-
|
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
|
-
|
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
data/lib/edb/cryptography.rb
CHANGED
@@ -28,14 +28,34 @@ module EDB
|
|
28
28
|
class << self
|
29
29
|
include ::EDB::IsModuleSupported
|
30
30
|
|
31
|
-
def encrypt(method,
|
32
|
-
|
33
|
-
|
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,
|
37
|
-
|
38
|
-
|
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(
|
32
|
-
|
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
|
-
|
40
|
-
|
38
|
+
cipher.key, authentication_key = hash_keychain(::EDB.opts[:CRYPTOGRAPHY][:AES_256_CBC][:secret])
|
39
|
+
cipher.iv = iv = cipher.random_iv
|
41
40
|
|
42
|
-
|
43
|
-
|
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
|
-
|
50
|
-
|
44
|
+
authentication = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), authentication_key, ciphered_data)
|
45
|
+
ciphered_data << authentication
|
46
|
+
end
|
51
47
|
|
52
|
-
|
53
|
-
raise "Cannot decrypt #{
|
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
|
-
|
60
|
-
decipher.
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
71
|
-
|
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
|
data/lib/edb/dbms/mysql.rb
CHANGED
data/lib/edb/dbms/postgresql.rb
CHANGED
@@ -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
data/lib/edb/version.rb
CHANGED
@@ -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::
|
3
|
+
describe EDB::DBMS do
|
4
4
|
describe '#supports?' do
|
5
|
-
context 'given
|
5
|
+
context 'given DBMS device is supported' do
|
6
6
|
it 'returns true' do
|
7
|
-
expect(EDB::
|
7
|
+
expect(EDB::DBMS.supports?('MySQL')).to be_truthy
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
context 'given
|
11
|
+
context 'given DBMS device is not supported' do
|
12
12
|
it 'returns false' do
|
13
|
-
expect(EDB::
|
13
|
+
expect(EDB::DBMS.supports?('CongoDB')).to be_falsy
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/specs/spec_helper.rb
CHANGED
data/specs/storage_spec.rb
CHANGED
@@ -2,15 +2,15 @@ require_relative 'spec_helper'
|
|
2
2
|
|
3
3
|
describe EDB::Cryptography do
|
4
4
|
describe '#supports?' do
|
5
|
-
context 'given
|
5
|
+
context 'given storage device is supported' do
|
6
6
|
it 'returns true' do
|
7
|
-
expect(EDB::
|
7
|
+
expect(EDB::Storage.supports?('S3')).to be_truthy
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
context 'given
|
11
|
+
context 'given storage device is not supported' do
|
12
12
|
it 'returns false' do
|
13
|
-
expect(EDB::
|
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.
|
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-
|
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
|