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?
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')