Search code examples
rubyapiamazon-web-servicescloudfog

Getting the list of reserved DB RDS instances from aws by doing an API call


I was trying to get the list of Reserved DB instances from the RDS amazon web services part using the ruby libraries present and return this data, mainly with the fog library. I've noticed though that they don't return this data unfortunately, thus I went ahead and started investigating.

I've found out that when using signature version 4 this data is being returned, found it when using the rds cli(tool provided by AWS), while the fog library uses signature version 2 for making requests. This lead me to start developing a simple solution that would return the RDS reserved instances using a ruby script, but due to the small amount of documentation I am currently stuck here. At this point the workaround is to call the rds cli script but it's a bad option.

Also spent some time googling for a ready solution(could be in any language) for the case but couldn't find any. The question is thus, does anyone have a ready solution written preferably in ruby that uses signature version 4 to make API calls to AWS?


Solution

  • After some time we managed to build a class with all the needed functionality that would return the reserved rds instances for aws using signature version 4 for signing the request. Here is the code:

    #!/usr/local/bin/ruby
    
    require 'rubygems'
    require 'net/http'
    require 'net/https'
    require 'time'
    require 'hmac'
    require 'hmac-sha2'
    require 'base64'
    require 'cgi'
    
    class AWSGetSignatureV4
     def initialize(aws_key,aws_secretpwd)
      @regions=['ap-northeast-1', 'ap-southeast-1', 'eu-west-1', 'us-east-1', 'us-west-1', 'us-west-2', 'sa-east-1']
      @rds_list={}
      @inst_list={}
      @rds_reserves={}
      @inst_reserves={}
      @aws_key=aws_key
      @aws_secret=aws_secretpwd
      @canonical_uri="/\n"
      @request_type="GET\n"
      @request_version='2012-04-23'
      @request_headers={
       'Host' => ''
      }
     end
    
     def form_request(requestname, zone)
      canonical_request_full(requestname, zone)
      form_string_to_sign(zone)
      form_signature(requestname, zone)
      form_request_url(requestname, zone)
     end
    
     def get_data(requestname, zone)
      form_request(requestname, zone)
      http = Net::HTTP.new(@https_addr, "443")
      http.use_ssl = true
      headers = { 'Host' => "#{@https_addr}" }
      @request_data=""
      retval = http.get(@url_to_use, headers) do |chunk|
       @request_data+=chunk
      end
      puts(retval.code)
      puts(@request_data)
     end
    
     def get_service_type(requestname)
      if requestname == 'DescribeReservedDBInstances'
       @service_type="rds"
      else
       raise "No such request type."
      end
     end
    
     def form_time_values()
      @timenowz=Time.now.utc.iso8601
      @time_use_now=@timenowz.gsub(/-|:/, '')
      @date_to_use=@time_use_now.gsub(/T.*$/,'')
     end
    
     def init_param_values(requestname)
      @init_params = {
       'Action' => requestname,
       'Version' => @request_version
      }
     end
    
     def other_param_values(zone)
      @other_params = {
       'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256',
       'X-Amz-Credential' => @aws_key+"/#{@date_to_use}/#{zone}/#{@service_type}/aws4_request",
       'X-Amz-Date' => @time_use_now,
       'X-Amz-SignedHeaders' => 'Host'
      }
     end
    
     def form_canonical_query_string(requestname, zone)
      @querystringz = @init_params.sort.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')+"&"+@other_params.sort.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
     end
    
     def modify_request_headers(requestname, zone)
      @request_headers['Host']="#{@service_type}.#{zone}.amazonaws.com"
     end
    
     def form_headers()
      @queryheaderz = "host:#{@request_headers['Host']}"
      @signed_headerz =@request_headers.sort.collect { |key, value| key.to_s.downcase }.join(';')
      @canonical_headerz =@request_headers.sort.collect { |key, value| [CGI.escape(key.to_s.downcase), CGI.escape(value.to_s)].join(':') }.join("\n")
     end
    
     def form_payload_data()
      @payload=@init_params.sort.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
      @hex_sign_string=Digest::SHA256.digest("").unpack('H*').first
      if @request_type == "GET\n"
       @hex_sign_string=Digest::SHA256.digest("").unpack('H*').first
      elsif @request_type == "POST\n"
       @hex_sign_string=Digest::SHA256.digest(@payload).unpack('H*').first
      end
     end
    
     def canonical_request_full(requestname, zone)
      form_time_values()
      get_service_type(requestname)
      init_param_values(requestname)
      other_param_values(zone)
      modify_request_headers(requestname, zone)
      form_canonical_query_string(requestname, zone)
      form_headers()
      form_payload_data()
      @canonical_request=@request_type+@canonical_uri+@querystringz+"\n"+@canonical_headerz+"\n\n"+@signed_headerz+"\n"+@hex_sign_string
     end
    
     def form_string_to_sign(zone)
      hex_sign_sts=Digest::SHA256.digest(@canonical_request).unpack('H*').first
      @string_to_sign="#{@other_params['X-Amz-Algorithm']}\n#{@other_params['X-Amz-Date']}\n#{@date_to_use}/#{zone}/#{@service_type}/aws4_request\n#{hex_sign_sts}"
     end
    
     def form_signature(requestname, zone)
      @kdatez    = OpenSSL::HMAC.digest('sha256', "AWS4" + @aws_secret, @date_to_use)
      @kregionz  = OpenSSL::HMAC.digest('sha256', @kdatez, zone)
      @kservicez = OpenSSL::HMAC.digest('sha256', @kregionz, "#{@service_type}")
      @ksigningz = OpenSSL::HMAC.digest('sha256', @kservicez, "aws4_request")
      @signaturez = OpenSSL::HMAC.digest('sha256', @ksigningz, @string_to_sign)
      @other_params['X-Amz-Signature']=@signaturez.unpack('H*').first
     end
    
     def form_request_url(requestname, zone)
      @url_to_use = @init_params.sort.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')+"&"+@other_params.sort.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
      if requestname == 'DescribeReservedDBInstances'
       @url_to_use="/?"+@url_to_use
       @https_addr="#{@service_type}.#{zone}.amazonaws.com"
       @url_to_use_full="https://#{@service_type}.#{zone}.amazonaws.com/?"+@url_to_use
      end
     end
    
    end
    
    billing_obj=AWSGetSignatureV4.new("AWS_KEY","AWS_SECRET")
    billing_obj.get_data("DescribeReservedDBInstances", 'us-east-1')