mss-sdk 1.0.0

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 (131) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +192 -0
  5. data/bin/mss-rb +178 -0
  6. data/ca-bundle.crt +3554 -0
  7. data/lib/mss/core/async_handle.rb +89 -0
  8. data/lib/mss/core/cacheable.rb +76 -0
  9. data/lib/mss/core/client.rb +786 -0
  10. data/lib/mss/core/collection/simple.rb +81 -0
  11. data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
  12. data/lib/mss/core/collection/with_next_token.rb +96 -0
  13. data/lib/mss/core/collection.rb +262 -0
  14. data/lib/mss/core/configuration.rb +527 -0
  15. data/lib/mss/core/credential_providers.rb +653 -0
  16. data/lib/mss/core/data.rb +251 -0
  17. data/lib/mss/core/deprecations.rb +83 -0
  18. data/lib/mss/core/endpoints.rb +36 -0
  19. data/lib/mss/core/http/connection_pool.rb +374 -0
  20. data/lib/mss/core/http/curb_handler.rb +150 -0
  21. data/lib/mss/core/http/handler.rb +88 -0
  22. data/lib/mss/core/http/net_http_handler.rb +144 -0
  23. data/lib/mss/core/http/patch.rb +98 -0
  24. data/lib/mss/core/http/request.rb +258 -0
  25. data/lib/mss/core/http/response.rb +80 -0
  26. data/lib/mss/core/indifferent_hash.rb +87 -0
  27. data/lib/mss/core/inflection.rb +55 -0
  28. data/lib/mss/core/ini_parser.rb +41 -0
  29. data/lib/mss/core/json_client.rb +46 -0
  30. data/lib/mss/core/json_parser.rb +75 -0
  31. data/lib/mss/core/json_request_builder.rb +34 -0
  32. data/lib/mss/core/json_response_parser.rb +78 -0
  33. data/lib/mss/core/lazy_error_classes.rb +107 -0
  34. data/lib/mss/core/log_formatter.rb +426 -0
  35. data/lib/mss/core/managed_file.rb +31 -0
  36. data/lib/mss/core/meta_utils.rb +44 -0
  37. data/lib/mss/core/model.rb +61 -0
  38. data/lib/mss/core/naming.rb +29 -0
  39. data/lib/mss/core/option_grammar.rb +737 -0
  40. data/lib/mss/core/options/json_serializer.rb +81 -0
  41. data/lib/mss/core/options/validator.rb +154 -0
  42. data/lib/mss/core/options/xml_serializer.rb +117 -0
  43. data/lib/mss/core/page_result.rb +74 -0
  44. data/lib/mss/core/policy.rb +938 -0
  45. data/lib/mss/core/query_client.rb +40 -0
  46. data/lib/mss/core/query_error_parser.rb +23 -0
  47. data/lib/mss/core/query_request_builder.rb +46 -0
  48. data/lib/mss/core/query_response_parser.rb +34 -0
  49. data/lib/mss/core/region.rb +84 -0
  50. data/lib/mss/core/region_collection.rb +79 -0
  51. data/lib/mss/core/resource.rb +412 -0
  52. data/lib/mss/core/resource_cache.rb +39 -0
  53. data/lib/mss/core/response.rb +214 -0
  54. data/lib/mss/core/response_cache.rb +49 -0
  55. data/lib/mss/core/rest_error_parser.rb +23 -0
  56. data/lib/mss/core/rest_json_client.rb +39 -0
  57. data/lib/mss/core/rest_request_builder.rb +153 -0
  58. data/lib/mss/core/rest_response_parser.rb +65 -0
  59. data/lib/mss/core/rest_xml_client.rb +46 -0
  60. data/lib/mss/core/service_interface.rb +82 -0
  61. data/lib/mss/core/signers/base.rb +45 -0
  62. data/lib/mss/core/signers/cloud_front.rb +55 -0
  63. data/lib/mss/core/signers/s3.rb +158 -0
  64. data/lib/mss/core/signers/version_2.rb +71 -0
  65. data/lib/mss/core/signers/version_3.rb +85 -0
  66. data/lib/mss/core/signers/version_3_https.rb +60 -0
  67. data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
  68. data/lib/mss/core/signers/version_4.rb +227 -0
  69. data/lib/mss/core/uri_escape.rb +43 -0
  70. data/lib/mss/core/xml/frame.rb +245 -0
  71. data/lib/mss/core/xml/frame_stack.rb +84 -0
  72. data/lib/mss/core/xml/grammar.rb +306 -0
  73. data/lib/mss/core/xml/parser.rb +69 -0
  74. data/lib/mss/core/xml/root_frame.rb +64 -0
  75. data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
  76. data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
  77. data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
  78. data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
  79. data/lib/mss/core/xml/stub.rb +122 -0
  80. data/lib/mss/core.rb +602 -0
  81. data/lib/mss/errors.rb +161 -0
  82. data/lib/mss/rails.rb +194 -0
  83. data/lib/mss/s3/access_control_list.rb +262 -0
  84. data/lib/mss/s3/acl_object.rb +263 -0
  85. data/lib/mss/s3/acl_options.rb +200 -0
  86. data/lib/mss/s3/bucket.rb +757 -0
  87. data/lib/mss/s3/bucket_collection.rb +161 -0
  88. data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
  89. data/lib/mss/s3/bucket_region_cache.rb +51 -0
  90. data/lib/mss/s3/bucket_tag_collection.rb +110 -0
  91. data/lib/mss/s3/bucket_version_collection.rb +78 -0
  92. data/lib/mss/s3/cipher_io.rb +119 -0
  93. data/lib/mss/s3/client/xml.rb +265 -0
  94. data/lib/mss/s3/client.rb +2076 -0
  95. data/lib/mss/s3/config.rb +60 -0
  96. data/lib/mss/s3/cors_rule.rb +107 -0
  97. data/lib/mss/s3/cors_rule_collection.rb +193 -0
  98. data/lib/mss/s3/data_options.rb +190 -0
  99. data/lib/mss/s3/encryption_utils.rb +145 -0
  100. data/lib/mss/s3/errors.rb +93 -0
  101. data/lib/mss/s3/multipart_upload.rb +353 -0
  102. data/lib/mss/s3/multipart_upload_collection.rb +75 -0
  103. data/lib/mss/s3/object_collection.rb +355 -0
  104. data/lib/mss/s3/object_metadata.rb +102 -0
  105. data/lib/mss/s3/object_upload_collection.rb +76 -0
  106. data/lib/mss/s3/object_version.rb +153 -0
  107. data/lib/mss/s3/object_version_collection.rb +88 -0
  108. data/lib/mss/s3/paginated_collection.rb +74 -0
  109. data/lib/mss/s3/policy.rb +73 -0
  110. data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
  111. data/lib/mss/s3/prefixed_collection.rb +84 -0
  112. data/lib/mss/s3/presign_v4.rb +135 -0
  113. data/lib/mss/s3/presigned_post.rb +574 -0
  114. data/lib/mss/s3/region_detection.rb +75 -0
  115. data/lib/mss/s3/request.rb +61 -0
  116. data/lib/mss/s3/s3_object.rb +1795 -0
  117. data/lib/mss/s3/tree/branch_node.rb +67 -0
  118. data/lib/mss/s3/tree/child_collection.rb +103 -0
  119. data/lib/mss/s3/tree/leaf_node.rb +93 -0
  120. data/lib/mss/s3/tree/node.rb +21 -0
  121. data/lib/mss/s3/tree/parent.rb +86 -0
  122. data/lib/mss/s3/tree.rb +115 -0
  123. data/lib/mss/s3/uploaded_part.rb +81 -0
  124. data/lib/mss/s3/uploaded_part_collection.rb +83 -0
  125. data/lib/mss/s3/website_configuration.rb +101 -0
  126. data/lib/mss/s3.rb +161 -0
  127. data/lib/mss/version.rb +16 -0
  128. data/lib/mss-sdk.rb +2 -0
  129. data/lib/mss.rb +14 -0
  130. data/rails/init.rb +14 -0
  131. metadata +201 -0
@@ -0,0 +1,653 @@
1
+ # Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ #
8
+ # or in the "license" file accompanying this file. This file is
9
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
10
+ # ANY KIND, either express or implied. See the License for the specific
11
+ # language governing permissions and limitations under the License.
12
+
13
+ require 'set'
14
+ require 'net/http'
15
+ require 'timeout'
16
+ require 'thread'
17
+ require 'time'
18
+ require 'json'
19
+
20
+ module MSS
21
+ module Core
22
+ module CredentialProviders
23
+
24
+ # This module is mixed into the various credential provider
25
+ # classes. It provides a unified interface for getting
26
+ # credentials and refreshing them.
27
+ module Provider
28
+
29
+ # The list of possible keys in the hash returned by {#credentials}.
30
+ KEYS = Set[:access_key_id, :secret_access_key, :session_token]
31
+
32
+ # @return [Hash] Returns a hash of credentials containg at least
33
+ # the `:access_key_id` and `:secret_access_key`. The hash may
34
+ # also contain a `:session_token`.
35
+ #
36
+ # @raise [Errors::MissingCredentialsError] Raised when the
37
+ # `:access_key_id` or the `:secret_access_key` can not be found.
38
+ #
39
+ def credentials
40
+ raise Errors::MissingCredentialsError unless set?
41
+ @cached_credentials.dup
42
+ end
43
+
44
+ # @return [Boolean] Returns true if has credentials and it contains
45
+ # at least the `:access_key_id` and `:secret_access_key`.
46
+ #
47
+ def set?
48
+ @cache_mutex ||= Mutex.new
49
+ unless @cached_credentials
50
+ @cache_mutex.synchronize do
51
+ @cached_credentials ||= get_credentials
52
+ end
53
+ end
54
+ !!(@cached_credentials[:access_key_id] &&
55
+ @cached_credentials[:secret_access_key])
56
+ end
57
+
58
+ # @return [String] Returns the MSS access key id.
59
+ # @raise (see #credentials)
60
+ def access_key_id
61
+ credentials[:access_key_id]
62
+ end
63
+
64
+ # @return [String] Returns the MSS secret access key.
65
+ # @raise (see #credentials)
66
+ def secret_access_key
67
+ credentials[:secret_access_key]
68
+ end
69
+
70
+ # @return [String,nil] Returns the MSS session token or nil if these
71
+ # are not session credentials.
72
+ # @raise (see #credentials)
73
+ def session_token
74
+ credentials[:session_token]
75
+ end
76
+
77
+ # Clears out cached/memoized credentials. Causes the provider
78
+ # to refetch credentials from the source.
79
+ # @return [nil]
80
+ def refresh
81
+ @cached_credentials = nil
82
+ end
83
+
84
+ protected
85
+
86
+ # This method is called on a credential provider to fetch
87
+ # credentials. The credentials hash returned from this
88
+ # method will be cached until the client calls {#refresh}.
89
+ # @return [Hash]
90
+ def get_credentials
91
+ # should be defined in provider classes.
92
+ raise NotImplementedError
93
+ end
94
+
95
+ end
96
+
97
+ # The default credential provider makes a best effort to
98
+ # locate your MSS credentials. It checks a variety of locations
99
+ # in the following order:
100
+ #
101
+ # * Static credentials from MSS.config (e.g. MSS.config.access_key_id,
102
+ # MSS.config.secret_access_key)
103
+ #
104
+ # * The environment (e.g. ENV['MSS_ACCESS_KEY_ID'] or
105
+ # ENV['AMAZON_ACCESS_KEY_ID'])
106
+ #
107
+ # * EC2 metadata service (checks for credentials provided by
108
+ # roles for instances).
109
+ #
110
+ class DefaultProvider
111
+
112
+ include Provider
113
+
114
+ # (see StaticProvider#new)
115
+ def initialize static_credentials = {}
116
+ @providers = []
117
+ @providers << StaticProvider.new(static_credentials)
118
+ @providers << ENVProvider.new('MSS')
119
+ @providers << ENVProvider.new('MSS', :access_key_id => 'ACCESS_KEY', :secret_access_key => 'SECRET_KEY', :session_token => 'SESSION_TOKEN')
120
+ @providers << ENVProvider.new('AMAZON')
121
+ begin
122
+ if Dir.home
123
+ @providers << SharedCredentialFileProvider.new
124
+ end
125
+ rescue ArgumentError, NoMethodError
126
+ end
127
+ @providers << EC2Provider.new
128
+ end
129
+
130
+ # @return [Array<Provider>]
131
+ attr_reader :providers
132
+
133
+ def credentials
134
+ providers.each do |provider|
135
+ if provider.set?
136
+ return provider.credentials
137
+ end
138
+ end
139
+ raise Errors::MissingCredentialsError
140
+ end
141
+
142
+ def set?
143
+ providers.any?(&:set?)
144
+ end
145
+
146
+ def refresh
147
+ providers.each do |provider|
148
+ provider.refresh
149
+ end
150
+ end
151
+ end
152
+
153
+ # Static credentials are provided directly to config via
154
+ # `:access_key_id` and `:secret_access_key` (and optionally
155
+ # `:session_token`).
156
+ # @api private
157
+ class StaticProvider
158
+
159
+ include Provider
160
+
161
+ # @param [Hash] static_credentials
162
+ # @option static_credentials [required,String] :access_key_id
163
+ # @option static_credentials [required,String] :secret_access_key
164
+ # @option static_credentials [String] :session_token
165
+ def initialize static_credentials = {}
166
+
167
+ static_credentials.keys.each do |opt_name|
168
+ unless KEYS.include?(opt_name)
169
+ raise ArgumentError, "invalid option #{opt_name.inspect}"
170
+ end
171
+ end
172
+
173
+ @static_credentials = {}
174
+ KEYS.each do |opt_name|
175
+ if opt_value = static_credentials[opt_name]
176
+ @static_credentials[opt_name] = opt_value
177
+ end
178
+ end
179
+
180
+ end
181
+
182
+ # (see Provider#get_credentials)
183
+ def get_credentials
184
+ @static_credentials
185
+ end
186
+
187
+ end
188
+
189
+ # Fetches credentials from the environment (ENV). You construct
190
+ # an ENV provider with a prefix. Given the prefix "MSS"
191
+ # ENV will be checked for the following keys:
192
+ #
193
+ # * MSS_ACCESS_KEY_ID
194
+ # * MSS_SECRET_ACCESS_KEY
195
+ # * MSS_SESSION_TOKEN (optional)
196
+ #
197
+ class ENVProvider
198
+
199
+ include Provider
200
+
201
+ # @param [String] prefix The prefix to apply to the ENV variable.
202
+ def initialize(prefix, suffixes=Hash[KEYS.map{|key| [key, key.to_s.upcase]}])
203
+ @prefix = prefix
204
+ @suffixes = suffixes
205
+ end
206
+
207
+ # @return [String]
208
+ attr_reader :prefix
209
+
210
+ # (see Provider#get_credentials)
211
+ def get_credentials
212
+ credentials = {}
213
+ KEYS.each do |key|
214
+ if value = ENV["#{@prefix}_#{@suffixes[key]}"]
215
+ credentials[key] = value
216
+ end
217
+ end
218
+
219
+ # Merge in CredentialFileProvider credentials if
220
+ # a #{@prefix}_CREDENTIAL_FILE environment(ENV) variable is set
221
+ if ENV["#{@prefix}_CREDENTIAL_FILE"]
222
+ credentials.merge! CredentialFileProvider.new(ENV["#{@prefix}_CREDENTIAL_FILE"]).get_credentials
223
+ end
224
+
225
+ credentials
226
+ end
227
+
228
+ end
229
+
230
+ # This credential provider gets credentials from a credential file
231
+ # with the following format:
232
+ #
233
+ # AWSAccessKeyId=your_key
234
+ # AWSSecretKey=your_secret
235
+ #
236
+ class CredentialFileProvider
237
+
238
+ include Provider
239
+
240
+ # Map of MSS credential file key names to accepted provider key names
241
+ CREDENTIAL_FILE_KEY_MAP = { "AWSAccessKeyId" => :access_key_id, "AWSSecretKey" => :secret_access_key }
242
+
243
+ attr_reader :credential_file
244
+
245
+ # @param [String] credential_file The file path of a credential file
246
+ def initialize(credential_file)
247
+ @credential_file = credential_file
248
+ end
249
+
250
+ # (see Provider#get_credentials)
251
+ def get_credentials
252
+ credentials = {}
253
+ if File.exist?(credential_file) && File.readable?(credential_file)
254
+ File.open(credential_file, 'r') do |fh|
255
+ fh.each_line do |line|
256
+ key, val = line.strip.split(%r(\s*=\s*))
257
+ if key && val && CREDENTIAL_FILE_KEY_MAP[key] && KEYS.include?(CREDENTIAL_FILE_KEY_MAP[key])
258
+ credentials[CREDENTIAL_FILE_KEY_MAP[key]] = val
259
+ end
260
+ end
261
+ fh.close
262
+ end
263
+ end
264
+ credentials
265
+ end
266
+ end
267
+
268
+ class SharedCredentialFileProvider
269
+
270
+ include Provider
271
+
272
+ def shared_credential_file_path
273
+ if RUBY_VERSION < '1.9'
274
+ msg = "Must specify the :path to your shared credential file when using"
275
+ msg << " Ruby #{RUBY_VERSION}"
276
+ raise ArgumentError, msg
277
+ else
278
+ File.join(Dir.home, '.mss', 'credentials')
279
+ end
280
+ end
281
+ # @api private
282
+ KEY_MAP = {
283
+ "mss_access_key_id" => :access_key_id,
284
+ "mss_secret_access_key" => :secret_access_key,
285
+ "mss_session_token" => :session_token,
286
+ }
287
+
288
+ # @option [String] :path
289
+ # @option [String] :profile_name
290
+ def initialize(options = {})
291
+ @path = options[:path] || shared_credential_file_path
292
+ @profile_name = options[:profile_name]
293
+ @profile_name ||= ENV['MSS_PROFILE']
294
+ @profile_name ||= 'default'
295
+ end
296
+
297
+ # @return [String]
298
+ attr_reader :path
299
+
300
+ # @return [String]
301
+ attr_reader :profile_name
302
+
303
+ # (see Provider#get_credentials)
304
+ def get_credentials
305
+ if File.exist?(path) && File.readable?(path)
306
+ load_from_path
307
+ else
308
+ {}
309
+ end
310
+ end
311
+
312
+ private
313
+
314
+ def load_from_path
315
+ profile = load_profile
316
+ KEY_MAP.inject({}) do |credentials, (source, target)|
317
+ credentials[target] = profile[source] if profile.key?(source)
318
+ credentials
319
+ end
320
+ end
321
+
322
+ def load_profile
323
+ ini = IniParser.parse(File.read(path))
324
+ ini[profile_name] || {}
325
+ end
326
+
327
+ end
328
+
329
+ # This credential provider tries to get credentials from the EC2
330
+ # metadata service.
331
+ class EC2Provider
332
+
333
+ # Raised when an http response is recieved with a non 200
334
+ # http status code.
335
+ # @api private
336
+ class FailedRequestError < StandardError; end
337
+
338
+ # These are the errors we trap when attempting to talk to the
339
+ # instance metadata service. Any of these imply the service
340
+ # is not present, no responding or some other non-recoverable
341
+ # error.
342
+ # @api private
343
+ FAILURES = [
344
+ FailedRequestError,
345
+ Errno::EHOSTUNREACH,
346
+ Errno::ECONNREFUSED,
347
+ SocketError,
348
+ Timeout::Error,
349
+ ]
350
+
351
+ include Provider
352
+
353
+ # @param [Hash] options
354
+ # @option options [String] :ip_address ('169.254.169.254')
355
+ # @option options [Integer] :port (80)
356
+ # @option options [Integer] :retries (0) Number of times to
357
+ # retry retrieving credentials.
358
+ # @option options [Float] :http_open_timeout (1)
359
+ # @option options [Float] :http_read_timeout (1)
360
+ # @option options [Object] :http_debug_output (nil) HTTP wire
361
+ # traces are sent to this object. You can specify something
362
+ # like $stdout.
363
+ def initialize options = {}
364
+ @ip_address = options[:ip_address] || '169.254.169.254'
365
+ @port = options[:port] || 80
366
+ @retries = options[:retries] || 0
367
+ @http_open_timeout = options[:http_open_timeout] || 1
368
+ @http_read_timeout = options[:http_read_timeout] || 1
369
+ @http_debug_output = options[:http_debug_output]
370
+ end
371
+
372
+ # @return [String] Defaults to '169.254.169.254'.
373
+ attr_accessor :ip_address
374
+
375
+ # @return [Integer] Defaults to port 80.
376
+ attr_accessor :port
377
+
378
+ # @return [Integer] Defaults to 0
379
+ attr_accessor :retries
380
+
381
+ # @return [Float]
382
+ attr_accessor :http_open_timeout
383
+
384
+ # @return [Float]
385
+ attr_accessor :http_read_timeout
386
+
387
+ # @return [Object,nil]
388
+ attr_accessor :http_debug_output
389
+
390
+ # @return [Time,nil]
391
+ attr_accessor :credentials_expiration
392
+
393
+ # Refresh provider if existing credentials will be expired in 15 min
394
+ # @return [Hash] Returns a hash of credentials containg at least
395
+ # the `:access_key_id` and `:secret_access_key`. The hash may
396
+ # also contain a `:session_token`.
397
+ #
398
+ # @raise [Errors::MissingCredentialsError] Raised when the
399
+ # `:access_key_id` or the `:secret_access_key` can not be found.
400
+ #
401
+ def credentials
402
+ if @credentials_expiration && @credentials_expiration.utc <= (Time.now.utc + (15 * 60))
403
+ refresh
404
+ end
405
+ super
406
+ end
407
+
408
+ protected
409
+
410
+ # (see Provider#get_credentials)
411
+ def get_credentials
412
+ retries_left = retries
413
+
414
+ begin
415
+
416
+ http = Net::HTTP.new(ip_address, port, nil)
417
+ http.open_timeout = http_open_timeout
418
+ http.read_timeout = http_read_timeout
419
+ http.set_debug_output(http_debug_output) if
420
+ http_debug_output
421
+ http.start
422
+
423
+ # get the first/default instance profile name
424
+ path = '/latest/meta-data/iam/security-credentials/'
425
+ profile_name = get(http, path).lines.map(&:strip).first
426
+
427
+ # get the session details from the instance profile name
428
+ path << profile_name
429
+ session = JSON.parse(get(http, path))
430
+
431
+ http.finish
432
+
433
+ credentials = {}
434
+ credentials[:access_key_id] = session['AccessKeyId']
435
+ credentials[:secret_access_key] = session['SecretAccessKey']
436
+ credentials[:session_token] = session['Token']
437
+ @credentials_expiration = Time.parse(session['Expiration'])
438
+
439
+ credentials
440
+
441
+ rescue *FAILURES => e
442
+ if retries_left > 0
443
+ sleep_time = 2 ** (retries - retries_left)
444
+ Kernel.sleep(sleep_time)
445
+
446
+ retries_left -= 1
447
+ retry
448
+ else
449
+ {}
450
+ end
451
+ end
452
+ end
453
+
454
+ # Makes an HTTP Get request with the given path. If a non-200
455
+ # response is received, then a FailedRequestError is raised.
456
+ # a {FailedRequestError} is raised.
457
+ # @param [Net::HTTPSession] session
458
+ # @param [String] path
459
+ # @raise [FailedRequestError]
460
+ # @return [String] Returns the http response body.
461
+ def get session, path
462
+ response = session.request(Net::HTTP::Get.new(path))
463
+ if response.code.to_i == 200
464
+ response.body
465
+ else
466
+ raise FailedRequestError
467
+ end
468
+ end
469
+
470
+ end
471
+
472
+ # # Session Credential Provider
473
+ #
474
+ # The session provider consumes long term credentials (`:access_key_id`
475
+ # and `:secret_access_key`) and requests a session from STS.
476
+ # It then returns the short term credential set from STS.
477
+ #
478
+ # Calling {#refresh} causes the session provider to request a new
479
+ # set of credentials.
480
+ #
481
+ # This session provider is currently only used for DynamoDB which
482
+ # requires session credentials.
483
+ class SessionProvider
484
+
485
+ include Provider
486
+
487
+ @create_mutex = Mutex.new
488
+
489
+ class << self
490
+
491
+ # @param [Hash] long_term_credentials A hash of credentials with
492
+ # `:access_key_id` and `:secret_access_key` (but not
493
+ # `:session_token`).
494
+ def for long_term_credentials
495
+ @create_mutex.synchronize do
496
+ @session_providers ||= {}
497
+ @session_providers[long_term_credentials[:access_key_id]] =
498
+ self.new(long_term_credentials)
499
+ end
500
+ end
501
+
502
+ # Creation of SessionProviders *must* happen behind the mutex and
503
+ # we want to reuse session providers for the same access key id.
504
+ protected :new
505
+
506
+ end
507
+
508
+ # @param [Hash] long_term_credentials A hash of credentials with
509
+ # `:access_key_id` and `:secret_access_key` (but not
510
+ # `:session_token`).
511
+ def initialize long_term_credentials
512
+ @static = StaticProvider.new(long_term_credentials)
513
+ if @static.session_token
514
+ raise ArgumentError, 'invalid option :session_token'
515
+ end
516
+ @session_mutex = Mutex.new
517
+ end
518
+
519
+ # Aliasing the refresh method so we can call it from the refresh
520
+ # method defined in this class.
521
+ alias_method :orig_refresh, :refresh
522
+ protected :orig_refresh
523
+
524
+ # (see Provider#refresh)
525
+ def refresh
526
+ refresh_session
527
+ orig_refresh
528
+ end
529
+
530
+ protected
531
+
532
+ # (see Provider#get_credentials)
533
+ def get_credentials
534
+ session = cached_session
535
+ if session.nil?
536
+ refresh_session
537
+ session = cached_session
538
+ end
539
+ session.credentials
540
+ end
541
+
542
+ # Replaces the cached STS session with a new one.
543
+ # @return [nil]
544
+ def refresh_session
545
+ sts = MSS::STS.new(@static.credentials.merge(:use_ssl => true))
546
+ @session_mutex.synchronize do
547
+ @session = sts.new_session
548
+ end
549
+ nil
550
+ end
551
+
552
+ # @return [nil,STS::Session] Returns nil if a session has not
553
+ # already been started.
554
+ def cached_session
555
+ local_session = nil
556
+ @session_mutex.synchronize do
557
+ local_session = @session
558
+ end
559
+ local_session
560
+ end
561
+
562
+ end
563
+
564
+ # An auto-refreshing credential provider that works by assuming
565
+ # a role via {MSS::STS#assume_role}.
566
+ #
567
+ # provider = MSS::Core::CredentialProviders::AssumeRoleProvider.new(
568
+ # sts: MSS::STS.new(access_key_id:'AKID', secret_access_key:'SECRET'),
569
+ # # assume role options:
570
+ # role_arn: "linked::account::arn",
571
+ # role_session_name: "session-name"
572
+ # )
573
+ #
574
+ # ec2 = MSS::EC2.new(credential_provider:provider)
575
+ #
576
+ # If you omit the `:sts` option, a new {STS} service object will be
577
+ # constructed and it will use the default credential provider
578
+ # from {mss.config}.
579
+ #
580
+ class AssumeRoleProvider
581
+
582
+ include Provider
583
+
584
+ # @option options [MSS::STS] :sts (STS.new) An instance of {MSS::STS}.
585
+ # This is used to make the API call to assume role.
586
+ # @option options [required, String] :role_arn
587
+ # @option options [required, String] :role_session_name
588
+ # @option options [String] :policy
589
+ # @option options [Integer] :duration_seconds
590
+ # @option options [String] :external_id
591
+ def initialize(options = {})
592
+ @options = options.dup
593
+ @sts = @options.delete(:sts) || STS.new
594
+ end
595
+
596
+ def credentials
597
+ refresh if near_expiration?
598
+ super
599
+ end
600
+
601
+ private
602
+
603
+ def near_expiration?
604
+ @expiration && @expiration.utc <= Time.now.utc + 5 * 60
605
+ end
606
+
607
+ def get_credentials
608
+ role = @sts.assume_role(@options)
609
+ @expiration = role[:credentials][:expiration]
610
+ role[:credentials]
611
+ end
612
+
613
+ end
614
+
615
+ # Returns a set of fake credentials, should only be used for testing.
616
+ class FakeProvider < StaticProvider
617
+
618
+ # @param [Hash] options
619
+ # @option options [Boolean] :with_session_token (false) When `true` a
620
+ # fake session token will also be provided.
621
+ def initialize options = {}
622
+ options[:access_key_id] ||= fake_access_key_id
623
+ options[:secret_access_key] ||= fake_secret_access_key
624
+ if options.delete(:with_session_token)
625
+ options[:session_token] ||= fake_session_token
626
+ end
627
+ super
628
+ end
629
+
630
+ protected
631
+
632
+ def fake_access_key_id
633
+ "AKIA" + random_chars(16).upcase
634
+ end
635
+
636
+ def fake_secret_access_key
637
+ random_chars(40)
638
+ end
639
+
640
+ def fake_session_token
641
+ random_chars(260)
642
+ end
643
+
644
+ def random_chars count
645
+ chars = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
646
+ (1..count).map{ chars[rand(chars.size)] }.join
647
+ end
648
+
649
+ end
650
+
651
+ end
652
+ end
653
+ end