Signs requests using the AWS sigv4 signing.
Public Class methods
new( service:, region:, credentials: nil, username: nil, password: nil, security_token: nil, provider_prefix: "aws", header_provider_field: "amz", unsigned_headers: [], apply_checksum_header: true, algorithm: "SHA256" )
[show source]
# File lib/httpx/plugins/aws_sigv4.rb 17 def initialize( 18 service:, 19 region:, 20 credentials: nil, 21 username: nil, 22 password: nil, 23 security_token: nil, 24 provider_prefix: "aws", 25 header_provider_field: "amz", 26 unsigned_headers: [], 27 apply_checksum_header: true, 28 algorithm: "SHA256" 29 ) 30 @credentials = credentials || Credentials.new(username, password, security_token) 31 @service = service 32 @region = region 33 34 @unsigned_headers = Set.new(unsigned_headers.map(&:downcase)) 35 @unsigned_headers << "authorization" 36 @unsigned_headers << "x-amzn-trace-id" 37 @unsigned_headers << "expect" 38 39 @apply_checksum_header = apply_checksum_header 40 @provider_prefix = provider_prefix 41 @header_provider_field = header_provider_field 42 43 @algorithm = algorithm 44 end
Public Instance methods
sign!(request)
[show source]
# File lib/httpx/plugins/aws_sigv4.rb 46 def sign!(request) 47 lower_provider_prefix = "#{@provider_prefix}4" 48 upper_provider_prefix = lower_provider_prefix.upcase 49 50 downcased_algorithm = @algorithm.downcase 51 52 datetime = (request.headers["x-#{@header_provider_field}-date"] ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ")) 53 date = datetime[0, 8] 54 55 content_hashed = request.headers["x-#{@header_provider_field}-content-#{downcased_algorithm}"] || hexdigest(request.body) 56 57 request.headers["x-#{@header_provider_field}-content-#{downcased_algorithm}"] ||= content_hashed if @apply_checksum_header 58 request.headers["x-#{@header_provider_field}-security-token"] ||= @credentials.security_token if @credentials.security_token 59 60 signature_headers = request.headers.each.reject do |k, _| 61 @unsigned_headers.include?(k) 62 end 63 # aws sigv4 needs to declare the host, regardless of protocol version 64 signature_headers << ["host", request.authority] unless request.headers.key?("host") 65 signature_headers.sort_by!(&:first) 66 67 signed_headers = signature_headers.map(&:first).join(";") 68 69 canonical_headers = signature_headers.map do |k, v| 70 # eliminate whitespace between value fields, unless it's a quoted value 71 "#{k}:#{v.start_with?("\"") && v.end_with?("\"") ? v : v.gsub(/\s+/, " ").strip}\n" 72 end.join 73 74 # canonical request 75 creq = "#{request.verb}" \ 76 "\n#{request.canonical_path}" \ 77 "\n#{request.canonical_query}" \ 78 "\n#{canonical_headers}" \ 79 "\n#{signed_headers}" \ 80 "\n#{content_hashed}" 81 82 credential_scope = "#{date}" \ 83 "/#{@region}" \ 84 "/#{@service}" \ 85 "/#{lower_provider_prefix}_request" 86 87 algo_line = "#{upper_provider_prefix}-HMAC-#{@algorithm}" 88 # string to sign 89 sts = "#{algo_line}" \ 90 "\n#{datetime}" \ 91 "\n#{credential_scope}" \ 92 "\n#{OpenSSL::Digest.new(@algorithm).hexdigest(creq)}" 93 94 # signature 95 k_date = hmac("#{upper_provider_prefix}#{@credentials.password}", date) 96 k_region = hmac(k_date, @region) 97 k_service = hmac(k_region, @service) 98 k_credentials = hmac(k_service, "#{lower_provider_prefix}_request") 99 sig = hexhmac(k_credentials, sts) 100 101 credential = "#{@credentials.username}/#{credential_scope}" 102 # apply signature 103 request.headers["authorization"] = 104 "#{algo_line} " \ 105 "Credential=#{credential}, " \ 106 "SignedHeaders=#{signed_headers}, " \ 107 "Signature=#{sig}" 108 end