right_aws_api 0.2.0 → 0.2.2
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.
- checksums.yaml +4 -4
- data/HISTORY +9 -0
- data/lib/cloud/aws/base/helpers/utils.rb +216 -13
- data/lib/cloud/aws/base/manager.rb +1 -1
- data/lib/cloud/aws/base/routines/request_signer.rb +13 -29
- data/lib/cloud/aws/s3/link/routines/request_signer.rb +12 -44
- data/lib/cloud/aws/s3/manager.rb +13 -0
- data/lib/cloud/aws/s3/routines/request_signer.rb +38 -107
- data/lib/cloud/aws/s3/wrappers/default.rb +37 -35
- data/lib/cloud/aws/sqs/manager.rb +2 -2
- data/lib/cloud/aws/sqs/routines/request_signer.rb +62 -0
- data/lib/right_aws_api_version.rb +1 -1
- data/right_aws_api.gemspec +2 -1
- data/spec/cloud/aws/base/helpers/utils_spec.rb +298 -0
- data/spec/cloud/aws/s3/routines/request_signer_spec.rb +3 -119
- metadata +23 -8
- data/spec/cloud/aws/s3/link/routines/request_signer_spec.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 164b81b2c4e7845571d6616ce59b22a3baa7788a
|
4
|
+
data.tar.gz: e7cbd22f3857d650a026b29699e22cc9718d1821
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b970d6a9794c29953c16e63018f7a97522476f66a7357ea5be7e0bd6d3d110b880615d86531a45a70b7ebe41d96d3bf18697074d2a7fc5844b1fc16caf51089
|
7
|
+
data.tar.gz: d0846816165f048ebfa43d026d144c03c35158f4006c750a7d0c14117a1f50d4031a3d46422b34321935d182e074f121bc426e84be043b3ba76371ed03b59ffa
|
data/HISTORY
CHANGED
@@ -1,2 +1,11 @@
|
|
1
|
+
== 2015-01-16
|
2
|
+
- v0.2.2
|
3
|
+
Added an ability to not use DNS like buckets in AWS S3
|
4
|
+
Some minor bugs were fixed in singature V4
|
5
|
+
|
6
|
+
== 2014-10-30
|
7
|
+
- v0.2.1
|
8
|
+
Added support for AWS v4 signature and make it the default
|
9
|
+
|
1
10
|
== 2013-06-28
|
2
11
|
- pre-release candidate created
|
@@ -171,7 +171,7 @@ module RightScale
|
|
171
171
|
#
|
172
172
|
# @example
|
173
173
|
# sign_s3_signature('secret', :get, 'xxx/yyy/zzz/object', {'header'=>'value'}) #=>
|
174
|
-
# "i85igH0sftHD/cGZcLiBKcYEuks="
|
174
|
+
# "i85igH0sftHD/cGZcLiBKcYEuks="
|
175
175
|
#
|
176
176
|
# @see http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
|
177
177
|
#
|
@@ -196,7 +196,210 @@ module RightScale
|
|
196
196
|
sign(aws_secret_access_key, string_to_sign)
|
197
197
|
end
|
198
198
|
|
199
|
-
|
199
|
+
|
200
|
+
def self.sign_v4_get_service_and_region(host)
|
201
|
+
result =
|
202
|
+
case
|
203
|
+
when host[ /^iam\.amazonaws\.com$/i ] then ['iam', 'us-east-1']
|
204
|
+
when host[ /^(.*\.)?s3\.amazonaws\.com$/i ] then ['s3', 'us-east-1']
|
205
|
+
when host[ /^(.*\.)?s3-external-1\.amazonaws\.com$/i ] then ['s3', 'us-east-1']
|
206
|
+
when host[ /s3-website-([^.]+)\.amazonaws\.com$/i ] then ['s3', $1]
|
207
|
+
when host[ /^(.*\.)?s3-([^.]+).amazonaws\.com$/i ] then ['s3', $2]
|
208
|
+
when host[ /^(.*\.)?s3\.([^.]+)\.amazonaws\.com$/i ] then ['s3', $2]
|
209
|
+
else host[ /^([^.]+)\.([^.]+)\.amazonaws\.com$/i ] && [$1, $2]
|
210
|
+
end
|
211
|
+
fail(ArgumentError, "Cannot extract service name from %s host" % host.inspect) if !result || result[0].to_s.empty?
|
212
|
+
fail(ArgumentError, "Cannot extract region name from %s host" % host.inspect) if result[1].to_s.empty?
|
213
|
+
result
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Signs and Authenticates REST Requests
|
218
|
+
#
|
219
|
+
# @param [String] aws_secret_access_key
|
220
|
+
# @param [String] aws_access_key
|
221
|
+
# @param [String] host
|
222
|
+
# @param [Hash] request
|
223
|
+
#
|
224
|
+
# @return [String]
|
225
|
+
#
|
226
|
+
# @see http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
227
|
+
#
|
228
|
+
def self.sign_v4_signature(aws_access_key, aws_secret_access_key, host, request, method=:headers)
|
229
|
+
now = Time.now.utc
|
230
|
+
current_date = now.strftime("%Y%m%d")
|
231
|
+
current_time = now.strftime("%Y%m%dT%H%M%SZ")
|
232
|
+
host = host.downcase
|
233
|
+
service, region = sign_v4_get_service_and_region(host)
|
234
|
+
creds_scope = "%s/%s/%s/aws4_request" % [current_date, region, service]
|
235
|
+
algorithm = "AWS4-HMAC-SHA256"
|
236
|
+
|
237
|
+
# Verb
|
238
|
+
canonical_verb = sign_v4_get_canonical_verb(request[:verb])
|
239
|
+
|
240
|
+
# Path
|
241
|
+
request[:path] ||= '/'
|
242
|
+
canonical_path = sign_v4_get_canonical_path(request[:path])
|
243
|
+
|
244
|
+
# Headers (Auth)
|
245
|
+
request[:headers].delete('Authorization')
|
246
|
+
if method == :headers
|
247
|
+
canonical_payload = sign_v4_headers(request, host, current_time)
|
248
|
+
end
|
249
|
+
# Headers (Standard)
|
250
|
+
request[:headers]['Host'] = host
|
251
|
+
_headers = {}
|
252
|
+
request[:headers].each do |key, value|
|
253
|
+
_headers[key.to_s.downcase] = value.is_a?(Array) ? value.join(',') : value
|
254
|
+
end
|
255
|
+
canonical_headers = sign_v4_get_canonical_headers(_headers)
|
256
|
+
signed_headers = sign_v4_get_signed_headers(_headers)
|
257
|
+
|
258
|
+
# Params (Auth)
|
259
|
+
if method != :headers
|
260
|
+
canonical_payload = sign_v4_query_params(
|
261
|
+
request,
|
262
|
+
algorithm,
|
263
|
+
current_time,
|
264
|
+
signed_headers,
|
265
|
+
aws_access_key,
|
266
|
+
creds_scope
|
267
|
+
)
|
268
|
+
end
|
269
|
+
# Params (Standard)
|
270
|
+
canonical_query_string = Utils::params_to_urn(request[:params]){ |value| amz_escape(value) }
|
271
|
+
|
272
|
+
# Canonical String
|
273
|
+
canonical_string = sign_v4_get_canonical_string(
|
274
|
+
canonical_verb,
|
275
|
+
canonical_path,
|
276
|
+
canonical_query_string,
|
277
|
+
canonical_headers,
|
278
|
+
signed_headers,
|
279
|
+
canonical_payload
|
280
|
+
)
|
281
|
+
|
282
|
+
# StringToSign
|
283
|
+
string_to_sign = sign_v4_get_string_to_sign(algorithm, current_time, creds_scope, canonical_string)
|
284
|
+
|
285
|
+
# Signature
|
286
|
+
signature = sign_v4_get_signature_key(aws_secret_access_key, string_to_sign, current_date, region, service)
|
287
|
+
|
288
|
+
request[:path] += "?%s" % canonical_query_string unless canonical_query_string.empty?
|
289
|
+
|
290
|
+
if method == :headers
|
291
|
+
# Authorization Header
|
292
|
+
authorization_header = "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s" %
|
293
|
+
[algorithm, aws_access_key, creds_scope, signed_headers, signature]
|
294
|
+
request[:headers]['Authorization'] = authorization_header
|
295
|
+
else
|
296
|
+
request[:path] += "&X-Amz-Signature=%s" % signature
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
def self.sign_v4_get_canonical_verb(verb)
|
302
|
+
verb.to_s.upcase
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
def self.sign_v4_get_canonical_path(path)
|
307
|
+
path
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
def self.sign_v4_query_params(request, algorithm, current_time, signed_headers, aws_access_key, creds_scope)
|
312
|
+
expires_at = request[:params]['X-Amz-Expires'] || 3600
|
313
|
+
expires_at = expires_at.to_i if expires_at.is_a?(Time)
|
314
|
+
|
315
|
+
request[:params]['X-Amz-Date'] = current_time
|
316
|
+
request[:params]['X-Amz-Expires'] = expires_at
|
317
|
+
request[:params]['X-Amz-Algorithm'] = algorithm
|
318
|
+
request[:params]['X-Amz-SignedHeaders'] = signed_headers
|
319
|
+
request[:params]['X-Amz-Credential'] = "%s/%s" % [aws_access_key, creds_scope]
|
320
|
+
|
321
|
+
'UNSIGNED-PAYLOAD'
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
def self.sign_v4_get_canonical_headers(headers)
|
326
|
+
headers.sort.map{ |key, value| "#{key}:#{value}" }.join("\n")
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
def self.sign_v4_get_signed_headers(headers)
|
331
|
+
headers.keys.sort.join(';')
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
def self.sign_v4_headers(request, host, current_time)
|
336
|
+
expires_at = request[:headers]['X-Amz-Expires'].first || 3600
|
337
|
+
expires_at = expires_at.to_i if expires_at.is_a?(Time)
|
338
|
+
|
339
|
+
if request[:body].is_a?(IO)
|
340
|
+
canonical_payload = ''
|
341
|
+
request[:headers].set_if_blank('X-Amz-Content-Sha256', 'UNSIGNED-PAYLOAD')
|
342
|
+
else
|
343
|
+
request[:body] = request[:body].to_s
|
344
|
+
canonical_payload = hex_encode(Digest::SHA256.digest(request[:body]))
|
345
|
+
content_type = 'application/x-www-form-urlencoded; charset=utf-8'
|
346
|
+
content_md5 = Base64::encode64(Digest::MD5::digest(request[:body])).strip
|
347
|
+
request[:headers].set_if_blank('Content-Length', request[:body].bytesize)
|
348
|
+
request[:headers].set_if_blank('Content-Type', content_type)
|
349
|
+
request[:headers].set_if_blank('Content-Md5', content_md5)
|
350
|
+
request[:headers].set_if_blank('X-Amz-Content-Sha256', canonical_payload)
|
351
|
+
end
|
352
|
+
request[:headers]['X-Amz-Date'] = current_time
|
353
|
+
request[:headers]['X-Amz-Expires'] = expires_at
|
354
|
+
|
355
|
+
canonical_payload
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
# Signature V4: Returns Canonical String
|
360
|
+
#
|
361
|
+
# @return [String]
|
362
|
+
#
|
363
|
+
def self.sign_v4_get_canonical_string(verb, path, query_string, headers, signed_headers, payload)
|
364
|
+
verb + "\n" +
|
365
|
+
path + "\n" +
|
366
|
+
query_string + "\n" +
|
367
|
+
headers + "\n\n" +
|
368
|
+
signed_headers + "\n" +
|
369
|
+
payload
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
# Signature V4: A string to sign value
|
374
|
+
#
|
375
|
+
# @return [String]
|
376
|
+
#
|
377
|
+
def self.sign_v4_get_string_to_sign(algorithm, current_time, creds_scope, canonical_string)
|
378
|
+
algorithm + "\n" +
|
379
|
+
current_time + "\n" +
|
380
|
+
creds_scope + "\n" +
|
381
|
+
hex_encode(Digest::SHA256.digest(canonical_string)).downcase
|
382
|
+
end
|
383
|
+
|
384
|
+
|
385
|
+
#Helpers from AWS documentation http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
|
386
|
+
def self.sign_v4_get_signature_key(key, string_to_sign, date, region, service, digest = @@digest256)
|
387
|
+
k_date = OpenSSL::HMAC.digest(digest, "AWS4" + key, date)
|
388
|
+
k_region = OpenSSL::HMAC.digest(digest, k_date, region)
|
389
|
+
k_service = OpenSSL::HMAC.digest(digest, k_region, service)
|
390
|
+
k_signing = OpenSSL::HMAC.digest(digest, k_service, "aws4_request")
|
391
|
+
hex_encode OpenSSL::HMAC.digest(digest, k_signing, string_to_sign)
|
392
|
+
end
|
393
|
+
|
394
|
+
|
395
|
+
def self.hex_encode(bindata)
|
396
|
+
result=""
|
397
|
+
data=bindata.unpack("C*")
|
398
|
+
data.each {|b| result+= "%02x" % b}
|
399
|
+
result
|
400
|
+
end
|
401
|
+
|
402
|
+
|
200
403
|
# Parametrizes data to the format that Amazon EC2 (and compatible APIs) loves
|
201
404
|
#
|
202
405
|
# @param [Hash] data
|
@@ -207,7 +410,7 @@ module RightScale
|
|
207
410
|
# # Where hash is:
|
208
411
|
# { Name.?.Mask => Value | [ Values ],
|
209
412
|
# NamePrefix.? => [{ SubNameA.1 => ValueA.1, SubNameB.1 => ValueB.1 }, # any simple parameter
|
210
|
-
# ...,
|
413
|
+
# ...,
|
211
414
|
# { SubNameN.X => ValueN.X, SubNameM.X => ValueN.X }] # see BlockDeviceMapping case
|
212
415
|
#
|
213
416
|
# @example
|
@@ -237,15 +440,15 @@ module RightScale
|
|
237
440
|
# 'MaxCount' => 2,
|
238
441
|
# 'KeyName' => 'my-key',
|
239
442
|
# 'SecurityGroupId' => ['sg-01234567', 'sg-12345670', 'sg-23456701'],
|
240
|
-
# 'BlockDeviceMapping' => [
|
241
|
-
# { 'DeviceName' => '/dev/sda1',
|
242
|
-
# 'Ebs.SnapshotId' => 'snap-01234567',
|
443
|
+
# 'BlockDeviceMapping' => [
|
444
|
+
# { 'DeviceName' => '/dev/sda1',
|
445
|
+
# 'Ebs.SnapshotId' => 'snap-01234567',
|
243
446
|
# 'Ebs.VolumeSize' => 20,
|
244
447
|
# 'Ebs.DeleteOnTermination' => true },
|
245
|
-
# { 'DeviceName' => '/dev/sdb1',
|
246
|
-
# 'Ebs.SnapshotId' => 'snap-12345670',
|
448
|
+
# { 'DeviceName' => '/dev/sdb1',
|
449
|
+
# 'Ebs.SnapshotId' => 'snap-12345670',
|
247
450
|
# 'Ebs.VolumeSize' => 10,
|
248
|
-
# 'Ebs.DeleteOnTermination' => false } ] ) #=>
|
451
|
+
# 'Ebs.DeleteOnTermination' => false } ] ) #=>
|
249
452
|
# {
|
250
453
|
# "BlockDeviceMapping.1.DeviceName" => "/dev/sda1",
|
251
454
|
# "BlockDeviceMapping.1.Ebs.DeleteOnTermination" => true,
|
@@ -262,7 +465,7 @@ module RightScale
|
|
262
465
|
# "SecurityGroupId.1" => "sg-01234567",
|
263
466
|
# "SecurityGroupId.2" => "sg-12345670",
|
264
467
|
# "SecurityGroupId.3" => "sg-23456701"
|
265
|
-
# }
|
468
|
+
# }
|
266
469
|
#
|
267
470
|
# @example
|
268
471
|
# parametrize( 'DomainName' => 'kdclient',
|
@@ -276,7 +479,7 @@ module RightScale
|
|
276
479
|
# { 'ItemName' => 'diana',
|
277
480
|
# 'Attribute' => [ { 'Name' => 'sex', 'Value' => 'female' },
|
278
481
|
# { 'Name' => 'weight', 'Value' => '120'},
|
279
|
-
# { 'Name' => 'age', 'Value' => '25'} ] } ] ) #=>
|
482
|
+
# { 'Name' => 'age', 'Value' => '25'} ] } ] ) #=>
|
280
483
|
# { "DomainName" => "kdclient",
|
281
484
|
# "Item.1.ItemName" => "konstantin",
|
282
485
|
# "Item.1.Attribute.1.Name" => "sex",
|
@@ -321,8 +524,8 @@ module RightScale
|
|
321
524
|
end
|
322
525
|
result
|
323
526
|
end
|
324
|
-
|
527
|
+
|
325
528
|
end
|
326
529
|
end
|
327
530
|
end
|
328
|
-
end
|
531
|
+
end
|
@@ -116,7 +116,7 @@ module RightScale
|
|
116
116
|
opts[:headers] = params.delete(:headers) || {}
|
117
117
|
opts[:options] = params.delete(:options) || {}
|
118
118
|
opts[:params] = parametrize(params)
|
119
|
-
process_api_request(:
|
119
|
+
process_api_request(:post, '', opts, &block)
|
120
120
|
end
|
121
121
|
|
122
122
|
|
@@ -43,38 +43,22 @@ module RightScale
|
|
43
43
|
# no example
|
44
44
|
#
|
45
45
|
def process
|
46
|
-
# Make sure all the required params are set
|
47
|
-
@data[:request][:params]['AWSAccessKeyId'] = @data[:credentials][:aws_access_key_id]
|
48
|
-
@data[:request][:params]['Version'] ||= @data[:options][:api_version]
|
49
46
|
# Compile a final request path
|
50
47
|
@data[:request][:path] = Utils::join_urn(@data[:connection][:uri].path, @data[:request][:relative_path])
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
signed_path = sign_proc.call(@data)
|
64
|
-
end
|
65
|
-
# Set new path or body and content-type
|
66
|
-
case @data[:request][:verb]
|
67
|
-
when :get
|
68
|
-
@data[:request][:path] << "?#{signed_path}"
|
69
|
-
when :post
|
70
|
-
@data[:request][:body] = signed_path
|
71
|
-
@data[:request][:headers]['content-type'] = 'application/x-www-form-urlencoded; charset=utf-8'
|
72
|
-
else
|
73
|
-
fail Error::new("Unsupported HTTP verb: #{@data[:request][:verb]}")
|
74
|
-
end
|
48
|
+
|
49
|
+
# Swap query params and body
|
50
|
+
@data[:request][:params]['Version'] ||= @data[:options][:api_version]
|
51
|
+
@data[:request][:body] = Utils::params_to_urn(@data[:request][:params]){ |value| Utils::AWS::amz_escape(value) }
|
52
|
+
@data[:request][:params] = {}
|
53
|
+
|
54
|
+
Utils::AWS::sign_v4_signature(
|
55
|
+
@data[:credentials][:aws_access_key_id],
|
56
|
+
@data[:credentials][:aws_secret_access_key],
|
57
|
+
@data[:connection][:uri].host,
|
58
|
+
@data[:request]
|
59
|
+
)
|
75
60
|
end
|
76
61
|
end
|
77
|
-
|
78
62
|
end
|
79
63
|
end
|
80
|
-
end
|
64
|
+
end
|
@@ -43,25 +43,24 @@ module RightScale
|
|
43
43
|
fail Error::new("Body must be blank") unless @data[:request][:body]._blank?
|
44
44
|
fail Error::new("Headers must be blank") unless @data[:request][:headers]._blank?
|
45
45
|
|
46
|
-
uri
|
47
|
-
|
48
|
-
|
49
|
-
bucket = @data[:request][:bucket]
|
50
|
-
object = @data[:request][:relative_path]
|
51
|
-
params = @data[:request][:params]
|
52
|
-
verb = @data[:request][:verb]
|
53
|
-
|
46
|
+
uri = @data[:connection][:uri]
|
47
|
+
bucket = @data[:request][:bucket]
|
48
|
+
object = @data[:request][:relative_path]
|
54
49
|
bucket, object = compute_bucket_name_and_object_path(bucket, object)
|
55
50
|
uri = compute_host(bucket, uri)
|
56
51
|
|
57
|
-
|
52
|
+
@data[:request][:path] = compute_path(bucket, object)
|
58
53
|
|
59
|
-
|
60
|
-
|
61
|
-
|
54
|
+
Utils::AWS::sign_v4_signature(
|
55
|
+
@data[:credentials][:aws_access_key_id],
|
56
|
+
@data[:credentials][:aws_secret_access_key],
|
57
|
+
@data[:connection][:uri].host,
|
58
|
+
@data[:request],
|
59
|
+
:query_params
|
60
|
+
)
|
62
61
|
|
63
62
|
# Compute href
|
64
|
-
path =
|
63
|
+
path = @data[:request][:path]
|
65
64
|
uri.path, uri.query = path.split('?')
|
66
65
|
@data[:result] = uri.to_s
|
67
66
|
|
@@ -69,37 +68,6 @@ module RightScale
|
|
69
68
|
@data[:vars][:system][:done] = true
|
70
69
|
end
|
71
70
|
|
72
|
-
|
73
|
-
# Sets response params
|
74
|
-
#
|
75
|
-
# @param [Hash] params
|
76
|
-
#
|
77
|
-
# @return [Hash]
|
78
|
-
#
|
79
|
-
def compute_params!(params, access_key)
|
80
|
-
# Expires
|
81
|
-
expires = params['Expires']
|
82
|
-
expires ||= Time.now.utc.to_i + ONE_YEAR_OF_SECONDS
|
83
|
-
expires = expires.to_i unless expires.is_a?(Fixnum)
|
84
|
-
params['Expires'] = expires
|
85
|
-
params['AWSAccessKeyId'] = access_key
|
86
|
-
params
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
# Computes signature
|
91
|
-
#
|
92
|
-
# @param [String] secret_key
|
93
|
-
# @param [String] verb
|
94
|
-
# @param [String] bucket
|
95
|
-
# @param [Hash] params
|
96
|
-
#
|
97
|
-
# @return [String]
|
98
|
-
#
|
99
|
-
def compute_signature(secret_key, verb, bucket, object, params)
|
100
|
-
can_path = compute_canonicalized_path(bucket, object, params)
|
101
|
-
Utils::AWS::sign_s3_signature(secret_key, verb, can_path, { 'expires' => params['Expires'] })
|
102
|
-
end
|
103
71
|
end
|
104
72
|
|
105
73
|
end
|
data/lib/cloud/aws/s3/manager.rb
CHANGED
@@ -293,6 +293,19 @@ module RightScale
|
|
293
293
|
# 'https://s3.amazonaws.com/?AWSAccessKeyId=AK...TA&Expires=1436651780&
|
294
294
|
# Signature=XK...53s%3D'
|
295
295
|
#
|
296
|
+
# @example
|
297
|
+
# link = RightScale::CloudApi::AWS::S3::Link::Manager::new(key, secret, endpoint)
|
298
|
+
# link.GetObject('Bucket' => 'foo', 'Object' => 'bar') #=>
|
299
|
+
# 'https://foo.s3.amazonaws.com/bar?AWSAccessKeyId=AK...TA&Expires=1436557118&
|
300
|
+
# Signature=hg...%3D&response-content-type=image%2Fpeg'
|
301
|
+
#
|
302
|
+
# @example
|
303
|
+
# # Do not use DNS-like bucket hosts but put buckets into path
|
304
|
+
# link = RightScale::CloudApi::AWS::S3::Link::Manager::new(key, secret, endpoint, :no_dns_buckets => true)
|
305
|
+
# link.GetObject('Bucket' => 'foo', 'Object' => 'bar') #=>
|
306
|
+
# 'https://s3.amazonaws.com/foo/bar?AWSAccessKeyId=AK...TA&Expires=1436557118&
|
307
|
+
# Signature=hg...%3D&response-content-type=image%2Fpeg'
|
308
|
+
#
|
296
309
|
# @see ApiManager
|
297
310
|
# @see Wrapper::DEFAULT.extended Wrapper::DEFAULT.extended (click [View source])
|
298
311
|
# @see http://docs.aws.amazon.com/AmazonS3/latest/API/APIRest.html
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
#--
|
2
3
|
# Copyright (c) 2013 RightScale, Inc.
|
3
4
|
#
|
@@ -75,91 +76,28 @@ module RightScale
|
|
75
76
|
# # no example
|
76
77
|
#
|
77
78
|
def process
|
78
|
-
uri
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
bucket = @data[:request][:bucket]
|
83
|
-
headers = @data[:request][:headers]
|
84
|
-
object = @data[:request][:relative_path]
|
85
|
-
params = @data[:request][:params]
|
86
|
-
verb = @data[:request][:verb]
|
87
|
-
|
79
|
+
uri = @data[:connection][:uri]
|
80
|
+
body = @data[:request][:body]
|
81
|
+
bucket = @data[:request][:bucket]
|
82
|
+
object = @data[:request][:relative_path]
|
88
83
|
bucket, object = compute_bucket_name_and_object_path(bucket, object)
|
89
|
-
body = compute_body(body, headers['content-type'])
|
84
|
+
body = compute_body(body, @data[:request][:headers]['content-type'])
|
90
85
|
uri = compute_host(bucket, uri)
|
91
86
|
|
92
|
-
compute_headers!(headers, body, uri.host)
|
93
|
-
|
94
|
-
# Set Authorization header
|
95
|
-
signature = compute_signature(access_key, secret_key, verb, bucket, object, params, headers)
|
96
|
-
headers['authorization'] = "AWS #{access_key}:#{signature}"
|
97
|
-
|
98
|
-
@data[:request][:body] = body
|
99
|
-
@data[:request][:bucket] = bucket
|
100
|
-
@data[:request][:headers] = headers
|
101
|
-
@data[:request][:params] = params
|
102
|
-
@data[:request][:path] = compute_path(bucket, object, params)
|
103
|
-
@data[:request][:relative_path] = object
|
104
|
-
@data[:connection][:uri] = uri
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
# Returns a list of sub-resource(s)
|
109
|
-
#
|
110
|
-
# Sub-resources are acl, torrent, versioning, location, etc. See SUB_RESOURCES
|
111
|
-
#
|
112
|
-
# @return [Hash]
|
113
|
-
#
|
114
|
-
def get_subresources(params)
|
115
|
-
result = {}
|
116
|
-
params.each do |key, value|
|
117
|
-
next unless SUB_RESOURCES.include?(key) || key[OVERRIDE_RESPONSE_HEADERS]
|
118
|
-
result[key] = (value._blank? ? nil : value)
|
119
|
-
end
|
120
|
-
result
|
121
|
-
end
|
87
|
+
compute_headers!(@data[:request][:headers], body, uri.host)
|
122
88
|
|
89
|
+
@data[:connection][:uri] = uri
|
90
|
+
@data[:request][:bucket] = bucket
|
91
|
+
@data[:request][:relative_path] = object
|
92
|
+
@data[:request][:body] = body
|
93
|
+
@data[:request][:path] = compute_path(bucket, object)
|
123
94
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
# @example
|
131
|
-
# # DNS bucket
|
132
|
-
# compute_canonicalized_bucket('foo-bar') #=> 'foo-bar/'
|
133
|
-
#
|
134
|
-
# @example
|
135
|
-
# # non DNS bucket
|
136
|
-
# compute_canonicalized_bucket('foo_bar') #=> 'foo_bar'
|
137
|
-
#
|
138
|
-
def compute_canonicalized_bucket(bucket)
|
139
|
-
bucket += '/' if Utils::AWS::is_dns_bucket?(bucket)
|
140
|
-
bucket
|
141
|
-
end
|
142
|
-
|
143
|
-
|
144
|
-
# Returns canonicalized path
|
145
|
-
#
|
146
|
-
# @param [String] bucket
|
147
|
-
# @param [String] relative_path
|
148
|
-
# @param [Hash] params
|
149
|
-
#
|
150
|
-
# @return [String]
|
151
|
-
#
|
152
|
-
# @example
|
153
|
-
# params = { 'Foo' => 1, 'acl' => '2', 'response-content-type' => 'jpg' }
|
154
|
-
# compute_canonicalized_path('foo-bar_bucket', 'a/b/c/d.jpg', params)
|
155
|
-
# #=> '/foo-bar_bucket/a/b/c/d.jpg?acl=3&response-content-type=jpg'
|
156
|
-
#
|
157
|
-
def compute_canonicalized_path(bucket, relative_path, params)
|
158
|
-
can_bucket = compute_canonicalized_bucket(bucket)
|
159
|
-
sub_params = get_subresources(params)
|
160
|
-
# We use the block below to avoid escaping: Amazon does not like escaped bucket and '/'
|
161
|
-
# in canonicalized path (relative path has been escaped above already)
|
162
|
-
Utils::join_urn(can_bucket, relative_path, sub_params) { |value| value }
|
95
|
+
Utils::AWS::sign_v4_signature(
|
96
|
+
@data[:credentials][:aws_access_key_id],
|
97
|
+
@data[:credentials][:aws_secret_access_key],
|
98
|
+
@data[:connection][:uri].host,
|
99
|
+
@data[:request]
|
100
|
+
)
|
163
101
|
end
|
164
102
|
|
165
103
|
|
@@ -180,7 +118,10 @@ module RightScale
|
|
180
118
|
relative_path.to_s[/^([^\/]*)\/?(.*)$/]
|
181
119
|
# Escape part of the path that may have UTF-8 chars (in S3 Object name for instance).
|
182
120
|
# Amazon wants them to be escaped before we sign the request.
|
183
|
-
|
121
|
+
# P.S. but do not escape "/" (signature v4 does not like this)
|
122
|
+
#
|
123
|
+
object = $2.to_s.split('/').map{|i| Utils::AWS::amz_escape(i)}.join('/')
|
124
|
+
[ $1, object ]
|
184
125
|
end
|
185
126
|
|
186
127
|
|
@@ -206,7 +147,7 @@ module RightScale
|
|
206
147
|
# @return [URI]
|
207
148
|
#
|
208
149
|
def compute_host(bucket, uri)
|
209
|
-
return uri unless
|
150
|
+
return uri unless is_dns_bucket?(bucket)
|
210
151
|
return uri if uri.host[/^#{bucket}\..+\.[^.]+\.[^.]+$/]
|
211
152
|
uri.host = "#{bucket}.#{uri.host}"
|
212
153
|
uri
|
@@ -221,7 +162,7 @@ module RightScale
|
|
221
162
|
# @return [Object]
|
222
163
|
#
|
223
164
|
def compute_body(body, content_type)
|
224
|
-
return body if body.
|
165
|
+
return body if body.nil?
|
225
166
|
# Make sure it is a String instance
|
226
167
|
return body unless body.is_a?(Hash)
|
227
168
|
Utils::contentify_body(body, content_type)
|
@@ -243,30 +184,10 @@ module RightScale
|
|
243
184
|
# 'The request signature we calculated does not match the signature you provided.
|
244
185
|
# Check your key and signing method.'
|
245
186
|
headers.set_if_blank('content-type', 'application/octet-stream')
|
246
|
-
headers.set_if_blank('date', Time::now.utc.httpdate)
|
247
|
-
headers['content-md5'] = Base64::encode64(Digest::MD5::digest(body)).strip if !body._blank?
|
248
|
-
headers['host'] = host
|
249
187
|
headers
|
250
188
|
end
|
251
189
|
|
252
190
|
|
253
|
-
# Computes signature
|
254
|
-
#
|
255
|
-
# @param [String] access_key
|
256
|
-
# @param [String] secret_key
|
257
|
-
# @param [String] verb
|
258
|
-
# @param [String] bucket
|
259
|
-
# @param [Hash] params
|
260
|
-
# @param [Hash] headers
|
261
|
-
#
|
262
|
-
# @return [String]
|
263
|
-
#
|
264
|
-
def compute_signature(access_key, secret_key, verb, bucket, object, params, headers)
|
265
|
-
can_path = compute_canonicalized_path(bucket, object, params)
|
266
|
-
Utils::AWS::sign_s3_signature(secret_key, verb, can_path, headers)
|
267
|
-
end
|
268
|
-
|
269
|
-
|
270
191
|
# Builds request path
|
271
192
|
#
|
272
193
|
# @param [String] bucket
|
@@ -275,16 +196,26 @@ module RightScale
|
|
275
196
|
#
|
276
197
|
# @return [String]
|
277
198
|
#
|
278
|
-
def compute_path(bucket, object
|
199
|
+
def compute_path(bucket, object)
|
279
200
|
data = []
|
280
|
-
data << bucket unless
|
201
|
+
data << bucket unless is_dns_bucket?(bucket)
|
281
202
|
data << object
|
282
|
-
data << params
|
283
203
|
Utils::join_urn(*data)
|
284
204
|
end
|
285
205
|
|
206
|
+
# Returns +true+ if DNS compatible buckets are enabled (default) and the
|
207
|
+
# given bucket is DNS compatible
|
208
|
+
#
|
209
|
+
# @param [String] bucket
|
210
|
+
# @return [Boolean]
|
211
|
+
#
|
212
|
+
def is_dns_bucket?(bucket)
|
213
|
+
return false if @data[:options][:cloud][:no_dns_buckets]
|
214
|
+
Utils::AWS::is_dns_bucket?(bucket)
|
215
|
+
end
|
216
|
+
|
286
217
|
end
|
287
218
|
end
|
288
219
|
end
|
289
220
|
end
|
290
|
-
end
|
221
|
+
end
|