Skip to content

Instantly share code, notes, and snippets.

@dongoclam
Last active March 8, 2022 07:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dongoclam/e36f6560124460c5c932b6a84afbd683 to your computer and use it in GitHub Desktop.
Save dongoclam/e36f6560124460c5c932b6a84afbd683 to your computer and use it in GitHub Desktop.
Examples of the Complete Version 4 Signing Process
require "faraday"
class CloudSearchService
METHOD = "GET"
SHA256 = "sha256"
ALGORITHM = "AWS4-HMAC-SHA256"
ENDPOINT = "iam.us-east-1.amazonaws.com"
CANONICAL_URI = "/"
REGION = "us-east-1"
SERVICE = "cloudsearch"
AWS4_REQUEST = "aws4_request"
SIGNED_HEADERS = "host;x-amz-date"
DATE_TIME_FORMAT = "%Y%m%dT%H%M%SZ"
DATE_FORMAT = "%Y%m%d"
def initialize
@amzdate = Time.now.utc.strftime DATE_TIME_FORMAT
@datestamp = amzdate.to_date.strftime DATE_FORMAT
@credential_scope = [datestamp, REGION, SERVICE, AWS4_REQUEST].join("/")
@host = Resolv::DNS.new.getresource(RESOURCE, Resolv::DNS::Resource::IN::CNAME).name.to_s
end
def perform
return parse_data if data_present?
create_error_log
false
rescue Faraday::ConnectionFailed, ActiveSupport::JSON.parse_error
Rails.logger.info "#{self.class}: Connect to api failed"
false
end
private
attr_reader :amzdate, :datestamp, :credential_scope, :host
def parse_data
response_body["hits"]["hit"].map{|resource| resource["fields"].symbolize_keys}
end
def data_present?
response_body && response_body["error"].blank?
end
def response_body
@response_body ||= JSON.parse response.body if response.present?
end
def create_error_log
if response_body && response_body["error"].present?
Rails.logger.info "#{self.class}: #{response_body["error"]["message"]}"
else
Rails.logger.info "#{self.class}: Client error"
end
end
def response
@response ||= faraday_connect.get do |request|
request.headers["X-Amz-Date"] = amzdate
request.headers["Authorization"] = auth_header
end
end
def faraday_connect
Faraday.new url: request_url do |faraday|
faraday.request :url_encoded
faraday.adapter Faraday.default_adapter
end
end
def request_url
"https://#{host}#{CANONICAL_URI}?#{request_parameters}"
end
def auth_header
"#{ALGORITHM} Credential=#{ENV["API_ACCESS_KEY"]}/#{credential_scope},
SignedHeaders=#{SIGNED_HEADERS}, Signature=#{signature}"
end
def signature
string_to_sign = [ALGORITHM, amzdate, credential_scope, request_hash].join("\n")
OpenSSL::HMAC.hexdigest SHA256, signature_key, string_to_sign
end
def signature_key
k_date = OpenSSL::HMAC.digest SHA256, "AWS4#{ENV["API_SECRET_KEY"]}", datestamp
k_region = OpenSSL::HMAC.digest SHA256, k_date, REGION
k_service = OpenSSL::HMAC.digest SHA256, k_region, SERVICE
OpenSSL::HMAC.digest SHA256, k_service, AWS4_REQUEST
end
def request_hash
canonical_headers = "host:#{host}\nx-amz-date:#{amzdate}\n"
payload_hash = Digest::SHA256.hexdigest("")
canonical_request = [METHOD, CANONICAL_URI, request_parameters,
canonical_headers, SIGNED_HEADERS, payload_hash].join("\n")
Digest::SHA256.hexdigest canonical_request
end
def request_parameters
[
{"Action": "DescribeRegions"},
{"Version": "2013-10-15"}
].map(&:to_query).join("&")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment