heirloom 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/CHANGELOG +7 -0
- data/README.md +1 -11
- data/lib/heirloom.rb +2 -0
- data/lib/heirloom/acl/s3.rb +8 -9
- data/lib/heirloom/archive/authorizer.rb +3 -1
- data/lib/heirloom/archive/destroyer.rb +3 -3
- data/lib/heirloom/archive/downloader.rb +4 -4
- data/lib/heirloom/archive/reader.rb +15 -5
- data/lib/heirloom/archive/uploader.rb +13 -9
- data/lib/heirloom/archive/writer.rb +2 -0
- data/lib/heirloom/cipher.rb +2 -0
- data/lib/heirloom/cipher/data.rb +37 -19
- data/lib/heirloom/cipher/file.rb +23 -22
- data/lib/heirloom/cipher/shared.rb +19 -0
- data/lib/heirloom/cli/upload.rb +2 -1
- data/lib/heirloom/directory/directory.rb +12 -1
- data/lib/heirloom/exceptions.rb +11 -0
- data/lib/heirloom/uploader/s3.rb +34 -23
- data/lib/heirloom/utils.rb +1 -0
- data/lib/heirloom/utils/file.rb +30 -0
- data/lib/heirloom/version.rb +1 -1
- data/spec/acl/s3_spec.rb +1 -1
- data/spec/archive/authorizer_spec.rb +25 -13
- data/spec/archive/destroyer_spec.rb +1 -1
- data/spec/archive/downloader_spec.rb +4 -4
- data/spec/archive/reader_spec.rb +19 -5
- data/spec/archive/uploader_spec.rb +47 -20
- data/spec/cipher/data_spec.rb +52 -35
- data/spec/cipher/file_spec.rb +23 -9
- data/spec/cli/upload_spec.rb +1 -0
- data/spec/directory/directory_spec.rb +36 -10
- data/spec/uploader/s3_spec.rb +64 -0
- data/spec/utils/file_spec.rb +30 -0
- metadata +22 -14
data/lib/heirloom/uploader/s3.rb
CHANGED
@@ -9,48 +9,59 @@ module Heirloom
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def upload_file(args)
|
12
|
-
bucket
|
13
|
-
file
|
14
|
-
id
|
15
|
-
key_name
|
16
|
-
key_folder
|
17
|
-
name
|
12
|
+
bucket = args[:bucket]
|
13
|
+
file = args[:file]
|
14
|
+
id = args[:id]
|
15
|
+
key_name = args[:key_name]
|
16
|
+
key_folder = args[:key_folder]
|
17
|
+
name = args[:name]
|
18
18
|
public_readable = args[:public_readable]
|
19
19
|
|
20
|
+
body = File.open file
|
20
21
|
s3_bucket = s3.get_bucket bucket
|
21
22
|
|
22
23
|
@logger.info "Uploading s3://#{bucket}/#{key_folder}/#{key_name}"
|
23
24
|
|
24
25
|
s3_bucket.files.create :key => "#{key_folder}/#{key_name}",
|
25
|
-
:body =>
|
26
|
+
:body => body,
|
26
27
|
:public => public_readable
|
28
|
+
@logger.warn "File is readable by entire Internet." if public_readable
|
27
29
|
|
28
|
-
|
30
|
+
body.close
|
29
31
|
end
|
30
32
|
|
31
33
|
def add_endpoint_attributes(args)
|
32
|
-
bucket
|
33
|
-
id
|
34
|
-
name
|
35
|
-
|
34
|
+
bucket = args[:bucket]
|
35
|
+
id = args[:id]
|
36
|
+
name = args[:name]
|
37
|
+
key_name = args[:key_name]
|
38
|
+
|
39
|
+
domain = "heirloom_#{name}"
|
36
40
|
key_folder = name
|
37
|
-
|
41
|
+
endpoint = endpoints[@region]
|
38
42
|
|
39
|
-
|
40
|
-
http_endpoint = "http://#{endpoints[@region]}/#{bucket}/#{key_folder}/#{key_name}"
|
41
|
-
https_endpoint = "https://#{endpoints[@region]}/#{bucket}/#{key_folder}/#{key_name}"
|
43
|
+
path = "#{bucket}/#{key_folder}/#{key_name}"
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
+
end_point_attributes(path).each_pair do |key, value|
|
46
|
+
add_endpoint_attribute domain, id, key, value
|
47
|
+
end
|
48
|
+
end
|
45
49
|
|
46
|
-
|
47
|
-
@logger.debug "Adding attribute #{http_endpoint}."
|
50
|
+
private
|
48
51
|
|
49
|
-
|
50
|
-
|
52
|
+
def end_point_attributes(path)
|
53
|
+
{
|
54
|
+
"#{@region}-s3-url" => "s3://#{path}",
|
55
|
+
"#{@region}-http-url" => "http://#{endpoints[@region]}/#{path}",
|
56
|
+
"#{@region}-https-url" => "https://#{endpoints[@region]}/#{path}"
|
57
|
+
}
|
51
58
|
end
|
52
59
|
|
53
|
-
|
60
|
+
def add_endpoint_attribute(domain, id, key, value)
|
61
|
+
sdb.put_attributes domain, id, { key => value }
|
62
|
+
@logger.debug "Adding tag #{key}."
|
63
|
+
@logger.debug "Adding tag #{value}."
|
64
|
+
end
|
54
65
|
|
55
66
|
def endpoints
|
56
67
|
{
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'heirloom/utils/file'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Heirloom
|
2
|
+
module Utils
|
3
|
+
module File
|
4
|
+
|
5
|
+
def which(cmd)
|
6
|
+
exts = pathext ? pathext.split(';') : ['']
|
7
|
+
path.split(path_separator).each do |path|
|
8
|
+
exts.each { |ext|
|
9
|
+
exe = "#{path}/#{cmd}#{ext}"
|
10
|
+
return exe if ::File.executable? exe
|
11
|
+
}
|
12
|
+
end
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def path_separator
|
17
|
+
::File::PATH_SEPARATOR
|
18
|
+
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
ENV['PATH']
|
22
|
+
end
|
23
|
+
|
24
|
+
def pathext
|
25
|
+
ENV['PATHEXT']
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/heirloom/version.rb
CHANGED
data/spec/acl/s3_spec.rb
CHANGED
@@ -29,7 +29,7 @@ describe Heirloom do
|
|
29
29
|
with("bucket", "key-folder/key.tar.gz", {"Owner"=>{"DisplayName"=>"Brett", "ID"=>"123"}, "AccessControlList"=>[{"Grantee"=>{"EmailAddress"=>"acct1@test.com"}, "Permission"=>"READ"}, {"Grantee"=>{"EmailAddress"=>"acct2@test.com"}, "Permission"=>"READ"}, {"Grantee"=>{"DisplayName"=>"Brett", "ID"=>"123"}, "Permission"=>"FULL_CONTROL"}]})
|
30
30
|
|
31
31
|
@s3.allow_read_access_from_accounts :bucket => 'bucket',
|
32
|
-
:key_name => 'key',
|
32
|
+
:key_name => 'key.tar.gz',
|
33
33
|
:key_folder => 'key-folder',
|
34
34
|
:accounts => ['acct1@test.com', 'acct2@test.com']
|
35
35
|
end
|
@@ -3,28 +3,40 @@ require 'spec_helper'
|
|
3
3
|
describe Heirloom do
|
4
4
|
|
5
5
|
before do
|
6
|
-
@config_mock =
|
7
|
-
@logger_mock =
|
6
|
+
@config_mock = mock 'config'
|
7
|
+
@logger_mock = mock 'logger'
|
8
8
|
@logger_mock.stub :info => true, :debug => true
|
9
9
|
@config_mock.should_receive(:logger).and_return(@logger_mock)
|
10
10
|
@authorizer = Heirloom::Authorizer.new :config => @config_mock,
|
11
11
|
:name => 'tim',
|
12
|
-
:id => '123'
|
12
|
+
:id => '123.tar.gz'
|
13
13
|
end
|
14
14
|
|
15
15
|
it "should authorize access to an archive in all regions" do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
and_return(reader)
|
21
|
-
reader.should_receive(:get_bucket).exactly(2).times.
|
16
|
+
s3_acl_mock = mock 's3 acl'
|
17
|
+
reader_mock = mock 'reader mock'
|
18
|
+
reader_mock.stub :key_name => '123.tar.gz'
|
19
|
+
reader_mock.should_receive(:get_bucket).exactly(2).times.
|
22
20
|
and_return('the-bucket')
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
|
22
|
+
accounts = [ "test@a.com", "a@test.com", "test@test.co", "test@test.co.uk" ]
|
23
|
+
|
24
|
+
Heirloom::Reader.should_receive(:new).
|
25
|
+
with(:config => @config_mock,
|
26
|
+
:name => 'tim',
|
27
|
+
:id => '123.tar.gz').
|
28
|
+
and_return reader_mock
|
29
|
+
Heirloom::ACL::S3.should_receive(:new).
|
30
|
+
with(:config => @config_mock,
|
31
|
+
:region => 'us-west-1').
|
32
|
+
and_return s3_acl_mock
|
33
|
+
Heirloom::ACL::S3.should_receive(:new).
|
34
|
+
with(:config => @config_mock,
|
35
|
+
:region => 'us-west-2').
|
36
|
+
and_return s3_acl_mock
|
37
|
+
s3_acl_mock.should_receive(:allow_read_access_from_accounts).
|
26
38
|
exactly(2).times.
|
27
|
-
with(:key_name => '123',
|
39
|
+
with(:key_name => '123.tar.gz',
|
28
40
|
:key_folder => 'tim',
|
29
41
|
:bucket => 'the-bucket',
|
30
42
|
:accounts => accounts)
|
@@ -17,7 +17,7 @@ describe Heirloom do
|
|
17
17
|
@reader_mock.should_receive(:get_bucket).
|
18
18
|
with(:region => 'us-west-1').
|
19
19
|
and_return 'bucket-us-west-1'
|
20
|
-
|
20
|
+
@reader_mock.stub :key_name => '123.tar.gz'
|
21
21
|
|
22
22
|
@s3_destroyer_mock = mock 's3 destroyer'
|
23
23
|
Heirloom::Destroyer::S3.should_receive(:new).
|
@@ -85,7 +85,7 @@ describe Heirloom do
|
|
85
85
|
before do
|
86
86
|
@s3_downloader_mock.should_receive(:download_file).
|
87
87
|
with(:bucket => 'bucket-us-west-1',
|
88
|
-
:key => 'tim/123.tar.gz').
|
88
|
+
:key => 'tim/123.tar.gz.gpg').
|
89
89
|
and_return 'encrypted_data'
|
90
90
|
Heirloom::Cipher::Data.should_receive(:new).
|
91
91
|
with(:config => @config_mock).
|
@@ -136,10 +136,10 @@ describe Heirloom do
|
|
136
136
|
end
|
137
137
|
|
138
138
|
it "should return false if the decrypt_data returns false" do
|
139
|
-
@downloader.download(:region
|
139
|
+
@downloader.download(:region => 'us-west-1',
|
140
140
|
:bucket_prefix => 'bucket',
|
141
|
-
:extract
|
142
|
-
:secret
|
141
|
+
:extract => false,
|
142
|
+
:secret => 'badsecret').should be_false
|
143
143
|
end
|
144
144
|
|
145
145
|
end
|
data/spec/archive/reader_spec.rb
CHANGED
@@ -52,7 +52,7 @@ describe Heirloom do
|
|
52
52
|
with("select * from `heirloom_tim` where itemName() = '123'").
|
53
53
|
and_return( { '123' =>
|
54
54
|
{ 'us-west-1-s3-url' =>
|
55
|
-
[ 's3://the-bucket/the-
|
55
|
+
[ 's3://the-bucket/the-name/123.tar.gz' ]
|
56
56
|
}
|
57
57
|
} )
|
58
58
|
@reader.get_bucket(:region => 'us-west-1').should == 'the-bucket'
|
@@ -80,10 +80,24 @@ describe Heirloom do
|
|
80
80
|
with("select * from `heirloom_tim` where itemName() = '123'").
|
81
81
|
and_return( { '123' =>
|
82
82
|
{ 'us-west-1-s3-url' =>
|
83
|
-
['s3://the-url/the-bucket/
|
83
|
+
['s3://the-url/the-bucket/123.tar.gz']
|
84
84
|
}
|
85
85
|
} )
|
86
|
-
@reader.get_key(:region => 'us-west-1').should == 'the-bucket/
|
86
|
+
@reader.get_key(:region => 'us-west-1').should == 'the-bucket/123.tar.gz'
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should return the encrypted key name" do
|
90
|
+
@sdb_mock.should_receive(:select).
|
91
|
+
with("select * from `heirloom_tim` where itemName() = '123'").
|
92
|
+
and_return( { '123' => { 'encrypted' => [ 'true' ] } } )
|
93
|
+
@reader.key_name.should == '123.tar.gz.gpg'
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return the unencrypted key name" do
|
97
|
+
@sdb_mock.should_receive(:select).
|
98
|
+
with("select * from `heirloom_tim` where itemName() = '123'").
|
99
|
+
and_return( { '123' => { 'encrypted' => [ 'false' ] } } )
|
100
|
+
@reader.key_name.should == '123.tar.gz'
|
87
101
|
end
|
88
102
|
|
89
103
|
it "should return the regions the archive has been uploaded to" do
|
@@ -92,11 +106,11 @@ describe Heirloom do
|
|
92
106
|
with("select * from `heirloom_tim` where itemName() = '123'").
|
93
107
|
and_return( { '123' =>
|
94
108
|
{ 'us-west-1-s3-url' =>
|
95
|
-
['s3://the-url-us-west-1/the-bucket/
|
109
|
+
['s3://the-url-us-west-1/the-bucket/123.tar.gz'],
|
96
110
|
'build_by' =>
|
97
111
|
['user'],
|
98
112
|
'us-east-1-s3-url' =>
|
99
|
-
['s3://the-url-us-east-1/the-bucket/
|
113
|
+
['s3://the-url-us-east-1/the-bucket/123.tar.gz']
|
100
114
|
}
|
101
115
|
} )
|
102
116
|
@reader.regions.should == ['us-west-1', 'us-east-1']
|
@@ -3,38 +3,65 @@ require 'spec_helper'
|
|
3
3
|
describe Heirloom do
|
4
4
|
|
5
5
|
before do
|
6
|
-
@config_mock =
|
7
|
-
@
|
8
|
-
@config_mock.
|
6
|
+
@config_mock = mock 'config'
|
7
|
+
@logger_stub = stub 'logger', :info => true
|
8
|
+
@config_mock.stub :logger => @logger_stub
|
9
9
|
@uploader = Heirloom::Uploader.new :config => @config_mock,
|
10
10
|
:name => 'tim',
|
11
11
|
:id => '123'
|
12
|
+
@s3_mock = mock 's3'
|
12
13
|
end
|
13
14
|
|
14
15
|
it "should upload a new archive" do
|
15
|
-
s3_mock = mock 's3'
|
16
16
|
Heirloom::Uploader::S3.should_receive(:new).
|
17
17
|
with(:config => @config_mock,
|
18
|
-
:logger => @
|
18
|
+
:logger => @logger_stub,
|
19
19
|
:region => 'us-west-1').
|
20
|
-
and_return s3_mock
|
21
|
-
s3_mock.should_receive(:upload_file).
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
s3_mock.should_receive(:add_endpoint_attributes).
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
20
|
+
and_return @s3_mock
|
21
|
+
@s3_mock.should_receive(:upload_file).
|
22
|
+
with(:bucket => 'prefix-us-west-1',
|
23
|
+
:file => '/tmp/file',
|
24
|
+
:id => '123',
|
25
|
+
:key_folder => 'tim',
|
26
|
+
:key_name => "123.tar.gz",
|
27
|
+
:name => 'tim',
|
28
|
+
:public_readable => true)
|
29
|
+
@s3_mock.should_receive(:add_endpoint_attributes).
|
30
|
+
with(:bucket => 'prefix-us-west-1',
|
31
|
+
:id => '123',
|
32
|
+
:key_name => '123.tar.gz',
|
33
|
+
:name => 'tim')
|
34
34
|
@uploader.upload :file => '/tmp/file',
|
35
35
|
:bucket_prefix => 'prefix',
|
36
36
|
:regions => ['us-west-1'],
|
37
|
-
:public_readable => true
|
37
|
+
:public_readable => true,
|
38
|
+
:secret => nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should upload a new archive with .gpg if secret provided" do
|
42
|
+
Heirloom::Uploader::S3.should_receive(:new).
|
43
|
+
with(:config => @config_mock,
|
44
|
+
:logger => @logger_stub,
|
45
|
+
:region => 'us-west-1').
|
46
|
+
and_return @s3_mock
|
47
|
+
@s3_mock.should_receive(:upload_file).
|
48
|
+
with(:bucket => 'prefix-us-west-1',
|
49
|
+
:file => '/tmp/file',
|
50
|
+
:id => '123',
|
51
|
+
:key_folder => 'tim',
|
52
|
+
:key_name => "123.tar.gz.gpg",
|
53
|
+
:name => 'tim',
|
54
|
+
:public_readable => true)
|
55
|
+
@s3_mock.should_receive(:add_endpoint_attributes).
|
56
|
+
with(:bucket => 'prefix-us-west-1',
|
57
|
+
:id => '123',
|
58
|
+
:key_name => '123.tar.gz.gpg',
|
59
|
+
:name => 'tim')
|
60
|
+
@uploader.upload :file => '/tmp/file',
|
61
|
+
:bucket_prefix => 'prefix',
|
62
|
+
:regions => ['us-west-1'],
|
63
|
+
:public_readable => true,
|
64
|
+
:secret => 'secret12'
|
38
65
|
end
|
39
66
|
|
40
67
|
end
|
data/spec/cipher/data_spec.rb
CHANGED
@@ -2,54 +2,71 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Heirloom do
|
4
4
|
before do
|
5
|
+
@encrypted_file_mock = mock 'encrypted mock'
|
6
|
+
@decrypted_file_mock = mock 'decrypted mock'
|
7
|
+
@encrypted_file_mock.stub :path => '/tmp/enc'
|
8
|
+
@decrypted_file_mock.stub :path => '/tmp/dec',
|
9
|
+
:read => 'plaintext'
|
5
10
|
@logger_mock = mock 'logger', :info => true
|
6
|
-
@logger_mock.stub :info
|
11
|
+
@logger_mock.stub :info => true,
|
12
|
+
:debug => true
|
7
13
|
@config_mock = mock 'config'
|
8
14
|
@config_mock.stub :logger => @logger_mock
|
9
15
|
@data = Heirloom::Cipher::Data.new :config => @config_mock
|
10
16
|
end
|
11
17
|
|
12
|
-
context "
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
context "when succesful" do
|
19
|
+
context "with secret given" do
|
20
|
+
it "should decrypt the given data" do
|
21
|
+
@data.should_receive(:which).with('gpg').and_return true
|
22
|
+
Tempfile.should_receive(:new).with('archive.tar.gz.gpg').
|
23
|
+
and_return @encrypted_file_mock
|
24
|
+
Tempfile.should_receive(:new).with('archive.tar.gz').
|
25
|
+
and_return @decrypted_file_mock
|
26
|
+
::File.should_receive(:open).
|
27
|
+
with(@encrypted_file_mock,'w')
|
28
|
+
$?.stub :success? => true
|
18
29
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@aes_mock.stub :final => 'final'
|
25
|
-
@data.decrypt_data(:data => 'firstsixteencharcrypteddata',
|
26
|
-
:secret => 'mysecret').
|
27
|
-
should == 'cleartextfinal'
|
30
|
+
command = 'gpg --batch --yes --cipher-algo AES256 --passphrase password --output /tmp/dec /tmp/enc 2>&1'
|
31
|
+
@data.should_receive(:`).with(command).and_return true
|
32
|
+
@data.decrypt_data(:data => 'crypted',
|
33
|
+
:secret => 'password').should == 'plaintext'
|
34
|
+
end
|
28
35
|
end
|
29
36
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@aes_mock.should_receive(:iv=).with 'firstsixteenchar'
|
36
|
-
@aes_mock.should_receive(:update).with('crypteddata').and_return 'crap'
|
37
|
-
@aes_mock.should_receive(:final).and_raise OpenSSL::Cipher::CipherError
|
38
|
-
@data.decrypt_data(:data => 'firstsixteencharcrypteddata',
|
39
|
-
:secret => 'badsecret').
|
40
|
-
should be_false
|
37
|
+
context "no secret given" do
|
38
|
+
it "should return the data if no secret given" do
|
39
|
+
@data.decrypt_data(:data => 'plaintext',
|
40
|
+
:secret => nil).should == 'plaintext'
|
41
|
+
end
|
41
42
|
end
|
42
|
-
|
43
43
|
end
|
44
44
|
|
45
|
-
context "
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
context "when unsuccesful" do
|
46
|
+
context "with secret given" do
|
47
|
+
it "should return false if gpg not in path" do
|
48
|
+
@logger_mock.should_receive(:error)
|
49
|
+
@data.should_receive(:which).with('gpg').and_return false
|
50
|
+
@data.decrypt_data(:data => 'crypted',
|
51
|
+
:secret => 'password').should be_false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return false if decrypted fails" do
|
55
|
+
@logger_mock.should_receive(:error)
|
56
|
+
@data.should_receive(:which).with('gpg').and_return true
|
57
|
+
Tempfile.should_receive(:new).with('archive.tar.gz.gpg').
|
58
|
+
and_return @encrypted_file_mock
|
59
|
+
Tempfile.should_receive(:new).with('archive.tar.gz').
|
60
|
+
and_return @decrypted_file_mock
|
61
|
+
::File.should_receive(:open).
|
62
|
+
with(@encrypted_file_mock,'w')
|
63
|
+
$?.stub :success? => false
|
49
64
|
|
50
|
-
|
51
|
-
|
52
|
-
|
65
|
+
command = 'gpg --batch --yes --cipher-algo AES256 --passphrase password --output /tmp/dec /tmp/enc 2>&1'
|
66
|
+
@data.should_receive(:`).with(command).and_return true
|
67
|
+
@data.decrypt_data(:data => 'crypted',
|
68
|
+
:secret => 'password').should be_false
|
69
|
+
end
|
53
70
|
end
|
54
71
|
end
|
55
72
|
|