cred_hubble 0.0.1.pre → 0.1.0.pre

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +7 -1
  4. data/.travis.yml +3 -1
  5. data/README.md +353 -13
  6. data/cred_hubble.gemspec +3 -0
  7. data/lib/cred_hubble.rb +3 -2
  8. data/lib/cred_hubble/client.rb +119 -13
  9. data/lib/cred_hubble/http/client.rb +39 -4
  10. data/lib/cred_hubble/resources/certificate_credential.rb +25 -0
  11. data/lib/cred_hubble/resources/credential.rb +32 -0
  12. data/lib/cred_hubble/resources/credential_collection.rb +21 -0
  13. data/lib/cred_hubble/resources/credential_factory.rb +41 -0
  14. data/lib/cred_hubble/resources/immutable_resource.rb +2 -2
  15. data/lib/cred_hubble/resources/json_credential.rb +13 -0
  16. data/lib/cred_hubble/resources/password_credential.rb +13 -0
  17. data/lib/cred_hubble/resources/permission.rb +10 -0
  18. data/lib/cred_hubble/resources/permission_collection.rb +21 -0
  19. data/lib/cred_hubble/resources/resource.rb +10 -0
  20. data/lib/cred_hubble/resources/resources.rb +15 -0
  21. data/lib/cred_hubble/resources/{base_resource.rb → rest_resource.rb} +6 -2
  22. data/lib/cred_hubble/resources/rsa_credential.rb +24 -0
  23. data/lib/cred_hubble/resources/ssh_credential.rb +39 -0
  24. data/lib/cred_hubble/resources/user_credential.rb +39 -0
  25. data/lib/cred_hubble/resources/value_credential.rb +13 -0
  26. data/lib/cred_hubble/version.rb +1 -1
  27. data/spec/cred_hubble/client_spec.rb +487 -3
  28. data/spec/cred_hubble/http/client_spec.rb +347 -53
  29. data/spec/cred_hubble/resources/certificate_credential_spec.rb +49 -0
  30. data/spec/cred_hubble/resources/credential_collection_spec.rb +59 -0
  31. data/spec/cred_hubble/resources/credential_factory_spec.rb +154 -0
  32. data/spec/cred_hubble/resources/credential_spec.rb +10 -0
  33. data/spec/cred_hubble/resources/json_credential_spec.rb +52 -0
  34. data/spec/cred_hubble/resources/password_credential_spec.rb +41 -0
  35. data/spec/cred_hubble/resources/permission_collection_spec.rb +87 -0
  36. data/spec/cred_hubble/resources/permission_spec.rb +36 -0
  37. data/spec/cred_hubble/resources/rsa_credential_spec.rb +46 -0
  38. data/spec/cred_hubble/resources/ssh_credential_spec.rb +73 -0
  39. data/spec/cred_hubble/resources/user_credential_spec.rb +72 -0
  40. data/spec/cred_hubble/resources/value_credential_spec.rb +42 -0
  41. data/spec/support/shared_examples/resource_examples.rb +49 -0
  42. metadata +57 -5
@@ -1,5 +1,67 @@
1
1
  require 'spec_helper'
2
2
 
3
+ RSpec.shared_examples 'a request with error handling' do
4
+ context 'when the response status is 400' do
5
+ let(:response_body) { 'Bad request' }
6
+ let(:status) { 400 }
7
+
8
+ it 'raises a CredHubble::Http::BadRequestError' do
9
+ expect { subject }
10
+ .to raise_error(CredHubble::Http::BadRequestError, "status: #{status}, body: #{response_body}")
11
+ end
12
+ end
13
+
14
+ context 'when the response status is 401' do
15
+ let(:response_body) { 'Unauthorized' }
16
+ let(:status) { 401 }
17
+
18
+ it 'raises a CredHubble::Http::UnauthorizedError' do
19
+ expect { subject }
20
+ .to raise_error(CredHubble::Http::UnauthorizedError, "status: #{status}, body: #{response_body}")
21
+ end
22
+ end
23
+
24
+ context 'when the response status is 403' do
25
+ let(:response_body) { 'Forbidden' }
26
+ let(:status) { 403 }
27
+
28
+ it 'raises a CredHubble::Http::ForbiddenError' do
29
+ expect { subject }
30
+ .to raise_error(CredHubble::Http::ForbiddenError, "status: #{status}, body: #{response_body}")
31
+ end
32
+ end
33
+
34
+ context 'when the response status is 404' do
35
+ let(:response_body) { 'Not found' }
36
+ let(:status) { 404 }
37
+
38
+ it 'raises a CredHubble::Http::NotFoundError' do
39
+ expect { subject }
40
+ .to raise_error(CredHubble::Http::NotFoundError, "status: #{status}, body: #{response_body}")
41
+ end
42
+ end
43
+
44
+ context 'when the response status is 500' do
45
+ let(:response_body) { 'Internal Server Error' }
46
+ let(:status) { 500 }
47
+
48
+ it 'raises a CredHubble::Http::InternalServerError' do
49
+ expect { subject }
50
+ .to raise_error(CredHubble::Http::InternalServerError, "status: #{status}, body: #{response_body}")
51
+ end
52
+ end
53
+
54
+ context 'when the response status is not otherwise handled' do
55
+ let(:response_body) { "I'm a teapot" }
56
+ let(:status) { 418 }
57
+
58
+ it 'raises a CredHubble::Http::InternalServerError' do
59
+ expect { subject }
60
+ .to raise_error(CredHubble::Http::UnknownError, "status: #{status}, body: #{response_body}")
61
+ end
62
+ end
63
+ end
64
+
3
65
  RSpec.describe CredHubble::Http::Client do
4
66
  let(:url) { 'https://example.com:8845' }
5
67
  subject { CredHubble::Http::Client.new(url) }
@@ -30,98 +92,330 @@ RSpec.describe CredHubble::Http::Client do
30
92
  expect(response.status).to eq(status)
31
93
  end
32
94
 
33
- context 'when a Faraday::SSLError occurrs' do
34
- let(:error) { Faraday::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed') }
35
- let(:fake_connection) { instance_double(Faraday::Connection) }
95
+ describe 'error handling' do
96
+ subject { CredHubble::Http::Client.new(url).get(path) }
97
+
98
+ context 'when a Faraday::SSLError occurs' do
99
+ let(:error) { Faraday::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed') }
100
+ let(:fake_connection) { instance_double(Faraday::Connection) }
101
+
102
+ before do
103
+ allow_any_instance_of(CredHubble::Http::Client).to receive(:connection).and_return(fake_connection)
104
+ allow(fake_connection).to receive(:get).and_raise(error)
105
+ end
36
106
 
37
- before do
38
- allow(subject).to receive(:connection).and_return(fake_connection)
39
- allow(fake_connection).to receive(:get).and_raise(error)
107
+ it 'raises a CredHubble::Exceptions::SSLError' do
108
+ expect { subject }.to raise_error(CredHubble::Http::SSLError)
109
+ end
40
110
  end
41
111
 
42
- it 'raises a CredHubble::Exceptions::SSLError' do
43
- expect { subject.get(path) }.to raise_error(CredHubble::Http::SSLError)
112
+ it_behaves_like 'a request with error handling'
113
+ end
114
+
115
+ describe 'request headers' do
116
+ context 'when client is initialized with an auth_token_header' do
117
+ let(:token) { 'meesa-jar-jar-binks-token' }
118
+ subject { CredHubble::Http::Client.new(url, auth_header_token: token) }
119
+
120
+ it 'includes an Authorization header with the provided bearer token' do
121
+ subject.get(path)
122
+ assert_requested(
123
+ :get,
124
+ "#{url}#{path}",
125
+ headers: { 'Content-Type' => 'application/json', 'Authorization' => "bearer #{token}" }
126
+ )
127
+ end
44
128
  end
129
+
130
+ context 'when client is not initialized with an auth_token_header' do
131
+ subject { CredHubble::Http::Client.new(url) }
132
+
133
+ it 'does not include an authorization header' do
134
+ subject.get(path)
135
+ assert_requested(:get, "#{url}#{path}", headers: { 'Content-Type' => 'application/json' })
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#put' do
142
+ let(:path) { '/api/v1/data' }
143
+ let(:status) { 200 }
144
+ let(:request_body) do
145
+ '{
146
+ "name": "/la-la-land",
147
+ "type": "value",
148
+ "value": "another day of sun: purple shirt parkour"
149
+ }'
150
+ end
151
+ let(:response_body) do
152
+ '{
153
+ "id": "62630719-2413-4332-9e39-a8acbd73d3b7",
154
+ "name": "/la-la-land",
155
+ "type": "value",
156
+ "value": "another day of sun: purple shirt parkour",
157
+ "version_created_at": "2017-01-01T04:07:18Z"
158
+ }'
45
159
  end
46
160
 
47
- context 'when the response status is 400' do
48
- let(:response_body) { 'Bad request' }
49
- let(:status) { 400 }
161
+ before do
162
+ stub_request(:put, "#{url}#{path}").with(body: request_body).to_return(status: status, body: response_body)
163
+ end
50
164
 
51
- it 'raises a CredHubble::Http::BadRequestError' do
52
- expect { subject.get(path) }
53
- .to raise_error(CredHubble::Http::BadRequestError, "status: #{status}, body: #{response_body}")
165
+ it 'makes a PUT request with the given body to the requested url and path' do
166
+ response = subject.put(path, request_body)
167
+ expect(response).to be_a(Faraday::Response)
168
+ expect(response.body).to eq(response_body)
169
+ expect(response.status).to eq(status)
170
+ end
171
+
172
+ describe 'error handling' do
173
+ subject { CredHubble::Http::Client.new(url).put(path, request_body) }
174
+
175
+ context 'when a Faraday::SSLError occurs' do
176
+ let(:error) { Faraday::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed') }
177
+ let(:fake_connection) { instance_double(Faraday::Connection) }
178
+
179
+ before do
180
+ allow_any_instance_of(CredHubble::Http::Client).to receive(:connection).and_return(fake_connection)
181
+ allow(fake_connection).to receive(:put).and_raise(error)
182
+ end
183
+
184
+ it 'raises a CredHubble::Exceptions::SSLError' do
185
+ expect { subject }.to raise_error(CredHubble::Http::SSLError)
186
+ end
54
187
  end
188
+
189
+ it_behaves_like 'a request with error handling'
55
190
  end
56
191
 
57
- context 'when the response status is 401' do
58
- let(:response_body) { 'Unauthorized' }
59
- let(:status) { 401 }
192
+ describe 'request headers' do
193
+ context 'when client is initialized with an auth_token_header' do
194
+ let(:token) { 'meesa-jar-jar-binks-token' }
195
+ subject { CredHubble::Http::Client.new(url, auth_header_token: token) }
60
196
 
61
- it 'raises a CredHubble::Http::UnauthorizedError' do
62
- expect { subject.get(path) }
63
- .to raise_error(CredHubble::Http::UnauthorizedError, "status: #{status}, body: #{response_body}")
197
+ it 'includes an Authorization header with the provided bearer token' do
198
+ subject.put(path, request_body)
199
+ assert_requested(
200
+ :put,
201
+ "#{url}#{path}",
202
+ body: request_body,
203
+ headers: { 'Content-Type' => 'application/json', 'Authorization' => "bearer #{token}" }
204
+ )
205
+ end
64
206
  end
207
+
208
+ context 'when client is not initialized with an auth_token_header' do
209
+ subject { CredHubble::Http::Client.new(url) }
210
+
211
+ it 'does not include an authorization header' do
212
+ subject.put(path, request_body)
213
+ assert_requested(
214
+ :put,
215
+ "#{url}#{path}",
216
+ body: request_body,
217
+ headers: { 'Content-Type' => 'application/json' }
218
+ )
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ describe '#post' do
225
+ let(:path) { '/api/v1/data' }
226
+ let(:status) { 200 }
227
+ let(:request_body) do
228
+ '{
229
+ "name": "/ron-dunn",
230
+ "type": "value",
231
+ "value": "is that your name or are you telling me you\'re finished talking?"
232
+ }'
233
+ end
234
+ let(:response_body) do
235
+ '{
236
+ "id": "62630719-2413-4332-9e39-a8acbd73d3b7",
237
+ "name": "/ron-dunn",
238
+ "type": "value",
239
+ "value": "is that your name or are you telling me you\'re finished talking?",
240
+ "version_created_at": "2017-01-01T04:07:18Z"
241
+ }'
242
+ end
243
+
244
+ before do
245
+ stub_request(:post, "#{url}#{path}").with(body: request_body).to_return(status: status, body: response_body)
65
246
  end
66
247
 
67
- context 'when the response status is 403' do
68
- let(:response_body) { 'Forbidden' }
69
- let(:status) { 403 }
248
+ it 'makes a POST request with the given body to the requested url and path' do
249
+ response = subject.post(path, request_body)
250
+ expect(response).to be_a(Faraday::Response)
251
+ expect(response.body).to eq(response_body)
252
+ expect(response.status).to eq(status)
253
+ end
254
+
255
+ describe 'error handling' do
256
+ subject { CredHubble::Http::Client.new(url).post(path, request_body) }
257
+
258
+ context 'when a Faraday::SSLError occurs' do
259
+ let(:error) { Faraday::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed') }
260
+ let(:fake_connection) { instance_double(Faraday::Connection) }
261
+
262
+ before do
263
+ allow_any_instance_of(CredHubble::Http::Client).to receive(:connection).and_return(fake_connection)
264
+ allow(fake_connection).to receive(:post).and_raise(error)
265
+ end
70
266
 
71
- it 'raises a CredHubble::Http::ForbiddenError' do
72
- expect { subject.get(path) }
73
- .to raise_error(CredHubble::Http::ForbiddenError, "status: #{status}, body: #{response_body}")
267
+ it 'raises a CredHubble::Exceptions::SSLError' do
268
+ expect { subject }.to raise_error(CredHubble::Http::SSLError)
269
+ end
74
270
  end
271
+
272
+ it_behaves_like 'a request with error handling'
75
273
  end
76
274
 
77
- context 'when the response status is 404' do
78
- let(:response_body) { 'Not found' }
79
- let(:status) { 404 }
275
+ describe 'request headers' do
276
+ context 'when client is initialized with an auth_token_header' do
277
+ let(:token) { 'meesa-jar-jar-binks-token' }
278
+ subject { CredHubble::Http::Client.new(url, auth_header_token: token) }
279
+
280
+ it 'includes an Authorization header with the provided bearer token' do
281
+ subject.post(path, request_body)
282
+ assert_requested(
283
+ :post,
284
+ "#{url}#{path}",
285
+ body: request_body,
286
+ headers: { 'Content-Type' => 'application/json', 'Authorization' => "bearer #{token}" }
287
+ )
288
+ end
289
+ end
290
+
291
+ context 'when client is not initialized with an auth_token_header' do
292
+ subject { CredHubble::Http::Client.new(url) }
80
293
 
81
- it 'raises a CredHubble::Http::NotFoundError' do
82
- expect { subject.get(path) }
83
- .to raise_error(CredHubble::Http::NotFoundError, "status: #{status}, body: #{response_body}")
294
+ it 'does not include an authorization header' do
295
+ subject.post(path, request_body)
296
+ assert_requested(
297
+ :post,
298
+ "#{url}#{path}",
299
+ body: request_body,
300
+ headers: { 'Content-Type' => 'application/json' }
301
+ )
302
+ end
84
303
  end
85
304
  end
305
+ end
306
+
307
+ describe '#delete' do
308
+ let(:path) { '/data' }
309
+ let(:status) { 204 }
310
+ let(:response_body) { '' }
311
+
312
+ before do
313
+ stub_request(:delete, "#{url}#{path}").to_return(status: status, body: response_body)
314
+ end
86
315
 
87
- context 'when the response status is 500' do
88
- let(:response_body) { 'Internal Server Error' }
89
- let(:status) { 500 }
316
+ it 'makes a GET request to the requested url and path' do
317
+ response = subject.delete(path)
318
+ expect(response).to be_a(Faraday::Response)
319
+ expect(response.body).to eq(response_body)
320
+ expect(response.status).to eq(status)
321
+ end
322
+
323
+ describe 'error handling' do
324
+ subject { CredHubble::Http::Client.new(url).delete(path) }
325
+
326
+ context 'when a Faraday::SSLError occurs' do
327
+ let(:error) { Faraday::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed') }
328
+ let(:fake_connection) { instance_double(Faraday::Connection) }
90
329
 
91
- it 'raises a CredHubble::Http::InternalServerError' do
92
- expect { subject.get(path) }
93
- .to raise_error(CredHubble::Http::InternalServerError, "status: #{status}, body: #{response_body}")
330
+ before do
331
+ allow_any_instance_of(CredHubble::Http::Client).to receive(:connection).and_return(fake_connection)
332
+ allow(fake_connection).to receive(:delete).and_raise(error)
333
+ end
334
+
335
+ it 'raises a CredHubble::Exceptions::SSLError' do
336
+ expect { subject }.to raise_error(CredHubble::Http::SSLError)
337
+ end
94
338
  end
339
+
340
+ it_behaves_like 'a request with error handling'
95
341
  end
96
342
 
97
- context 'when the response status is not otherwise handled' do
98
- let(:response_body) { "I'm a teapot" }
99
- let(:status) { 418 }
343
+ describe 'request headers' do
344
+ context 'when client is initialized with an auth_token_header' do
345
+ let(:token) { 'some-oauth2-bearer-token' }
346
+ subject { CredHubble::Http::Client.new(url, auth_header_token: token) }
347
+
348
+ it 'includes an Authorization header with the provided bearer token' do
349
+ subject.delete(path)
350
+ assert_requested(
351
+ :delete,
352
+ "#{url}#{path}",
353
+ headers: { 'Content-Type' => 'application/json', 'Authorization' => "bearer #{token}" }
354
+ )
355
+ end
356
+ end
357
+
358
+ context 'when client is not initialized with an auth_token_header' do
359
+ subject { CredHubble::Http::Client.new(url) }
100
360
 
101
- it 'raises a CredHubble::Http::InternalServerError' do
102
- expect { subject.get(path) }
103
- .to raise_error(CredHubble::Http::UnknownError, "status: #{status}, body: #{response_body}")
361
+ it 'does not include an authorization header' do
362
+ subject.delete(path)
363
+ assert_requested(:delete, "#{url}#{path}", headers: { 'Content-Type' => 'application/json' })
364
+ end
104
365
  end
105
366
  end
106
367
  end
107
368
 
108
- describe 'SSL verification' do
109
- context 'when verify_ssl is not specified' do
369
+ describe 'SSL/TLS configuration' do
370
+ subject { CredHubble::Http::Client.new(url) }
371
+
372
+ it 'has ssl verification enabled' do
373
+ connection = subject.send(:connection)
374
+ expect(connection.ssl.verify).to eq(true)
375
+ end
376
+
377
+ context 'when a file path is not provided for the CredHub CA' do
110
378
  subject { CredHubble::Http::Client.new(url) }
111
379
 
112
- it 'has ssl verification enabled by default' do
380
+ it 'does not include any additional CA certs' do
113
381
  connection = subject.send(:connection)
114
- expect(connection.ssl.verify).to eq(true)
382
+ expect(connection.ssl.ca_file).to be_nil
115
383
  end
116
384
  end
117
385
 
118
- # TODO: Remove ability to disable ssl verification
119
- context 'when verify_ssl is set to false' do
120
- subject { CredHubble::Http::Client.new(url, verify_ssl: false) }
386
+ context 'when a file path is provided for the CredHub CA' do
387
+ let(:ca_path) { '/custom/certstore/credhub_ca.crt' }
388
+ subject { CredHubble::Http::Client.new(url, ca_path: ca_path) }
121
389
 
122
- it 'has ssl verification disabled' do
390
+ it 'includes the CA cert file path in the connection ssl config' do
123
391
  connection = subject.send(:connection)
124
- expect(connection.ssl.verify).to eq(false)
392
+ expect(connection.ssl.ca_file).to eq(ca_path)
393
+ end
394
+ end
395
+
396
+ describe 'mutual TLS client cert and client key' do
397
+ let(:mock_cert) { instance_double(OpenSSL::X509::Certificate) }
398
+ let(:mock_key) { instance_double(OpenSSL::PKey::RSA) }
399
+
400
+ context 'when a client cert and client key are not provided' do
401
+ subject { CredHubble::Http::Client.new(url) }
402
+
403
+ it 'does not include any client cert or client key' do
404
+ connection = subject.send(:connection)
405
+ expect(connection.ssl.client_cert).to be_nil
406
+ expect(connection.ssl.client_key).to be_nil
407
+ end
408
+ end
409
+
410
+ context 'when a client cert and client key are provided for the CredHub CA' do
411
+ let(:ca_path) { '/custom/certstore/credhub_ca.crt' }
412
+ subject { CredHubble::Http::Client.new(url, client_cert: mock_cert, client_key: mock_key) }
413
+
414
+ it 'includes the cert file in the connection ssl config' do
415
+ connection = subject.send(:connection)
416
+ expect(connection.ssl.client_cert).to eq(mock_cert)
417
+ expect(connection.ssl.client_key).to eq(mock_key)
418
+ end
125
419
  end
126
420
  end
127
421
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CredHubble::Resources::CertificateCredential do
4
+ let(:json_response) do
5
+ '{
6
+ "id": "15811465-8538-460d-9682-5514d44439fd",
7
+ "name": "/load-balancer-tls-cert",
8
+ "type": "certificate",
9
+ "value": {
10
+ "ca": "-----BEGIN CERTIFICATE-----\n... CA CERT ...\n-----END CERTIFICATE-----",
11
+ "certificate": "-----BEGIN CERTIFICATE-----\n... CERTIFICATE ...\n-----END CERTIFICATE-----",
12
+ "private_key": "-----BEGIN RSA PRIVATE KEY-----\n... RSA PRIVATE KEY ...\n-----END RSA PRIVATE KEY-----"
13
+ },
14
+ "version_created_at": "1990-05-18T01:01:01Z"
15
+ }'
16
+ end
17
+
18
+ describe '.from_json' do
19
+ subject { CredHubble::Resources::CertificateCredential }
20
+
21
+ context 'when the JSON includes the required attributes' do
22
+ it 'instantiates a new CertificateCredential object' do
23
+ credential = subject.from_json(json_response)
24
+
25
+ expect(credential).to be_a(CredHubble::Resources::CertificateCredential)
26
+ expect(credential.value.ca)
27
+ .to eq("-----BEGIN CERTIFICATE-----\n... CA CERT ...\n-----END CERTIFICATE-----")
28
+ expect(credential.value.certificate)
29
+ .to eq("-----BEGIN CERTIFICATE-----\n... CERTIFICATE ...\n-----END CERTIFICATE-----")
30
+ expect(credential.value.private_key)
31
+ .to eq("-----BEGIN RSA PRIVATE KEY-----\n... RSA PRIVATE KEY ...\n-----END RSA PRIVATE KEY-----")
32
+ end
33
+ end
34
+
35
+ it_behaves_like 'a Credential resource'
36
+ it_behaves_like 'a JSON deserializing resource'
37
+ end
38
+
39
+ describe '#type' do
40
+ it 'returns "certificate"' do
41
+ subject.type = 'attempting-to-overwrite'
42
+ expect(subject.type).to eq('certificate')
43
+ end
44
+ end
45
+
46
+ describe '#to_json' do
47
+ it_behaves_like 'a JSON serializing resource'
48
+ end
49
+ end