AWS4 request signing with Ruby -
i have codebase work aws cloudfront in limited capacity , trying status of distribution invalidation through api endpoint documented here: http://docs.aws.amazon.com/cloudfront/latest/apireference/api_getinvalidation.html
this endpoint requires request headers signed using aws4 signature, i'm stuggling with. i've modeled of request signing code based on signing code in aws sdk ruby: https://github.com/aws/aws-sdk-ruby/blob/master/aws-sdk-core/lib/aws-sdk-core/signers/v4.rb
my code follows:
class cfagent rfc8601basic = "%y%m%dt%h%m%sz" cf_agent_access_key = "validaccesskey" cf_agent_secret_access_key = "validsecret" def self.cf_dist_invalidation_status dist_id = "abc123" invalidation_id = "xyz" url = "https://cloudfront.amazonaws.com/2017-03-25/#{dist_id}/invalidation/#{invalidation_id}" headers = { "content-type" => "application/json; charset=utf8" } headers = sign("get", uri.parse(url), headers, "{}", cf_agent_access_key, cf_agent_secret_access_key, "us-east-1") puts "signed headers:", headers res = simplehttp.get(url, headers) puts "status:", res.code puts "body:", res.body end def self.sign(method, uri, headers, body, access_key, secret_key, region, service_name=nil) method = method.upcase service = service_name || uri.host.split(".", 2)[0] date_header = headers["date"] || headers["date"] || headers["date"] date = (date_header ? time.parse(date_header) : time.zone.now).utc.strftime(rfc8601basic) body_digest = hexdigest(body) headers['x-amz-date'] = date headers['host'] = host(uri) headers['x-amz-content-sha256'] ||= body_digest headers['authorization'] = authorization(method, uri, headers, body_digest, date, access_key, secret_key, region, service) headers end private def self.host(uri) if ((uri.scheme == 'http' && uri.port == 80) || (uri.scheme == 'https' && uri.port = 443)) uri.host else "#{uri.host}:#{uri.port}" end end def self.authorization(method, uri, headers, body_digest, date, access_key, secret_key, region, service) [ "aws4-hmac-sha256 credential=#{credential(access_key, date, region, service)}", "signedheaders=#{signed_headers(headers)}", "signature=#{signature(method, uri, headers, body_digest, date, access_key, secret_key, region, service)}" ].join(', ') end def self.credential(access_key, date, region, service) "#{access_key}/#{credential_string(date, region, service)}" end def self.signature(method, uri, headers, body_digest, date, access_key, secret_key, region, service) k_date = hmac("aws4" + secret_key, date[0,8]) k_region = hmac(k_date, region) k_service = hmac(k_region, service) k_credentials = hmac(k_service, "aws4_request") hexhmac(k_credentials, string_to_sign(method, uri, headers, body_digest, date, access_key, secret_key, region, service)) end def self.string_to_sign(method, uri, headers, body_digest, date, access_key, secret_key, region, service) [ 'aws4-hmac-sha256', date, credential_string(date, region, service), hexdigest(canonical_request(method, uri, headers, body_digest)) ].join("\n") end def self.credential_string(date, region, service) [ date[0,8], region, service, "aws4_request" ].join("/") end def self.canonical_request(method, uri, headers, body_digest) [ method, pathname.new(uri.path).cleanpath.to_s, uri.query, canonical_headers(headers), signed_headers(headers), body_digest ].join("\n") end def self.canonical_headers(headers) c_headers = [] headers.each_pair |k,v| k = k.downcase c_headers << [k,v] end c_headers = c_headers.sort_by(&:first) c_headers.map{|k,v| "#{k}:#{canonical_header_value(v.to_s)}" }.join("\n") end def self.canonical_header_value(value) value.match(/^".*"$/) ? value : value.gsub(/\s+/, ' ').strip end def self.signed_headers(headers) headers.keys.inject([]) |signed_headers, header_key| header_key = header_key.downcase signed_headers << header_key signed_headers end.sort.join(';') end def self.hexdigest(value) digest::sha256.new.update(value).hexdigest end def self.hmac(key, value) openssl::hmac.digest(openssl::digest.new('sha256'), key, value) end def self.hexhmac(key, value) openssl::hmac.hexdigest(openssl::digest.new('sha256'), key, value) end end
when call cfagent.cfinvalidation_status, receive 403 error "missinginvalidationtoken" code. "simplehttp" simple library wraps net/http make http/https requests (please assume works).
what missing?
thanks in advance.
Comments
Post a Comment