googleauth 1.2.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +99 -0
 - data/README.md +49 -8
 - data/lib/googleauth/application_default.rb +5 -9
 - data/lib/googleauth/base_client.rb +80 -0
 - data/lib/googleauth/client_id.rb +25 -8
 - data/lib/googleauth/compute_engine.rb +65 -35
 - data/lib/googleauth/credentials.rb +32 -30
 - data/lib/googleauth/credentials_loader.rb +6 -14
 - data/lib/googleauth/default_credentials.rb +5 -2
 - data/lib/googleauth/external_account/aws_credentials.rb +378 -0
 - data/lib/googleauth/external_account/base_credentials.rb +159 -0
 - data/lib/googleauth/external_account/external_account_utils.rb +103 -0
 - data/lib/googleauth/external_account/identity_pool_credentials.rb +118 -0
 - data/lib/googleauth/external_account/pluggable_credentials.rb +156 -0
 - data/lib/googleauth/external_account.rb +94 -0
 - data/lib/googleauth/helpers/connection.rb +35 -0
 - data/lib/googleauth/id_tokens/key_sources.rb +9 -10
 - data/lib/googleauth/id_tokens.rb +2 -2
 - data/lib/googleauth/json_key_reader.rb +2 -1
 - data/lib/googleauth/oauth2/sts_client.rb +109 -0
 - data/lib/googleauth/scope_util.rb +35 -2
 - data/lib/googleauth/service_account.rb +25 -11
 - data/lib/googleauth/signet.rb +14 -38
 - data/lib/googleauth/user_authorizer.rb +66 -9
 - data/lib/googleauth/user_refresh.rb +4 -2
 - data/lib/googleauth/version.rb +1 -1
 - data/lib/googleauth/web_user_authorizer.rb +19 -8
 - metadata +29 -20
 
| 
         @@ -259,7 +259,7 @@ module Google 
     | 
|
| 
       259 
259 
     | 
    
         
             
                  # @return [Object] The value
         
     | 
| 
       260 
260 
     | 
    
         
             
                  #
         
     | 
| 
       261 
261 
     | 
    
         
             
                  def self.lookup_auth_param name, method_name = name
         
     | 
| 
       262 
     | 
    
         
            -
                    val = instance_variable_get "@#{name}" 
     | 
| 
      
 262 
     | 
    
         
            +
                    val = instance_variable_get :"@#{name}"
         
     | 
| 
       263 
263 
     | 
    
         
             
                    val = yield if val.nil? && block_given?
         
     | 
| 
       264 
264 
     | 
    
         
             
                    return val unless val.nil?
         
     | 
| 
       265 
265 
     | 
    
         
             
                    return superclass.send method_name if superclass.respond_to? method_name
         
     | 
| 
         @@ -328,9 +328,13 @@ module Google 
     | 
|
| 
       328 
328 
     | 
    
         
             
                  #   @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
         
     | 
| 
       329 
329 
     | 
    
         
             
                  #     suitable for passing as a closure.
         
     | 
| 
       330 
330 
     | 
    
         
             
                  #
         
     | 
| 
      
 331 
     | 
    
         
            +
                  # @!attribute [rw] universe_domain
         
     | 
| 
      
 332 
     | 
    
         
            +
                  #   @return [String] The universe domain issuing these credentials.
         
     | 
| 
      
 333 
     | 
    
         
            +
                  #
         
     | 
| 
       331 
334 
     | 
    
         
             
                  def_delegators :@client,
         
     | 
| 
       332 
335 
     | 
    
         
             
                                 :token_credential_uri, :audience,
         
     | 
| 
       333 
     | 
    
         
            -
                                 :scope, :issuer, :signing_key, :updater_proc, :target_audience
         
     | 
| 
      
 336 
     | 
    
         
            +
                                 :scope, :issuer, :signing_key, :updater_proc, :target_audience,
         
     | 
| 
      
 337 
     | 
    
         
            +
                                 :universe_domain, :universe_domain=
         
     | 
| 
       334 
338 
     | 
    
         | 
| 
       335 
339 
     | 
    
         
             
                  ##
         
     | 
| 
       336 
340 
     | 
    
         
             
                  # Creates a new Credentials instance with the provided auth credentials, and with the default
         
     | 
| 
         @@ -352,17 +356,17 @@ module Google 
     | 
|
| 
       352 
356 
     | 
    
         
             
                  #
         
     | 
| 
       353 
357 
     | 
    
         
             
                  def initialize keyfile, options = {}
         
     | 
| 
       354 
358 
     | 
    
         
             
                    verify_keyfile_provided! keyfile
         
     | 
| 
       355 
     | 
    
         
            -
                     
     | 
| 
       356 
     | 
    
         
            -
                    @ 
     | 
| 
      
 359 
     | 
    
         
            +
                    options = symbolize_hash_keys options
         
     | 
| 
      
 360 
     | 
    
         
            +
                    @project_id = options[:project_id] || options[:project]
         
     | 
| 
      
 361 
     | 
    
         
            +
                    @quota_project_id = options[:quota_project_id]
         
     | 
| 
       357 
362 
     | 
    
         
             
                    case keyfile
         
     | 
| 
       358 
     | 
    
         
            -
                    when  
     | 
| 
      
 363 
     | 
    
         
            +
                    when Google::Auth::BaseClient
         
     | 
| 
       359 
364 
     | 
    
         
             
                      update_from_signet keyfile
         
     | 
| 
       360 
365 
     | 
    
         
             
                    when Hash
         
     | 
| 
       361 
366 
     | 
    
         
             
                      update_from_hash keyfile, options
         
     | 
| 
       362 
367 
     | 
    
         
             
                    else
         
     | 
| 
       363 
368 
     | 
    
         
             
                      update_from_filepath keyfile, options
         
     | 
| 
       364 
369 
     | 
    
         
             
                    end
         
     | 
| 
       365 
     | 
    
         
            -
                    CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
         
     | 
| 
       366 
370 
     | 
    
         
             
                    @project_id ||= CredentialsLoader.load_gcloud_project_id
         
     | 
| 
       367 
371 
     | 
    
         
             
                    @client.fetch_access_token! if @client.needs_access_token?
         
     | 
| 
       368 
372 
     | 
    
         
             
                    @env_vars = nil
         
     | 
| 
         @@ -481,10 +485,11 @@ module Google 
     | 
|
| 
       481 
485 
     | 
    
         
             
                  end
         
     | 
| 
       482 
486 
     | 
    
         | 
| 
       483 
487 
     | 
    
         
             
                  # Initializes the Signet client.
         
     | 
| 
       484 
     | 
    
         
            -
                  def init_client  
     | 
| 
       485 
     | 
    
         
            -
                     
     | 
| 
       486 
     | 
    
         
            -
                     
     | 
| 
       487 
     | 
    
         
            -
             
     | 
| 
      
 488 
     | 
    
         
            +
                  def init_client hash, options = {}
         
     | 
| 
      
 489 
     | 
    
         
            +
                    options = update_client_options options
         
     | 
| 
      
 490 
     | 
    
         
            +
                    io = StringIO.new JSON.generate hash
         
     | 
| 
      
 491 
     | 
    
         
            +
                    options.merge! json_key_io: io
         
     | 
| 
      
 492 
     | 
    
         
            +
                    Google::Auth::DefaultCredentials.make_creds options
         
     | 
| 
       488 
493 
     | 
    
         
             
                  end
         
     | 
| 
       489 
494 
     | 
    
         | 
| 
       490 
495 
     | 
    
         
             
                  # returns a new Hash with string keys instead of symbol keys.
         
     | 
| 
         @@ -492,31 +497,28 @@ module Google 
     | 
|
| 
       492 
497 
     | 
    
         
             
                    hash.to_h.transform_keys(&:to_s)
         
     | 
| 
       493 
498 
     | 
    
         
             
                  end
         
     | 
| 
       494 
499 
     | 
    
         | 
| 
       495 
     | 
    
         
            -
                  #  
     | 
| 
      
 500 
     | 
    
         
            +
                  # returns a new Hash with symbol keys instead of string keys.
         
     | 
| 
      
 501 
     | 
    
         
            +
                  def symbolize_hash_keys hash
         
     | 
| 
      
 502 
     | 
    
         
            +
                    hash.to_h.transform_keys(&:to_sym)
         
     | 
| 
      
 503 
     | 
    
         
            +
                  end
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
      
 505 
     | 
    
         
            +
                  def update_client_options options
         
     | 
| 
      
 506 
     | 
    
         
            +
                    options = options.dup
         
     | 
| 
       496 
507 
     | 
    
         | 
| 
       497 
     | 
    
         
            -
             
     | 
| 
       498 
     | 
    
         
            -
                     
     | 
| 
       499 
     | 
    
         
            -
                    options[ 
     | 
| 
       500 
     | 
    
         
            -
                    options[ 
     | 
| 
       501 
     | 
    
         
            -
                    options[ 
     | 
| 
       502 
     | 
    
         
            -
                    options["target_audience"] ||= self.class.target_audience
         
     | 
| 
      
 508 
     | 
    
         
            +
                    # options have higher priority over constructor defaults
         
     | 
| 
      
 509 
     | 
    
         
            +
                    options[:token_credential_uri] ||= self.class.token_credential_uri
         
     | 
| 
      
 510 
     | 
    
         
            +
                    options[:audience] ||= self.class.audience
         
     | 
| 
      
 511 
     | 
    
         
            +
                    options[:scope] ||= self.class.scope
         
     | 
| 
      
 512 
     | 
    
         
            +
                    options[:target_audience] ||= self.class.target_audience
         
     | 
| 
       503 
513 
     | 
    
         | 
| 
       504 
     | 
    
         
            -
                    if !Array(options[ 
     | 
| 
      
 514 
     | 
    
         
            +
                    if !Array(options[:scope]).empty? && options[:target_audience]
         
     | 
| 
       505 
515 
     | 
    
         
             
                      raise ArgumentError, "Cannot specify both scope and target_audience"
         
     | 
| 
       506 
516 
     | 
    
         
             
                    end
         
     | 
| 
      
 517 
     | 
    
         
            +
                    options.delete :scope unless options[:target_audience].nil?
         
     | 
| 
       507 
518 
     | 
    
         | 
| 
       508 
     | 
    
         
            -
                     
     | 
| 
       509 
     | 
    
         
            -
                    # client options for initializing signet client
         
     | 
| 
       510 
     | 
    
         
            -
                    { token_credential_uri: options["token_credential_uri"],
         
     | 
| 
       511 
     | 
    
         
            -
                      audience:             options["audience"],
         
     | 
| 
       512 
     | 
    
         
            -
                      scope:                (needs_scope ? Array(options["scope"]) : nil),
         
     | 
| 
       513 
     | 
    
         
            -
                      target_audience:      options["target_audience"],
         
     | 
| 
       514 
     | 
    
         
            -
                      issuer:               options["client_email"],
         
     | 
| 
       515 
     | 
    
         
            -
                      signing_key:          OpenSSL::PKey::RSA.new(options["private_key"]) }
         
     | 
| 
      
 519 
     | 
    
         
            +
                    options
         
     | 
| 
       516 
520 
     | 
    
         
             
                  end
         
     | 
| 
       517 
521 
     | 
    
         | 
| 
       518 
     | 
    
         
            -
                  # rubocop:enable Metrics/AbcSize
         
     | 
| 
       519 
     | 
    
         
            -
             
     | 
| 
       520 
522 
     | 
    
         
             
                  def update_from_signet client
         
     | 
| 
       521 
523 
     | 
    
         
             
                    @project_id ||= client.project_id if client.respond_to? :project_id
         
     | 
| 
       522 
524 
     | 
    
         
             
                    @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
         
     | 
| 
         @@ -527,7 +529,7 @@ module Google 
     | 
|
| 
       527 
529 
     | 
    
         
             
                    hash = stringify_hash_keys hash
         
     | 
| 
       528 
530 
     | 
    
         
             
                    hash["scope"] ||= options[:scope]
         
     | 
| 
       529 
531 
     | 
    
         
             
                    hash["target_audience"] ||= options[:target_audience]
         
     | 
| 
       530 
     | 
    
         
            -
                    @project_id ||=  
     | 
| 
      
 532 
     | 
    
         
            +
                    @project_id ||= hash["project_id"] || hash["project"]
         
     | 
| 
       531 
533 
     | 
    
         
             
                    @quota_project_id ||= hash["quota_project_id"]
         
     | 
| 
       532 
534 
     | 
    
         
             
                    @client = init_client hash, options
         
     | 
| 
       533 
535 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -537,7 +539,7 @@ module Google 
     | 
|
| 
       537 
539 
     | 
    
         
             
                    json = JSON.parse ::File.read(path)
         
     | 
| 
       538 
540 
     | 
    
         
             
                    json["scope"] ||= options[:scope]
         
     | 
| 
       539 
541 
     | 
    
         
             
                    json["target_audience"] ||= options[:target_audience]
         
     | 
| 
       540 
     | 
    
         
            -
                    @project_id ||=  
     | 
| 
      
 542 
     | 
    
         
            +
                    @project_id ||= json["project_id"] || json["project"]
         
     | 
| 
       541 
543 
     | 
    
         
             
                    @quota_project_id ||= json["quota_project_id"]
         
     | 
| 
       542 
544 
     | 
    
         
             
                    @client = init_client json, options
         
     | 
| 
       543 
545 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -30,6 +30,11 @@ module Google 
     | 
|
| 
       30 
30 
     | 
    
         
             
                  REFRESH_TOKEN_VAR         = "GOOGLE_REFRESH_TOKEN".freeze
         
     | 
| 
       31 
31 
     | 
    
         
             
                  ACCOUNT_TYPE_VAR          = "GOOGLE_ACCOUNT_TYPE".freeze
         
     | 
| 
       32 
32 
     | 
    
         
             
                  PROJECT_ID_VAR            = "GOOGLE_PROJECT_ID".freeze
         
     | 
| 
      
 33 
     | 
    
         
            +
                  AWS_REGION_VAR            = "AWS_REGION".freeze
         
     | 
| 
      
 34 
     | 
    
         
            +
                  AWS_DEFAULT_REGION_VAR    = "AWS_DEFAULT_REGION".freeze
         
     | 
| 
      
 35 
     | 
    
         
            +
                  AWS_ACCESS_KEY_ID_VAR     = "AWS_ACCESS_KEY_ID".freeze
         
     | 
| 
      
 36 
     | 
    
         
            +
                  AWS_SECRET_ACCESS_KEY_VAR = "AWS_SECRET_ACCESS_KEY".freeze
         
     | 
| 
      
 37 
     | 
    
         
            +
                  AWS_SESSION_TOKEN_VAR     = "AWS_SESSION_TOKEN".freeze
         
     | 
| 
       33 
38 
     | 
    
         
             
                  GCLOUD_POSIX_COMMAND      = "gcloud".freeze
         
     | 
| 
       34 
39 
     | 
    
         
             
                  GCLOUD_WINDOWS_COMMAND    = "gcloud.cmd".freeze
         
     | 
| 
       35 
40 
     | 
    
         
             
                  GCLOUD_CONFIG_COMMAND     = "config config-helper --format json --verbosity none".freeze
         
     | 
| 
         @@ -41,16 +46,9 @@ module Google 
     | 
|
| 
       41 
46 
     | 
    
         | 
| 
       42 
47 
     | 
    
         
             
                  SYSTEM_DEFAULT_ERROR = "Unable to read the system default credential file".freeze
         
     | 
| 
       43 
48 
     | 
    
         | 
| 
       44 
     | 
    
         
            -
                  CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.app"\
         
     | 
| 
      
 49 
     | 
    
         
            +
                  CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.app" \
         
     | 
| 
       45 
50 
     | 
    
         
             
                                        "s.googleusercontent.com".freeze
         
     | 
| 
       46 
51 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
                  CLOUD_SDK_CREDENTIALS_WARNING =
         
     | 
| 
       48 
     | 
    
         
            -
                    "Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most" \
         
     | 
| 
       49 
     | 
    
         
            -
                    " server applications use service accounts instead. If your application continues to use end user credentials" \
         
     | 
| 
       50 
     | 
    
         
            -
                    ' from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about' \
         
     | 
| 
       51 
     | 
    
         
            -
                    " service accounts, see https://cloud.google.com/docs/authentication/. To suppress this message, set the"\
         
     | 
| 
       52 
     | 
    
         
            -
                    " GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS environment variable.".freeze
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
52 
     | 
    
         
             
                  # make_creds proxies the construction of a credentials instance
         
     | 
| 
       55 
53 
     | 
    
         
             
                  #
         
     | 
| 
       56 
54 
     | 
    
         
             
                  # By default, it calls #new on the current class, but this behaviour can
         
     | 
| 
         @@ -144,12 +142,6 @@ module Google 
     | 
|
| 
       144 
142 
     | 
    
         | 
| 
       145 
143 
     | 
    
         
             
                  module_function
         
     | 
| 
       146 
144 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
                  # Issues warning if cloud sdk client id is used
         
     | 
| 
       148 
     | 
    
         
            -
                  def warn_if_cloud_sdk_credentials client_id
         
     | 
| 
       149 
     | 
    
         
            -
                    return if ENV["GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS"]
         
     | 
| 
       150 
     | 
    
         
            -
                    warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID
         
     | 
| 
       151 
     | 
    
         
            -
                  end
         
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
145 
     | 
    
         
             
                  # Finds project_id from gcloud CLI configuration
         
     | 
| 
       154 
146 
     | 
    
         
             
                  def load_gcloud_project_id
         
     | 
| 
       155 
147 
     | 
    
         
             
                    gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
         
     | 
| 
         @@ -18,6 +18,7 @@ require "stringio" 
     | 
|
| 
       18 
18 
     | 
    
         
             
            require "googleauth/credentials_loader"
         
     | 
| 
       19 
19 
     | 
    
         
             
            require "googleauth/service_account"
         
     | 
| 
       20 
20 
     | 
    
         
             
            require "googleauth/user_refresh"
         
     | 
| 
      
 21 
     | 
    
         
            +
            require "googleauth/external_account"
         
     | 
| 
       21 
22 
     | 
    
         | 
| 
       22 
23 
     | 
    
         
             
            module Google
         
     | 
| 
       23 
24 
     | 
    
         
             
              # Module Auth provides classes that provide Google-specific authorization
         
     | 
| 
         @@ -34,11 +35,9 @@ module Google 
     | 
|
| 
       34 
35 
     | 
    
         
             
                    json_key_io = options[:json_key_io]
         
     | 
| 
       35 
36 
     | 
    
         
             
                    if json_key_io
         
     | 
| 
       36 
37 
     | 
    
         
             
                      json_key, clz = determine_creds_class json_key_io
         
     | 
| 
       37 
     | 
    
         
            -
                      warn_if_cloud_sdk_credentials json_key["client_id"]
         
     | 
| 
       38 
38 
     | 
    
         
             
                      io = StringIO.new MultiJson.dump(json_key)
         
     | 
| 
       39 
39 
     | 
    
         
             
                      clz.make_creds options.merge(json_key_io: io)
         
     | 
| 
       40 
40 
     | 
    
         
             
                    else
         
     | 
| 
       41 
     | 
    
         
            -
                      warn_if_cloud_sdk_credentials ENV[CredentialsLoader::CLIENT_ID_VAR]
         
     | 
| 
       42 
41 
     | 
    
         
             
                      clz = read_creds
         
     | 
| 
       43 
42 
     | 
    
         
             
                      clz.make_creds options
         
     | 
| 
       44 
43 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -53,6 +52,8 @@ module Google 
     | 
|
| 
       53 
52 
     | 
    
         
             
                      ServiceAccountCredentials
         
     | 
| 
       54 
53 
     | 
    
         
             
                    when "authorized_user"
         
     | 
| 
       55 
54 
     | 
    
         
             
                      UserRefreshCredentials
         
     | 
| 
      
 55 
     | 
    
         
            +
                    when "external_account"
         
     | 
| 
      
 56 
     | 
    
         
            +
                      ExternalAccount::Credentials
         
     | 
| 
       56 
57 
     | 
    
         
             
                    else
         
     | 
| 
       57 
58 
     | 
    
         
             
                      raise "credentials type '#{type}' is not supported"
         
     | 
| 
       58 
59 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -69,6 +70,8 @@ module Google 
     | 
|
| 
       69 
70 
     | 
    
         
             
                      [json_key, ServiceAccountCredentials]
         
     | 
| 
       70 
71 
     | 
    
         
             
                    when "authorized_user"
         
     | 
| 
       71 
72 
     | 
    
         
             
                      [json_key, UserRefreshCredentials]
         
     | 
| 
      
 73 
     | 
    
         
            +
                    when "external_account"
         
     | 
| 
      
 74 
     | 
    
         
            +
                      [json_key, ExternalAccount::Credentials]
         
     | 
| 
       72 
75 
     | 
    
         
             
                    else
         
     | 
| 
       73 
76 
     | 
    
         
             
                      raise "credentials type '#{type}' is not supported"
         
     | 
| 
       74 
77 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -0,0 +1,378 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright 2023 Google, Inc.
         
     | 
| 
      
 2 
     | 
    
         
            +
            #
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Licensed under the Apache License, Version 2.0 (the "License");
         
     | 
| 
      
 4 
     | 
    
         
            +
            # you may not use this file except in compliance with the License.
         
     | 
| 
      
 5 
     | 
    
         
            +
            # You may obtain a copy of the License at
         
     | 
| 
      
 6 
     | 
    
         
            +
            #
         
     | 
| 
      
 7 
     | 
    
         
            +
            #     http://www.apache.org/licenses/LICENSE-2.0
         
     | 
| 
      
 8 
     | 
    
         
            +
            #
         
     | 
| 
      
 9 
     | 
    
         
            +
            # Unless required by applicable law or agreed to in writing, software
         
     | 
| 
      
 10 
     | 
    
         
            +
            # distributed under the License is distributed on an "AS IS" BASIS,
         
     | 
| 
      
 11 
     | 
    
         
            +
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         
     | 
| 
      
 12 
     | 
    
         
            +
            # See the License for the specific language governing permissions and
         
     | 
| 
      
 13 
     | 
    
         
            +
            # limitations under the License.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            require "time"
         
     | 
| 
      
 16 
     | 
    
         
            +
            require "googleauth/external_account/base_credentials"
         
     | 
| 
      
 17 
     | 
    
         
            +
            require "googleauth/external_account/external_account_utils"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            module Google
         
     | 
| 
      
 20 
     | 
    
         
            +
              # Module Auth provides classes that provide Google-specific authorization used to access Google APIs.
         
     | 
| 
      
 21 
     | 
    
         
            +
              module Auth
         
     | 
| 
      
 22 
     | 
    
         
            +
                # Authenticates requests using External Account credentials, such as those provided by the AWS provider.
         
     | 
| 
      
 23 
     | 
    
         
            +
                module ExternalAccount
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # This module handles the retrieval of credentials from Google Cloud by utilizing the AWS EC2 metadata service and
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # then exchanging the credentials for a short-lived Google Cloud access token.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  class AwsCredentials
         
     | 
| 
      
 27 
     | 
    
         
            +
                    # Constant for imdsv2 session token expiration in seconds
         
     | 
| 
      
 28 
     | 
    
         
            +
                    IMDSV2_TOKEN_EXPIRATION_IN_SECONDS = 300
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    include Google::Auth::ExternalAccount::BaseCredentials
         
     | 
| 
      
 31 
     | 
    
         
            +
                    include Google::Auth::ExternalAccount::ExternalAccountUtils
         
     | 
| 
      
 32 
     | 
    
         
            +
                    extend CredentialsLoader
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    # Will always be nil, but method still gets used.
         
     | 
| 
      
 35 
     | 
    
         
            +
                    attr_reader :client_id
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    def initialize options = {}
         
     | 
| 
      
 38 
     | 
    
         
            +
                      base_setup options
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                      @audience = options[:audience]
         
     | 
| 
      
 41 
     | 
    
         
            +
                      @credential_source = options[:credential_source] || {}
         
     | 
| 
      
 42 
     | 
    
         
            +
                      @environment_id = @credential_source[:environment_id]
         
     | 
| 
      
 43 
     | 
    
         
            +
                      @region_url = @credential_source[:region_url]
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @credential_verification_url = @credential_source[:url]
         
     | 
| 
      
 45 
     | 
    
         
            +
                      @regional_cred_verification_url = @credential_source[:regional_cred_verification_url]
         
     | 
| 
      
 46 
     | 
    
         
            +
                      @imdsv2_session_token_url = @credential_source[:imdsv2_session_token_url]
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                      # These will be lazily loaded when needed, or will raise an error if not provided
         
     | 
| 
      
 49 
     | 
    
         
            +
                      @region = nil
         
     | 
| 
      
 50 
     | 
    
         
            +
                      @request_signer = nil
         
     | 
| 
      
 51 
     | 
    
         
            +
                      @imdsv2_session_token = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                      @imdsv2_session_token_expiry = nil
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    # Retrieves the subject token using the credential_source object.
         
     | 
| 
      
 56 
     | 
    
         
            +
                    # The subject token is a serialized [AWS GetCallerIdentity signed request](
         
     | 
| 
      
 57 
     | 
    
         
            +
                    #   https://cloud.google.com/iam/docs/access-resources-aws#exchange-token).
         
     | 
| 
      
 58 
     | 
    
         
            +
                    #
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # The logic is summarized as:
         
     | 
| 
      
 60 
     | 
    
         
            +
                    #
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION environment variable or from the AWS
         
     | 
| 
      
 62 
     | 
    
         
            +
                    # metadata server availability-zone if not found in the environment variable.
         
     | 
| 
      
 63 
     | 
    
         
            +
                    #
         
     | 
| 
      
 64 
     | 
    
         
            +
                    # Check AWS credentials in environment variables. If not found, retrieve from the AWS metadata server
         
     | 
| 
      
 65 
     | 
    
         
            +
                    # security-credentials endpoint.
         
     | 
| 
      
 66 
     | 
    
         
            +
                    #
         
     | 
| 
      
 67 
     | 
    
         
            +
                    # When retrieving AWS credentials from the metadata server security-credentials endpoint, the AWS role needs to
         
     | 
| 
      
 68 
     | 
    
         
            +
                    # be determined by # calling the security-credentials endpoint without any argument.
         
     | 
| 
      
 69 
     | 
    
         
            +
                    # Then the credentials can be retrieved via: security-credentials/role_name
         
     | 
| 
      
 70 
     | 
    
         
            +
                    #
         
     | 
| 
      
 71 
     | 
    
         
            +
                    # Generate the signed request to AWS STS GetCallerIdentity action.
         
     | 
| 
      
 72 
     | 
    
         
            +
                    #
         
     | 
| 
      
 73 
     | 
    
         
            +
                    # Inject x-goog-cloud-target-resource into header and serialize the signed request.
         
     | 
| 
      
 74 
     | 
    
         
            +
                    # This will be the subject-token to pass to GCP STS.
         
     | 
| 
      
 75 
     | 
    
         
            +
                    #
         
     | 
| 
      
 76 
     | 
    
         
            +
                    # @return [string] The retrieved subject token.
         
     | 
| 
      
 77 
     | 
    
         
            +
                    #
         
     | 
| 
      
 78 
     | 
    
         
            +
                    def retrieve_subject_token!
         
     | 
| 
      
 79 
     | 
    
         
            +
                      if @request_signer.nil?
         
     | 
| 
      
 80 
     | 
    
         
            +
                        @region = region
         
     | 
| 
      
 81 
     | 
    
         
            +
                        @request_signer = AwsRequestSigner.new @region
         
     | 
| 
      
 82 
     | 
    
         
            +
                      end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                      request = {
         
     | 
| 
      
 85 
     | 
    
         
            +
                        method: "POST",
         
     | 
| 
      
 86 
     | 
    
         
            +
                        url: @regional_cred_verification_url.sub("{region}", @region)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      }
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                      request_options = @request_signer.generate_signed_request fetch_security_credentials, request
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                      request_headers = request_options[:headers]
         
     | 
| 
      
 92 
     | 
    
         
            +
                      request_headers["x-goog-cloud-target-resource"] = @audience
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                      aws_signed_request = {
         
     | 
| 
      
 95 
     | 
    
         
            +
                        headers: [],
         
     | 
| 
      
 96 
     | 
    
         
            +
                        method: request_options[:method],
         
     | 
| 
      
 97 
     | 
    
         
            +
                        url: request_options[:url]
         
     | 
| 
      
 98 
     | 
    
         
            +
                      }
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                      aws_signed_request[:headers] = request_headers.keys.sort.map do |key|
         
     | 
| 
      
 101 
     | 
    
         
            +
                        { key: key, value: request_headers[key] }
         
     | 
| 
      
 102 
     | 
    
         
            +
                      end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                      uri_escape aws_signed_request.to_json
         
     | 
| 
      
 105 
     | 
    
         
            +
                    end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                    private
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    def imdsv2_session_token
         
     | 
| 
      
 110 
     | 
    
         
            +
                      return @imdsv2_session_token unless imdsv2_session_token_invalid?
         
     | 
| 
      
 111 
     | 
    
         
            +
                      raise "IMDSV2 token url must be provided" if @imdsv2_session_token_url.nil?
         
     | 
| 
      
 112 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 113 
     | 
    
         
            +
                        response = connection.put @imdsv2_session_token_url do |req|
         
     | 
| 
      
 114 
     | 
    
         
            +
                          req.headers["x-aws-ec2-metadata-token-ttl-seconds"] = IMDSV2_TOKEN_EXPIRATION_IN_SECONDS.to_s
         
     | 
| 
      
 115 
     | 
    
         
            +
                        end
         
     | 
| 
      
 116 
     | 
    
         
            +
                      rescue Faraday::Error => e
         
     | 
| 
      
 117 
     | 
    
         
            +
                        raise "Fetching AWS IMDSV2 token error: #{e}"
         
     | 
| 
      
 118 
     | 
    
         
            +
                      end
         
     | 
| 
      
 119 
     | 
    
         
            +
                      raise Faraday::Error unless response.success?
         
     | 
| 
      
 120 
     | 
    
         
            +
                      @imdsv2_session_token = response.body
         
     | 
| 
      
 121 
     | 
    
         
            +
                      @imdsv2_session_token_expiry = Time.now + IMDSV2_TOKEN_EXPIRATION_IN_SECONDS
         
     | 
| 
      
 122 
     | 
    
         
            +
                      @imdsv2_session_token
         
     | 
| 
      
 123 
     | 
    
         
            +
                    end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    def imdsv2_session_token_invalid?
         
     | 
| 
      
 126 
     | 
    
         
            +
                      return true if @imdsv2_session_token.nil?
         
     | 
| 
      
 127 
     | 
    
         
            +
                      @imdsv2_session_token_expiry.nil? || @imdsv2_session_token_expiry < Time.now
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                    def get_aws_resource url, name, data: nil, headers: {}
         
     | 
| 
      
 131 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 132 
     | 
    
         
            +
                        headers["x-aws-ec2-metadata-token"] = imdsv2_session_token
         
     | 
| 
      
 133 
     | 
    
         
            +
                        response = if data
         
     | 
| 
      
 134 
     | 
    
         
            +
                                     headers["Content-Type"] = "application/json"
         
     | 
| 
      
 135 
     | 
    
         
            +
                                     connection.post url, data, headers
         
     | 
| 
      
 136 
     | 
    
         
            +
                                   else
         
     | 
| 
      
 137 
     | 
    
         
            +
                                     connection.get url, nil, headers
         
     | 
| 
      
 138 
     | 
    
         
            +
                                   end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                        raise Faraday::Error unless response.success?
         
     | 
| 
      
 141 
     | 
    
         
            +
                        response
         
     | 
| 
      
 142 
     | 
    
         
            +
                      rescue Faraday::Error
         
     | 
| 
      
 143 
     | 
    
         
            +
                        raise "Failed to retrieve AWS #{name}."
         
     | 
| 
      
 144 
     | 
    
         
            +
                      end
         
     | 
| 
      
 145 
     | 
    
         
            +
                    end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                    def uri_escape string
         
     | 
| 
      
 148 
     | 
    
         
            +
                      if string.nil?
         
     | 
| 
      
 149 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 150 
     | 
    
         
            +
                      else
         
     | 
| 
      
 151 
     | 
    
         
            +
                        CGI.escape(string.encode("UTF-8")).gsub("+", "%20").gsub("%7E", "~")
         
     | 
| 
      
 152 
     | 
    
         
            +
                      end
         
     | 
| 
      
 153 
     | 
    
         
            +
                    end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                    # Retrieves the AWS security credentials required for signing AWS requests from either the AWS security
         
     | 
| 
      
 156 
     | 
    
         
            +
                    # credentials environment variables or from the AWS metadata server.
         
     | 
| 
      
 157 
     | 
    
         
            +
                    def fetch_security_credentials
         
     | 
| 
      
 158 
     | 
    
         
            +
                      env_aws_access_key_id = ENV[CredentialsLoader::AWS_ACCESS_KEY_ID_VAR]
         
     | 
| 
      
 159 
     | 
    
         
            +
                      env_aws_secret_access_key = ENV[CredentialsLoader::AWS_SECRET_ACCESS_KEY_VAR]
         
     | 
| 
      
 160 
     | 
    
         
            +
                      # This is normally not available for permanent credentials.
         
     | 
| 
      
 161 
     | 
    
         
            +
                      env_aws_session_token = ENV[CredentialsLoader::AWS_SESSION_TOKEN_VAR]
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                      if env_aws_access_key_id && env_aws_secret_access_key
         
     | 
| 
      
 164 
     | 
    
         
            +
                        return {
         
     | 
| 
      
 165 
     | 
    
         
            +
                          access_key_id: env_aws_access_key_id,
         
     | 
| 
      
 166 
     | 
    
         
            +
                          secret_access_key: env_aws_secret_access_key,
         
     | 
| 
      
 167 
     | 
    
         
            +
                          session_token: env_aws_session_token
         
     | 
| 
      
 168 
     | 
    
         
            +
                        }
         
     | 
| 
      
 169 
     | 
    
         
            +
                      end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                      role_name = fetch_metadata_role_name
         
     | 
| 
      
 172 
     | 
    
         
            +
                      credentials = fetch_metadata_security_credentials role_name
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                      {
         
     | 
| 
      
 175 
     | 
    
         
            +
                        access_key_id: credentials["AccessKeyId"],
         
     | 
| 
      
 176 
     | 
    
         
            +
                        secret_access_key: credentials["SecretAccessKey"],
         
     | 
| 
      
 177 
     | 
    
         
            +
                        session_token: credentials["Token"]
         
     | 
| 
      
 178 
     | 
    
         
            +
                      }
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                    # Retrieves the AWS role currently attached to the current AWS workload by querying the AWS metadata server.
         
     | 
| 
      
 182 
     | 
    
         
            +
                    # This is needed for the AWS metadata server security credentials endpoint in order to retrieve the AWS security
         
     | 
| 
      
 183 
     | 
    
         
            +
                    # credentials needed to sign requests to AWS APIs.
         
     | 
| 
      
 184 
     | 
    
         
            +
                    def fetch_metadata_role_name
         
     | 
| 
      
 185 
     | 
    
         
            +
                      unless @credential_verification_url
         
     | 
| 
      
 186 
     | 
    
         
            +
                        raise "Unable to determine the AWS metadata server security credentials endpoint"
         
     | 
| 
      
 187 
     | 
    
         
            +
                      end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                      get_aws_resource(@credential_verification_url, "IAM Role").body
         
     | 
| 
      
 190 
     | 
    
         
            +
                    end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                    # Retrieves the AWS security credentials required for signing AWS requests from the AWS metadata server.
         
     | 
| 
      
 193 
     | 
    
         
            +
                    def fetch_metadata_security_credentials role_name
         
     | 
| 
      
 194 
     | 
    
         
            +
                      response = get_aws_resource "#{@credential_verification_url}/#{role_name}", "credentials"
         
     | 
| 
      
 195 
     | 
    
         
            +
                      MultiJson.load response.body
         
     | 
| 
      
 196 
     | 
    
         
            +
                    end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                    def region
         
     | 
| 
      
 199 
     | 
    
         
            +
                      @region = ENV[CredentialsLoader::AWS_REGION_VAR] || ENV[CredentialsLoader::AWS_DEFAULT_REGION_VAR]
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                      unless @region
         
     | 
| 
      
 202 
     | 
    
         
            +
                        raise "region_url or region must be set for external account credentials" unless @region_url
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                        @region ||= get_aws_resource(@region_url, "region").body[0..-2]
         
     | 
| 
      
 205 
     | 
    
         
            +
                      end
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                      @region
         
     | 
| 
      
 208 
     | 
    
         
            +
                    end
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                  # Implements an AWS request signer based on the AWS Signature Version 4 signing process.
         
     | 
| 
      
 212 
     | 
    
         
            +
                  # https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
         
     | 
| 
      
 213 
     | 
    
         
            +
                  class AwsRequestSigner
         
     | 
| 
      
 214 
     | 
    
         
            +
                    # Instantiates an AWS request signer used to compute authenticated signed requests to AWS APIs based on the AWS
         
     | 
| 
      
 215 
     | 
    
         
            +
                    # Signature Version 4 signing process.
         
     | 
| 
      
 216 
     | 
    
         
            +
                    #
         
     | 
| 
      
 217 
     | 
    
         
            +
                    # @param [string] region_name
         
     | 
| 
      
 218 
     | 
    
         
            +
                    #     The AWS region to use.
         
     | 
| 
      
 219 
     | 
    
         
            +
                    def initialize region_name
         
     | 
| 
      
 220 
     | 
    
         
            +
                      @region_name = region_name
         
     | 
| 
      
 221 
     | 
    
         
            +
                    end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                    # Generates the signed request for the provided HTTP request for calling
         
     | 
| 
      
 224 
     | 
    
         
            +
                    # an AWS API. This follows the steps described at:
         
     | 
| 
      
 225 
     | 
    
         
            +
                    # https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
         
     | 
| 
      
 226 
     | 
    
         
            +
                    #
         
     | 
| 
      
 227 
     | 
    
         
            +
                    # @param [Hash[string, string]] aws_security_credentials
         
     | 
| 
      
 228 
     | 
    
         
            +
                    #     A dictionary containing the AWS security credentials.
         
     | 
| 
      
 229 
     | 
    
         
            +
                    # @param [string] url
         
     | 
| 
      
 230 
     | 
    
         
            +
                    #     The AWS service URL containing the canonical URI and query string.
         
     | 
| 
      
 231 
     | 
    
         
            +
                    # @param [string] method
         
     | 
| 
      
 232 
     | 
    
         
            +
                    #     The HTTP method used to call this API.
         
     | 
| 
      
 233 
     | 
    
         
            +
                    #
         
     | 
| 
      
 234 
     | 
    
         
            +
                    # @return [hash{string => string}]
         
     | 
| 
      
 235 
     | 
    
         
            +
                    #     The AWS signed request dictionary object.
         
     | 
| 
      
 236 
     | 
    
         
            +
                    #
         
     | 
| 
      
 237 
     | 
    
         
            +
                    def generate_signed_request aws_credentials, original_request
         
     | 
| 
      
 238 
     | 
    
         
            +
                      uri = Addressable::URI.parse original_request[:url]
         
     | 
| 
      
 239 
     | 
    
         
            +
                      raise "Invalid AWS service URL" unless uri.hostname && uri.scheme == "https"
         
     | 
| 
      
 240 
     | 
    
         
            +
                      service_name = uri.host.split(".").first
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
                      datetime = Time.now.utc.strftime "%Y%m%dT%H%M%SZ"
         
     | 
| 
      
 243 
     | 
    
         
            +
                      date = datetime[0, 8]
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                      headers = aws_headers aws_credentials, original_request, datetime
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
                      request_payload = original_request[:data] || ""
         
     | 
| 
      
 248 
     | 
    
         
            +
                      content_sha256 = sha256_hexdigest request_payload
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                      canonical_req = canonical_request original_request[:method], uri, headers, content_sha256
         
     | 
| 
      
 251 
     | 
    
         
            +
                      sts = string_to_sign datetime, canonical_req, service_name
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                      # Authorization header requires everything else to be properly setup in order to be properly
         
     | 
| 
      
 254 
     | 
    
         
            +
                      # calculated.
         
     | 
| 
      
 255 
     | 
    
         
            +
                      headers["Authorization"] = build_authorization_header headers, sts, aws_credentials, service_name, date
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                      {
         
     | 
| 
      
 258 
     | 
    
         
            +
                        url: uri.to_s,
         
     | 
| 
      
 259 
     | 
    
         
            +
                        headers: headers,
         
     | 
| 
      
 260 
     | 
    
         
            +
                        method: original_request[:method],
         
     | 
| 
      
 261 
     | 
    
         
            +
                        data: (request_payload unless request_payload.empty?)
         
     | 
| 
      
 262 
     | 
    
         
            +
                      }.compact
         
     | 
| 
      
 263 
     | 
    
         
            +
                    end
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                    private
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                    def aws_headers aws_credentials, original_request, datetime
         
     | 
| 
      
 268 
     | 
    
         
            +
                      uri = Addressable::URI.parse original_request[:url]
         
     | 
| 
      
 269 
     | 
    
         
            +
                      temp_headers = original_request[:headers] || {}
         
     | 
| 
      
 270 
     | 
    
         
            +
                      headers = {}
         
     | 
| 
      
 271 
     | 
    
         
            +
                      temp_headers.each_key { |k| headers[k.to_s] = temp_headers[k] }
         
     | 
| 
      
 272 
     | 
    
         
            +
                      headers["host"] = uri.host
         
     | 
| 
      
 273 
     | 
    
         
            +
                      headers["x-amz-date"] = datetime
         
     | 
| 
      
 274 
     | 
    
         
            +
                      headers["x-amz-security-token"] = aws_credentials[:session_token] if aws_credentials[:session_token]
         
     | 
| 
      
 275 
     | 
    
         
            +
                      headers
         
     | 
| 
      
 276 
     | 
    
         
            +
                    end
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                    def build_authorization_header headers, sts, aws_credentials, service_name, date
         
     | 
| 
      
 279 
     | 
    
         
            +
                      [
         
     | 
| 
      
 280 
     | 
    
         
            +
                        "AWS4-HMAC-SHA256",
         
     | 
| 
      
 281 
     | 
    
         
            +
                        "Credential=#{credential aws_credentials[:access_key_id], date, service_name},",
         
     | 
| 
      
 282 
     | 
    
         
            +
                        "SignedHeaders=#{headers.keys.sort.join ';'},",
         
     | 
| 
      
 283 
     | 
    
         
            +
                        "Signature=#{signature aws_credentials[:secret_access_key], date, sts, service_name}"
         
     | 
| 
      
 284 
     | 
    
         
            +
                      ].join(" ")
         
     | 
| 
      
 285 
     | 
    
         
            +
                    end
         
     | 
| 
      
 286 
     | 
    
         
            +
             
     | 
| 
      
 287 
     | 
    
         
            +
                    def signature secret_access_key, date, string_to_sign, service
         
     | 
| 
      
 288 
     | 
    
         
            +
                      k_date = hmac "AWS4#{secret_access_key}", date
         
     | 
| 
      
 289 
     | 
    
         
            +
                      k_region = hmac k_date, @region_name
         
     | 
| 
      
 290 
     | 
    
         
            +
                      k_service = hmac k_region, service
         
     | 
| 
      
 291 
     | 
    
         
            +
                      k_credentials = hmac k_service, "aws4_request"
         
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
      
 293 
     | 
    
         
            +
                      hexhmac k_credentials, string_to_sign
         
     | 
| 
      
 294 
     | 
    
         
            +
                    end
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
                    def hmac key, value
         
     | 
| 
      
 297 
     | 
    
         
            +
                      OpenSSL::HMAC.digest OpenSSL::Digest.new("sha256"), key, value
         
     | 
| 
      
 298 
     | 
    
         
            +
                    end
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
                    def hexhmac key, value
         
     | 
| 
      
 301 
     | 
    
         
            +
                      OpenSSL::HMAC.hexdigest OpenSSL::Digest.new("sha256"), key, value
         
     | 
| 
      
 302 
     | 
    
         
            +
                    end
         
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
      
 304 
     | 
    
         
            +
                    def credential access_key_id, date, service
         
     | 
| 
      
 305 
     | 
    
         
            +
                      "#{access_key_id}/#{credential_scope date, service}"
         
     | 
| 
      
 306 
     | 
    
         
            +
                    end
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
      
 308 
     | 
    
         
            +
                    def credential_scope date, service
         
     | 
| 
      
 309 
     | 
    
         
            +
                      [
         
     | 
| 
      
 310 
     | 
    
         
            +
                        date,
         
     | 
| 
      
 311 
     | 
    
         
            +
                        @region_name,
         
     | 
| 
      
 312 
     | 
    
         
            +
                        service,
         
     | 
| 
      
 313 
     | 
    
         
            +
                        "aws4_request"
         
     | 
| 
      
 314 
     | 
    
         
            +
                      ].join("/")
         
     | 
| 
      
 315 
     | 
    
         
            +
                    end
         
     | 
| 
      
 316 
     | 
    
         
            +
             
     | 
| 
      
 317 
     | 
    
         
            +
                    def string_to_sign datetime, canonical_request, service
         
     | 
| 
      
 318 
     | 
    
         
            +
                      [
         
     | 
| 
      
 319 
     | 
    
         
            +
                        "AWS4-HMAC-SHA256",
         
     | 
| 
      
 320 
     | 
    
         
            +
                        datetime,
         
     | 
| 
      
 321 
     | 
    
         
            +
                        credential_scope(datetime[0, 8], service),
         
     | 
| 
      
 322 
     | 
    
         
            +
                        sha256_hexdigest(canonical_request)
         
     | 
| 
      
 323 
     | 
    
         
            +
                      ].join("\n")
         
     | 
| 
      
 324 
     | 
    
         
            +
                    end
         
     | 
| 
      
 325 
     | 
    
         
            +
             
     | 
| 
      
 326 
     | 
    
         
            +
                    def host uri
         
     | 
| 
      
 327 
     | 
    
         
            +
                      # Handles known and unknown URI schemes; default_port nil when unknown.
         
     | 
| 
      
 328 
     | 
    
         
            +
                      if uri.default_port == uri.port
         
     | 
| 
      
 329 
     | 
    
         
            +
                        uri.host
         
     | 
| 
      
 330 
     | 
    
         
            +
                      else
         
     | 
| 
      
 331 
     | 
    
         
            +
                        "#{uri.host}:#{uri.port}"
         
     | 
| 
      
 332 
     | 
    
         
            +
                      end
         
     | 
| 
      
 333 
     | 
    
         
            +
                    end
         
     | 
| 
      
 334 
     | 
    
         
            +
             
     | 
| 
      
 335 
     | 
    
         
            +
                    def canonical_request http_method, uri, headers, content_sha256
         
     | 
| 
      
 336 
     | 
    
         
            +
                      headers = headers.sort_by(&:first) # transforms to a sorted array of [key, value]
         
     | 
| 
      
 337 
     | 
    
         
            +
             
     | 
| 
      
 338 
     | 
    
         
            +
                      [
         
     | 
| 
      
 339 
     | 
    
         
            +
                        http_method,
         
     | 
| 
      
 340 
     | 
    
         
            +
                        uri.path.empty? ? "/" : uri.path,
         
     | 
| 
      
 341 
     | 
    
         
            +
                        build_canonical_querystring(uri.query || ""),
         
     | 
| 
      
 342 
     | 
    
         
            +
                        headers.map { |k, v| "#{k}:#{v}\n" }.join, # Canonical headers
         
     | 
| 
      
 343 
     | 
    
         
            +
                        headers.map(&:first).join(";"), # Signed headers
         
     | 
| 
      
 344 
     | 
    
         
            +
                        content_sha256
         
     | 
| 
      
 345 
     | 
    
         
            +
                      ].join("\n")
         
     | 
| 
      
 346 
     | 
    
         
            +
                    end
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
                    def sha256_hexdigest string
         
     | 
| 
      
 349 
     | 
    
         
            +
                      OpenSSL::Digest::SHA256.hexdigest string
         
     | 
| 
      
 350 
     | 
    
         
            +
                    end
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
                    # Generates the canonical query string given a raw query string.
         
     | 
| 
      
 353 
     | 
    
         
            +
                    # Logic is based on
         
     | 
| 
      
 354 
     | 
    
         
            +
                    # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
         
     | 
| 
      
 355 
     | 
    
         
            +
                    # Code is from the AWS SDK for Ruby
         
     | 
| 
      
 356 
     | 
    
         
            +
                    # https://github.com/aws/aws-sdk-ruby/blob/0ac3d0a393ed216290bfb5f0383380376f6fb1f1/gems/aws-sigv4/lib/aws-sigv4/signer.rb#L532
         
     | 
| 
      
 357 
     | 
    
         
            +
                    def build_canonical_querystring query
         
     | 
| 
      
 358 
     | 
    
         
            +
                      params = query.split "&"
         
     | 
| 
      
 359 
     | 
    
         
            +
                      params = params.map { |p| p.include?("=") ? p : "#{p}=" }
         
     | 
| 
      
 360 
     | 
    
         
            +
             
     | 
| 
      
 361 
     | 
    
         
            +
                      params.each.with_index.sort do |(a, a_offset), (b, b_offset)|
         
     | 
| 
      
 362 
     | 
    
         
            +
                        a_name, a_value = a.split "="
         
     | 
| 
      
 363 
     | 
    
         
            +
                        b_name, b_value = b.split "="
         
     | 
| 
      
 364 
     | 
    
         
            +
                        if a_name == b_name
         
     | 
| 
      
 365 
     | 
    
         
            +
                          if a_value == b_value
         
     | 
| 
      
 366 
     | 
    
         
            +
                            a_offset <=> b_offset
         
     | 
| 
      
 367 
     | 
    
         
            +
                          else
         
     | 
| 
      
 368 
     | 
    
         
            +
                            a_value <=> b_value
         
     | 
| 
      
 369 
     | 
    
         
            +
                          end
         
     | 
| 
      
 370 
     | 
    
         
            +
                        else
         
     | 
| 
      
 371 
     | 
    
         
            +
                          a_name <=> b_name
         
     | 
| 
      
 372 
     | 
    
         
            +
                        end
         
     | 
| 
      
 373 
     | 
    
         
            +
                      end.map(&:first).join("&")
         
     | 
| 
      
 374 
     | 
    
         
            +
                    end
         
     | 
| 
      
 375 
     | 
    
         
            +
                  end
         
     | 
| 
      
 376 
     | 
    
         
            +
                end
         
     | 
| 
      
 377 
     | 
    
         
            +
              end
         
     | 
| 
      
 378 
     | 
    
         
            +
            end
         
     |