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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
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
@@ -0,0 +1,5 @@
1
+ module Bosh
2
+ module Core
3
+ VERSION = '1.5.0.pre.1113'
4
+ end
5
+ end
data/lib/bosh/core.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Bosh
2
+ module Core
3
+ end
4
+ end
@@ -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,6 @@
1
+ require 'spec_helper'
2
+ require 'bosh/core'
3
+
4
+ describe Bosh::Core do
5
+ it { should be_a(Module) }
6
+ 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
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+
3
+ Dir.glob(File.expand_path('support/**/*.rb', File.dirname(__FILE__))).each do |support|
4
+ require support
5
+ end
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