class Google::Auth::ExternalAccount::AwsRequestSigner

Implements an AWS request signer based on the AWS Signature Version 4 signing process. docs.aws.amazon.com/general/latest/gr/signature-version-4.html

Public Class Methods

new(region_name) click to toggle source

Instantiates an AWS request signer used to compute authenticated signed requests to AWS APIs based on the AWS Signature Version 4 signing process.

@param [string] region_name

The AWS region to use.
# File lib/googleauth/external_account/aws_credentials.rb, line 219
def initialize region_name
  @region_name = region_name
end

Public Instance Methods

generate_signed_request(aws_credentials, original_request) click to toggle source

Generates the signed request for the provided HTTP request for calling an AWS API. This follows the steps described at: docs.aws.amazon.com/general/latest/gr/sigv4_signing.html

@param [Hash[string, string]] aws_security_credentials

A dictionary containing the AWS security credentials.

@param [string] url

The AWS service URL containing the canonical URI and query string.

@param [string] method

The HTTP method used to call this API.

@return [hash{string => string}]

The AWS signed request dictionary object.
# File lib/googleauth/external_account/aws_credentials.rb, line 237
def generate_signed_request aws_credentials, original_request
  uri = Addressable::URI.parse original_request[:url]
  raise "Invalid AWS service URL" unless uri.hostname && uri.scheme == "https"
  service_name = uri.host.split(".").first

  datetime = Time.now.utc.strftime "%Y%m%dT%H%M%SZ"
  date = datetime[0, 8]

  headers = aws_headers aws_credentials, original_request, datetime

  request_payload = original_request[:data] || ""
  content_sha256 = sha256_hexdigest request_payload

  canonical_req = canonical_request original_request[:method], uri, headers, content_sha256
  sts = string_to_sign datetime, canonical_req, service_name

  # Authorization header requires everything else to be properly setup in order to be properly
  # calculated.
  headers["Authorization"] = build_authorization_header headers, sts, aws_credentials, service_name, date

  {
    url: uri.to_s,
    headers: headers,
    method: original_request[:method],
    data: (request_payload unless request_payload.empty?)
  }.compact
end

Private Instance Methods

aws_headers(aws_credentials, original_request, datetime) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 267
def aws_headers aws_credentials, original_request, datetime
  uri = Addressable::URI.parse original_request[:url]
  temp_headers = original_request[:headers] || {}
  headers = {}
  temp_headers.each_key { |k| headers[k.to_s] = temp_headers[k] }
  headers["host"] = uri.host
  headers["x-amz-date"] = datetime
  headers["x-amz-security-token"] = aws_credentials[:session_token] if aws_credentials[:session_token]
  headers
end
build_authorization_header(headers, sts, aws_credentials, service_name, date) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 278
def build_authorization_header headers, sts, aws_credentials, service_name, date
  [
    "AWS4-HMAC-SHA256",
    "Credential=#{credential aws_credentials[:access_key_id], date, service_name},",
    "SignedHeaders=#{headers.keys.sort.join ';'},",
    "Signature=#{signature aws_credentials[:secret_access_key], date, sts, service_name}"
  ].join(" ")
end
build_canonical_querystring(query) click to toggle source

Generates the canonical query string given a raw query string. Logic is based on docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html Code is from the AWS SDK for Ruby github.com/aws/aws-sdk-ruby/blob/0ac3d0a393ed216290bfb5f0383380376f6fb1f1/gems/aws-sigv4/lib/aws-sigv4/signer.rb#L532

# File lib/googleauth/external_account/aws_credentials.rb, line 357
def build_canonical_querystring query
  params = query.split "&"
  params = params.map { |p| p.include?("=") ? p : "#{p}=" }

  params.each.with_index.sort do |(a, a_offset), (b, b_offset)|
    a_name, a_value = a.split "="
    b_name, b_value = b.split "="
    if a_name == b_name
      if a_value == b_value
        a_offset <=> b_offset
      else
        a_value <=> b_value
      end
    else
      a_name <=> b_name
    end
  end.map(&:first).join("&")
end
canonical_request(http_method, uri, headers, content_sha256) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 335
def canonical_request http_method, uri, headers, content_sha256
  headers = headers.sort_by(&:first) # transforms to a sorted array of [key, value]

  [
    http_method,
    uri.path.empty? ? "/" : uri.path,
    build_canonical_querystring(uri.query || ""),
    headers.map { |k, v| "#{k}:#{v}\n" }.join, # Canonical headers
    headers.map(&:first).join(";"), # Signed headers
    content_sha256
  ].join("\n")
end
credential(access_key_id, date, service) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 304
def credential access_key_id, date, service
  "#{access_key_id}/#{credential_scope date, service}"
end
credential_scope(date, service) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 308
def credential_scope date, service
  [
    date,
    @region_name,
    service,
    "aws4_request"
  ].join("/")
end
hexhmac(key, value) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 300
def hexhmac key, value
  OpenSSL::HMAC.hexdigest OpenSSL::Digest.new("sha256"), key, value
end
hmac(key, value) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 296
def hmac key, value
  OpenSSL::HMAC.digest OpenSSL::Digest.new("sha256"), key, value
end
host(uri) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 326
def host uri
  # Handles known and unknown URI schemes; default_port nil when unknown.
  if uri.default_port == uri.port
    uri.host
  else
    "#{uri.host}:#{uri.port}"
  end
end
sha256_hexdigest(string) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 348
def sha256_hexdigest string
  OpenSSL::Digest::SHA256.hexdigest string
end
signature(secret_access_key, date, string_to_sign, service) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 287
def signature secret_access_key, date, string_to_sign, service
  k_date = hmac "AWS4#{secret_access_key}", date
  k_region = hmac k_date, @region_name
  k_service = hmac k_region, service
  k_credentials = hmac k_service, "aws4_request"

  hexhmac k_credentials, string_to_sign
end
string_to_sign(datetime, canonical_request, service) click to toggle source
# File lib/googleauth/external_account/aws_credentials.rb, line 317
def string_to_sign datetime, canonical_request, service
  [
    "AWS4-HMAC-SHA256",
    datetime,
    credential_scope(datetime[0, 8], service),
    sha256_hexdigest(canonical_request)
  ].join("\n")
end