bosh-core 1.5.0.pre.1113
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/bosh-core.gemspec +27 -0
- data/lib/bosh/core/encryption_handler.rb +149 -0
- data/lib/bosh/core/shell.rb +54 -0
- data/lib/bosh/core/version.rb +5 -0
- data/lib/bosh/core.rb +4 -0
- data/spec/bosh/core/shell_spec.rb +66 -0
- data/spec/bosh/core/version_spec.rb +12 -0
- data/spec/bosh/core_spec.rb +6 -0
- data/spec/bosh/encryption_handler_spec.rb +135 -0
- data/spec/spec_helper.rb +5 -0
- metadata +142 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/bosh-core.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
version = File.read(File.expand_path('../../BOSH_VERSION', __FILE__)).strip
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'bosh-core'
|
6
|
+
spec.version = version
|
7
|
+
spec.authors = 'Pivotal'
|
8
|
+
spec.email = 'support@cloudfoundry.com'
|
9
|
+
spec.description = 'Bosh::Core provides things BOSH needs to exist'
|
10
|
+
spec.summary = 'Bosh::Core provides things BOSH needs to exist'
|
11
|
+
spec.homepage = 'https://github.com/cloudfoundry/bosh'
|
12
|
+
spec.license = 'Apache 2.0'
|
13
|
+
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 1.9.3')
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = %w[lib]
|
20
|
+
|
21
|
+
spec.add_dependency 'gibberish', '~>1.2.0'
|
22
|
+
spec.add_dependency 'yajl-ruby', '~>1.1.0'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'rspec-fire'
|
27
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'gibberish'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'yajl'
|
7
|
+
|
8
|
+
module Bosh::Core
|
9
|
+
# Utility class for decrypting/encrypting Director/Agent message exchanges
|
10
|
+
class EncryptionHandler
|
11
|
+
class CryptError < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class SessionError < CryptError
|
15
|
+
end
|
16
|
+
|
17
|
+
class SequenceNumberError < CryptError
|
18
|
+
end
|
19
|
+
|
20
|
+
class SignatureError < CryptError
|
21
|
+
end
|
22
|
+
|
23
|
+
class DecryptionError < CryptError
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :session_id
|
27
|
+
|
28
|
+
def initialize(id, credentials)
|
29
|
+
@id = id
|
30
|
+
crypt_key = credentials['crypt_key']
|
31
|
+
@cipher = Gibberish::AES.new(crypt_key)
|
32
|
+
@sign_key = credentials['sign_key']
|
33
|
+
@session_id = nil
|
34
|
+
@session_sequence_number = 0
|
35
|
+
|
36
|
+
initiate_sequence_number
|
37
|
+
end
|
38
|
+
|
39
|
+
def initiate_sequence_number
|
40
|
+
@sequence_number = Time.now.to_i + SecureRandom.random_number(1 << 32)
|
41
|
+
end
|
42
|
+
|
43
|
+
def encrypt(data)
|
44
|
+
raise ArgumentError unless data.is_a?(Hash)
|
45
|
+
|
46
|
+
start_session unless @session_id
|
47
|
+
|
48
|
+
encapsulated_data = data.dup
|
49
|
+
# Add encrytpion related metadata before signing and encrypting
|
50
|
+
@sequence_number += 1
|
51
|
+
encapsulated_data['sequence_number'] = @sequence_number
|
52
|
+
encapsulated_data['client_id'] = @id
|
53
|
+
encapsulated_data['session_id'] = @session_id
|
54
|
+
|
55
|
+
signed_data = sign(encapsulated_data)
|
56
|
+
encrypted_data = @cipher.encrypt(encode(signed_data))
|
57
|
+
encrypted_data
|
58
|
+
end
|
59
|
+
|
60
|
+
def sign(encapsulated_data)
|
61
|
+
data_json = encode(encapsulated_data)
|
62
|
+
hmac = signature(data_json)
|
63
|
+
signed_data = { 'hmac' => hmac, 'json_data' => data_json }
|
64
|
+
signed_data
|
65
|
+
end
|
66
|
+
|
67
|
+
def decrypt(encrypted_data)
|
68
|
+
begin
|
69
|
+
decrypted_data = @cipher.decrypt(encrypted_data)
|
70
|
+
# rubocop:disable RescueException
|
71
|
+
rescue Exception => e
|
72
|
+
# rubocop:enable RescueException
|
73
|
+
|
74
|
+
raise DecryptionError, e.inspect
|
75
|
+
end
|
76
|
+
|
77
|
+
data = Yajl::Parser.new.parse(decrypted_data)
|
78
|
+
|
79
|
+
verify_signature(data)
|
80
|
+
decoded_data = decode(data['json_data'])
|
81
|
+
verify_session(decoded_data)
|
82
|
+
decoded_data
|
83
|
+
end
|
84
|
+
|
85
|
+
def start_session
|
86
|
+
@session_id = SecureRandom.uuid
|
87
|
+
end
|
88
|
+
|
89
|
+
def verify_signature(data)
|
90
|
+
hmac = data['hmac']
|
91
|
+
json_data = data['json_data']
|
92
|
+
|
93
|
+
json_hmac = signature(json_data)
|
94
|
+
unless constant_time_comparison(hmac, json_hmac)
|
95
|
+
raise SignatureError, "Expected hmac (#{hmac}), got (#{json_hmac})"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# constant time comparison snagged from activesupport
|
100
|
+
def constant_time_comparison(a, b)
|
101
|
+
return false unless a.bytesize == b.bytesize
|
102
|
+
l = a.unpack "C#{a.bytesize}"
|
103
|
+
res = 0
|
104
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
105
|
+
res == 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def verify_session(decrypted_data)
|
109
|
+
# If you are the receiver of a session - use session_id from payload
|
110
|
+
if @session_id.nil?
|
111
|
+
if !decrypted_data['session_id'].nil?
|
112
|
+
@session_id = decrypted_data['session_id']
|
113
|
+
else
|
114
|
+
raise SessionError, 'no session_id'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
unless decrypted_data['session_id'] == @session_id
|
119
|
+
raise SessionError, 'session_id mismatch'
|
120
|
+
end
|
121
|
+
|
122
|
+
sender_sequence_number = decrypted_data['sequence_number'].to_i
|
123
|
+
if sender_sequence_number > @session_sequence_number
|
124
|
+
@session_sequence_number = sender_sequence_number
|
125
|
+
else
|
126
|
+
raise SequenceNumberError, 'invalid sequence number'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def signature(sign_data)
|
131
|
+
Gibberish.HMAC(@sign_key, sign_data, { digest: :sha256 })
|
132
|
+
end
|
133
|
+
|
134
|
+
def encode(data)
|
135
|
+
Yajl::Encoder.encode(data)
|
136
|
+
end
|
137
|
+
|
138
|
+
def decode(json)
|
139
|
+
Yajl::Parser.new.parse(json)
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.generate_credentials
|
143
|
+
%w(crypt_key sign_key).inject({}) do |credentials, key|
|
144
|
+
credentials[key] = SecureRandom.base64(48)
|
145
|
+
credentials
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Bosh::Core
|
2
|
+
class Shell
|
3
|
+
def initialize(stdout = $stdout)
|
4
|
+
@stdout = stdout
|
5
|
+
end
|
6
|
+
|
7
|
+
def run(command, options = {})
|
8
|
+
output_lines = run_command(command, options)
|
9
|
+
output_lines = tail(output_lines, options)
|
10
|
+
|
11
|
+
command_output = output_lines.join("\n")
|
12
|
+
report(command, command_output, options)
|
13
|
+
command_output
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :stdout
|
19
|
+
|
20
|
+
def run_command(command, options)
|
21
|
+
stdout.puts command if options[:output_command]
|
22
|
+
lines = []
|
23
|
+
|
24
|
+
IO.popen(command).each do |line|
|
25
|
+
stdout.puts line.chomp
|
26
|
+
lines << line.chomp
|
27
|
+
end.close
|
28
|
+
|
29
|
+
lines
|
30
|
+
end
|
31
|
+
|
32
|
+
def tail(lines, options)
|
33
|
+
line_number = options[:last_number]
|
34
|
+
line_number ? lines.last(line_number) : lines
|
35
|
+
end
|
36
|
+
|
37
|
+
def report(cmd, command_output, options)
|
38
|
+
return if command_exited_successfully?
|
39
|
+
|
40
|
+
err_msg = "Failed: '#{cmd}' from #{pwd}, with exit status #{$?.to_i}\n\n #{command_output}"
|
41
|
+
options[:ignore_failures] ? stdout.puts("#{err_msg}, continuing anyway") : raise(err_msg)
|
42
|
+
end
|
43
|
+
|
44
|
+
def command_exited_successfully?
|
45
|
+
$?.success?
|
46
|
+
end
|
47
|
+
|
48
|
+
def pwd
|
49
|
+
Dir.pwd
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
'a deleted directory'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/bosh/core.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bosh/core/shell'
|
3
|
+
|
4
|
+
module Bosh::Core
|
5
|
+
describe Shell do
|
6
|
+
let(:stdout) { StringIO.new }
|
7
|
+
|
8
|
+
subject do
|
9
|
+
Shell.new(stdout)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#run' do
|
13
|
+
it 'shells out, prints and returns the output of the command' do
|
14
|
+
expect(subject.run('echo hello; echo world')).to eq("hello\nworld")
|
15
|
+
expect(stdout.string).to eq("hello\nworld\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when "output_command" is specified' do
|
19
|
+
it 'outputs the command' do
|
20
|
+
cmd = 'echo 1;echo 2;echo 3;echo 4;echo 5'
|
21
|
+
subject.run(cmd, output_command: true)
|
22
|
+
|
23
|
+
expect(stdout.string).to include('echo 1;echo 2;echo 3;echo 4;echo 5')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when "last_number" is specified' do
|
28
|
+
it 'tails "last_number" lines of output' do
|
29
|
+
cmd = 'echo 1;echo 2;echo 3;echo 4;echo 5'
|
30
|
+
expect(subject.run(cmd, last_number: 3)).to eq("3\n4\n5")
|
31
|
+
expect(stdout.string).to eq("1\n2\n3\n4\n5\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'outputs the entire output if more lines are requested than generated' do
|
35
|
+
cmd = 'echo 1;echo 2;echo 3;echo 4;echo 5'
|
36
|
+
expect(subject.run(cmd, last_number: 6)).to eq("1\n2\n3\n4\n5")
|
37
|
+
expect(stdout.string).to eq("1\n2\n3\n4\n5\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when the command fails' do
|
42
|
+
it 'raises an error' do
|
43
|
+
expect {
|
44
|
+
subject.run('false')
|
45
|
+
}.to raise_error /Failed: 'false' from /
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'because the working directory has gone missing' do
|
49
|
+
it 'fails gracefully with a slightly helpful error message' do
|
50
|
+
Dir.stub(:pwd).and_raise(Errno::ENOENT, 'No such file or directory - getcwd')
|
51
|
+
expect {
|
52
|
+
subject.run('false')
|
53
|
+
}.to raise_error /from a deleted directory/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'and ignoring failures' do
|
58
|
+
it 'raises an error' do
|
59
|
+
subject.run('false', ignore_failures: true)
|
60
|
+
expect(stdout.string).to match(/continuing anyway/)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bosh/core/version'
|
3
|
+
|
4
|
+
module Bosh::Core
|
5
|
+
describe VERSION do
|
6
|
+
let(:bosh_version_file) do
|
7
|
+
File.expand_path('../../../../BOSH_VERSION', File.dirname(__FILE__))
|
8
|
+
end
|
9
|
+
|
10
|
+
it { should eq(File.read(bosh_version_file).strip) }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bosh/core/encryption_handler'
|
3
|
+
|
4
|
+
module Bosh::Core
|
5
|
+
describe EncryptionHandler do
|
6
|
+
before(:each) do
|
7
|
+
@credentials = EncryptionHandler.generate_credentials
|
8
|
+
@cipher = Gibberish::AES.new(@credentials['crypt_key'])
|
9
|
+
@sign_key = @credentials['sign_key']
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should encrypt data' do
|
13
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
14
|
+
encrypted_data = handler.encrypt('hubba' => 'bubba')
|
15
|
+
|
16
|
+
# double decode is not an error - data need to be serialized before it is
|
17
|
+
# signed and then serialized again to be encrypted
|
18
|
+
decrypted_data = handler.decode(handler.decode(@cipher.decrypt(encrypted_data))['json_data'])
|
19
|
+
decrypted_data['hubba'].should eq 'bubba'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be signed' do
|
23
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
24
|
+
encrypted_data = handler.encrypt('hubba' => 'bubba')
|
25
|
+
|
26
|
+
decrypted_data = handler.decode(@cipher.decrypt(encrypted_data))
|
27
|
+
signature = decrypted_data['hmac']
|
28
|
+
json_data = decrypted_data['json_data']
|
29
|
+
|
30
|
+
signature.should eq Gibberish.HMAC(@sign_key, json_data, { digest: :sha256 })
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should decrypt' do
|
34
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
35
|
+
|
36
|
+
encrypted_data = handler.encrypt('hubba' => 'bubba')
|
37
|
+
handler.decrypt(encrypted_data)['hubba'].should eq 'bubba'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should verify signature' do
|
41
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
42
|
+
|
43
|
+
encrypted_data = handler.encrypt('hubba' => 'bubba')
|
44
|
+
|
45
|
+
# build bad data
|
46
|
+
manipulated_data = handler.decode(@cipher.decrypt(encrypted_data))
|
47
|
+
manipulated_data['hmac'] = 'foo'
|
48
|
+
encrypted_manipulated_data = @cipher.encrypt(handler.encode(manipulated_data))
|
49
|
+
|
50
|
+
lambda {
|
51
|
+
handler.decrypt(encrypted_manipulated_data)
|
52
|
+
}.should raise_error(EncryptionHandler::SignatureError,
|
53
|
+
/Expected hmac \(foo\)/)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should verify session' do
|
57
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
58
|
+
|
59
|
+
encrypted_data = handler.encrypt('knife' => 'fork')
|
60
|
+
|
61
|
+
# build bad data
|
62
|
+
decrypted_data = handler.decode(@cipher.decrypt(encrypted_data))
|
63
|
+
|
64
|
+
bad_data = handler.decode(decrypted_data['json_data'])
|
65
|
+
bad_data['session_id'] = 'bad_session_data'
|
66
|
+
|
67
|
+
bad_json_data = handler.encode(bad_data)
|
68
|
+
|
69
|
+
manipulated_data = {
|
70
|
+
'hmac' => handler.signature(bad_json_data),
|
71
|
+
'json_data' => bad_json_data
|
72
|
+
}
|
73
|
+
|
74
|
+
encrypted_manipulated_data = @cipher.encrypt(handler.encode(manipulated_data))
|
75
|
+
|
76
|
+
lambda {
|
77
|
+
handler.decrypt(encrypted_manipulated_data)
|
78
|
+
}.should raise_error(EncryptionHandler::SessionError)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should decrypt for multiple messages' do
|
82
|
+
h1 = EncryptionHandler.new('client_id', @credentials)
|
83
|
+
h2 = EncryptionHandler.new('client_id', @credentials)
|
84
|
+
encrypted_data1 = h1.encrypt('hubba' => 'bubba')
|
85
|
+
encrypted_data2 = h1.encrypt('bubba' => 'hubba')
|
86
|
+
|
87
|
+
h2.decrypt(encrypted_data1)['hubba'].should eq 'bubba'
|
88
|
+
h2.decrypt(encrypted_data2)['bubba'].should eq 'hubba'
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should exchange messages' do
|
92
|
+
h1 = EncryptionHandler.new('client_id', @credentials)
|
93
|
+
h2 = EncryptionHandler.new('client_id', @credentials)
|
94
|
+
|
95
|
+
encrypted_data1 = h1.encrypt('hubba' => 'bubba')
|
96
|
+
h2.decrypt(encrypted_data1)['hubba'].should eq 'bubba'
|
97
|
+
|
98
|
+
encrypted_data2 = h2.encrypt('kermit' => 'frog')
|
99
|
+
h1.decrypt(encrypted_data2)['kermit'].should eq 'frog'
|
100
|
+
|
101
|
+
encrypted_data3 = h1.encrypt('frank' => 'zappa')
|
102
|
+
h2.decrypt(encrypted_data3)['frank'].should eq 'zappa'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should fail when sequence number is out of order' do
|
106
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
107
|
+
encrypted_data1 = handler.encrypt('foo' => 'bar')
|
108
|
+
encrypted_data2 = handler.encrypt('baz' => 'bus')
|
109
|
+
|
110
|
+
handler.decrypt(encrypted_data2)
|
111
|
+
|
112
|
+
lambda {
|
113
|
+
handler.decrypt(encrypted_data1)
|
114
|
+
}.should raise_error(EncryptionHandler::SequenceNumberError)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should handle garbage encrypt args' do
|
118
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
119
|
+
lambda {
|
120
|
+
handler.encrypt('bleh')
|
121
|
+
}.should raise_error(ArgumentError)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should handle garbage decrypt args' do
|
125
|
+
handler = EncryptionHandler.new('client_id', @credentials)
|
126
|
+
lambda {
|
127
|
+
handler.decrypt('f')
|
128
|
+
}.should raise_error(EncryptionHandler::DecryptionError, /TypeError/)
|
129
|
+
|
130
|
+
lambda {
|
131
|
+
handler.decrypt('fddddddddddddddddddddddddddddddddddddddddddddddddd')
|
132
|
+
}.should raise_error(EncryptionHandler::DecryptionError, /CipherError/)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bosh-core
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.5.0.pre.1113
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pivotal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: gibberish
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: yajl-ruby
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.1.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.1.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec-fire
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Bosh::Core provides things BOSH needs to exist
|
95
|
+
email: support@cloudfoundry.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- .gitignore
|
101
|
+
- .rspec
|
102
|
+
- bosh-core.gemspec
|
103
|
+
- lib/bosh/core.rb
|
104
|
+
- lib/bosh/core/encryption_handler.rb
|
105
|
+
- lib/bosh/core/shell.rb
|
106
|
+
- lib/bosh/core/version.rb
|
107
|
+
- spec/bosh/core/shell_spec.rb
|
108
|
+
- spec/bosh/core/version_spec.rb
|
109
|
+
- spec/bosh/core_spec.rb
|
110
|
+
- spec/bosh/encryption_handler_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
homepage: https://github.com/cloudfoundry/bosh
|
113
|
+
licenses:
|
114
|
+
- Apache 2.0
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.9.3
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>'
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 1.3.1
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.23
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: Bosh::Core provides things BOSH needs to exist
|
137
|
+
test_files:
|
138
|
+
- spec/bosh/core/shell_spec.rb
|
139
|
+
- spec/bosh/core/version_spec.rb
|
140
|
+
- spec/bosh/core_spec.rb
|
141
|
+
- spec/bosh/encryption_handler_spec.rb
|
142
|
+
- spec/spec_helper.rb
|