Module: RightScale::CloudApi::Utils::AWS

Defined in:
lib/cloud/aws/base/helpers/utils.rb

Overview

AWS helpers namespace

Constant Summary collapse

@@digest1 =
OpenSSL::Digest.new("sha1")
@@digest256 =

Some installations may not support sha256

OpenSSL::Digest.new("sha256") rescue nil

Class Method Summary collapse

Class Method Details

.amz_escape(string) ⇒ String

Escapes a string accordingly to Amazon rules

Examples:

RightScale::CloudApi::Utils::AWS.amz_escape('something >= 13') #=>
  'something%20%3E%3D%2013'

Parameters:

  • string (String)

Returns:

  • (String)

See Also:



85
86
87
88
89
90
91
92
93
# File 'lib/cloud/aws/base/helpers/utils.rb', line 85

def self.amz_escape(string)
  string = string.to_s
  # Use UTF-8 if current ruby supports it (1.9+)
  string = string.encode("UTF-8") if string.respond_to?(:encode)
  # CGI::escape is too clever:
  #  - it escapes '~' when Amazon wants it to be un-escaped
  #  - it escapes ' ' as '+' but Amazon loves it as '%20'
  CGI.escape(string).gsub('%7E','~').gsub('+','%20')
end

.is_dns_bucket?(bucket_name) ⇒ Boolean

Returns true if the provided bucket name is a DNS compliant bucket name

Examples:

RightScale::CloudApi::Utils::AWS.is_dns_bucket?('my') #=> false
RightScale::CloudApi::Utils::AWS.is_dns_bucket?('my_bycket') #=> false
RightScale::CloudApi::Utils::AWS.is_dns_bucket?('my-bucket') #=> true

Parameters:

  • bucket_name (String)

Returns:

  • (Boolean)

See Also:



153
154
155
156
157
158
159
160
# File 'lib/cloud/aws/base/helpers/utils.rb', line 153

def self.is_dns_bucket?(bucket_name)
  bucket_name = bucket_name.to_s
  return false unless (3..63) === bucket_name.size
  bucket_name.split('.').each do |component|
    return false unless component[/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/]
  end
  true
end

.parametrize(data) ⇒ Hash

Parametrizes data to the format that Amazon EC2 (and compatible APIs) loves

Examples:

# Where hash is:
{ Name.?.Mask  => Value | [ Values ],
  NamePrefix.? => [{ SubNameA.1 => ValueA.1,  SubNameB.1 => ValueB.1 },   # any simple parameter
                  ..., 
                   { SubNameN.X => ValueN.X,  SubNameM.X => ValueN.X }]   # see BlockDeviceMapping case
parametrize( 'ParamA'             => 'a',
             'ParamB'             => ['b', 'c'],
             'ParamC.?.Something' => ['d', 'e'],
             'Filter'             => [ { 'Key' => 'A', 'Value' => ['aa','ab']},
                                       { 'Key' => 'B', 'Value' => ['ba','bb']}] ) #=>
  {
    "Filter.1.Key"       => "A",
    "Filter.1.Value.1"   => "aa",
    "Filter.1.Value.2"   => "ab",
    "Filter.2.Key"       => "B",
    "Filter.2.Value.1"   => "ba",
    "Filter.2.Value.2"   => "bb",
    "ParamA"             => "a",
    "ParamB.1"           => "b",
    "ParamB.2"           => "c",
    "ParamC.1.Something" => "d",
    "ParamC.2.Something" => "e"
  }
# BlockDeviceMapping example
parametrize( 'ImageId'  => 'i-01234567',
             'MinCount' => 1,
             'MaxCount' => 2,
             'KeyName'  => 'my-key',
             'SecurityGroupId' => ['sg-01234567', 'sg-12345670', 'sg-23456701'],
             'BlockDeviceMapping' => [ 
                { 'DeviceName'     => '/dev/sda1', 
                  'Ebs.SnapshotId' => 'snap-01234567', 
                  'Ebs.VolumeSize' => 20,
                  'Ebs.DeleteOnTermination' => true },
                { 'DeviceName'     => '/dev/sdb1', 
                  'Ebs.SnapshotId' => 'snap-12345670', 
                  'Ebs.VolumeSize' => 10,
                  'Ebs.DeleteOnTermination' => false } ] ) #=> 
  {
    "BlockDeviceMapping.1.DeviceName"              => "/dev/sda1",
    "BlockDeviceMapping.1.Ebs.DeleteOnTermination" => true,
    "BlockDeviceMapping.1.Ebs.SnapshotId"          => "snap-01234567",
    "BlockDeviceMapping.1.Ebs.VolumeSize"          => 20,
    "BlockDeviceMapping.2.DeviceName"              => "/dev/sdb1",
    "BlockDeviceMapping.2.Ebs.DeleteOnTermination" => false,
    "BlockDeviceMapping.2.Ebs.SnapshotId"          => "snap-12345670",
    "BlockDeviceMapping.2.Ebs.VolumeSize"          => 10,
    "ImageId"                                      => "i-01234567",
    "KeyName"                                      => "my-key",
    "MaxCount"                                     => 2,
    "MinCount"                                     => 1,
    "SecurityGroupId.1"                            => "sg-01234567",
    "SecurityGroupId.2"                            => "sg-12345670",
    "SecurityGroupId.3"                            => "sg-23456701"
  }          
parametrize( 'DomainName' => 'kdclient',
             'Item' => [ { 'ItemName'  => 'konstantin',
                           'Attribute' => [ { 'Name' => 'sex',    'Value' => 'male' },
                                            { 'Name' => 'age',    'Value' => '38'} ] },
                         { 'ItemName'  => 'alex',
                           'Attribute' => [ { 'Name' => 'sex',    'Value' => 'male' },
                                            { 'Name' => 'weight', 'Value' => '188'},
                                            { 'Name' => 'age',    'Value' => '42'} ] },
                         { 'ItemName'  => 'diana',
                           'Attribute' => [ { 'Name' => 'sex',    'Value' => 'female' },
                                            { 'Name' => 'weight', 'Value' => '120'},
                                            { 'Name' => 'age',    'Value' => '25'} ] } ] ) #=> 
  { "DomainName"               => "kdclient",
    "Item.1.ItemName"          => "konstantin",
    "Item.1.Attribute.1.Name"  => "sex",
    "Item.1.Attribute.1.Value" => "male",
    "Item.1.Attribute.2.Name"  => "weight",
    "Item.1.Attribute.2.Value" => "170",
    "Item.1.Attribute.3.Name"  => "age",
    "Item.1.Attribute.3.Value" => "38",
    "Item.2.ItemName"          => "alex",
    "Item.2.Attribute.1.Name"  => "sex",
    "Item.2.Attribute.1.Value" => "male",
    "Item.2.Attribute.2.Name"  => "weight",
    "Item.2.Attribute.2.Value" => "188",
    "Item.2.Attribute.3.Name"  => "age",
    "Item.2.Attribute.3.Value" => "42",
    "Item.3.ItemName"          => "diana",
    "Item.3.Attribute.1.Name"  => "sex",
    "Item.3.Attribute.1.Value" => "female",
    "Item.3.Attribute.2.Name"  => "weight",
    "Item.3.Attribute.2.Value" => "120",
    "Item.3.Attribute.3.Name"  => "age",
    "Item.3.Attribute.3.Value" => "25"}

Parameters:

  • data (Hash)

Returns:

  • (Hash)


303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/cloud/aws/base/helpers/utils.rb', line 303

def self.parametrize(data)
  return data unless data.is_a?(Hash)
  result = {}
  #
  data.each do |mask, values|
    current_values = Utils::arrayify(values)
    current_mask   = mask.dup.to_s
    current_mask  << ".?" unless current_mask[/\?/] if current_values.size > 1
    #
    current_values.dup.each_with_index do |value, idx|
      key  = current_mask.sub('?', (idx+1).to_s)
      item = parametrize(value)
      if item.is_a?(Hash)
        item.each{ |k, v| result["#{key}.#{k}"] = v }
      else
        result[key] = item
      end
    end
  end
  result
end

.sign(aws_secret_access_key, auth_string, digest = nil) ⇒ String

Generates a signature for the given string, secret access key and digest

Examples:

RightScale::CloudApi::Utils::AWS.sign('my-secret-key', 'something-that-needs-to-be-signed') #=>
  'kdHo0Ks4KkypU1CkYZzAxFIIX+0='

Parameters:

  • aws_secret_access_key (String)
  • auth_string (String)
  • digest (String) (defaults to: nil)

Returns:

  • (String)

    The signature.



50
51
52
# File 'lib/cloud/aws/base/helpers/utils.rb', line 50

def self.sign(aws_secret_access_key, auth_string, digest=nil)
  Utils::base64en(OpenSSL::HMAC.digest(digest || @@digest1, aws_secret_access_key, auth_string))
end

.sign_s3_signature(aws_secret_access_key, verb, canonicalized_resource, _headers = {}) ⇒ String

Signs and Authenticates REST Requests

Examples:

sign_s3_signature('secret', :get, 'xxx/yyy/zzz/object', {'header'=>'value'}) #=>
  "i85igH0sftHD/cGZcLiBKcYEuks=" 

Parameters:

  • aws_secret_access_key (String)
  • verb (String, Symbol)

    ‘get’ | ‘post’

  • canonicalized_resource (String)
  • _headers (Hash) (defaults to: {})

Returns:

  • (String)

See Also:



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/cloud/aws/base/helpers/utils.rb', line 178

def self.sign_s3_signature(aws_secret_access_key, verb, canonicalized_resource, _headers={})
  headers = {}
  # Make sure all our headers ara downcased
  _headers.each do |key, value|
    headers[key.to_s.downcase] = value.is_a?(Array) ? value.join(',') : value
  end
  content_md5                 = headers['content-md5']
  content_type                = headers['content-type']
  date                        = headers['x-amz-date'] || headers['date'] || headers['expires']
  canonicalized_x_amz_headers = headers.select{|key, value|  key[/^x-amz-/]}.sort.map{|key, value| "#{key}:#{value}"}.join("\n")
  canonicalized_x_amz_headers << "\n" unless canonicalized_x_amz_headers._blank?
  # StringToSign
  string_to_sign = "#{verb.to_s.upcase}\n"         +
                   "#{content_md5}\n"              +
                   "#{content_type}\n"             +
                   "#{date}\n"                     +
                   "#{canonicalized_x_amz_headers}"+
                   "#{canonicalized_resource}"
  sign(aws_secret_access_key, string_to_sign)
end

.sign_v2_signature(aws_secret_access_key, params, verb, host, urn) ⇒ String

Signature Version 2

EC2, SQS and SDB requests must be signed by this guy

Examples:

params = {'InstanceId' => 'i-00000000'}
sign_v2_signature('secret', params, :get, 'ec2.amazonaws.com', '/') #=>
  "InstanceId=i-00000000&SignatureMethod=HmacSHA256&SignatureVersion=2&
   Timestamp=2014-03-12T21%3A52%3A21.000Z&Signature=gR2x3oWmNbh4bdZksPS
   sg3t7U0zbTcnFOfizWF3Zujw%3D"

Parameters:

  • aws_secret_access_key (String)
  • params (Hash)
  • verb (String, Symbol)

    ‘get’ | ‘post’

  • host (String)
  • urn (String)

Returns:

  • (String)

See Also:



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/cloud/aws/base/helpers/utils.rb', line 118

def self.sign_v2_signature(aws_secret_access_key, params, verb, host, urn)
  params["Timestamp"]      ||= utc_iso8601(Time.now) unless params["Expires"]
  params["SignatureVersion"] = '2'
  # select a signing method (make an old openssl working with sha1)
  # make 'HmacSHA256' to be a default one
  params['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(params['SignatureMethod'])
  params['SignatureMethod'] = 'HmacSHA1'   unless @@digest256
  # select a digest
  digest = (params['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
  # form string to sign
  canonical_string = Utils::params_to_urn(params){ |value| amz_escape(value) }
  string_to_sign = "#{verb.to_s.upcase}\n" +
                   "#{host.downcase}\n"    +
                   "#{urn}\n"              +
                   "#{canonical_string}"
  "#{canonical_string}&Signature=#{amz_escape(sign(aws_secret_access_key, string_to_sign, digest))}"
end

.utc_iso8601(time) ⇒ String

Returns ISO-8601 representation for the given time

Examples:

RightScale::CloudApi::Utils::AWS.utc_iso8601(Time.now) #=> '2013-03-22T21:00:21.000Z'
RightScale::CloudApi::Utils::AWS.utc_iso8601(0) #=> '1970-01-01T00:00:00.000Z'

Parameters:

  • time (Time, Fixnum)

Returns:

  • (String)


66
67
68
69
70
71
72
# File 'lib/cloud/aws/base/helpers/utils.rb', line 66

def self.utc_iso8601(time)
  case
  when time.is_a?(Fixnum) then Time::at(time)
  when time.is_a?(String) then Time::parse(time)
  else                         time
  end.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
end