googleauth 1.11.1 → 1.13.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d69318fbe3400719c053c16d4de765ae8a87ad1250c6f62c2c1c2e9c51a97d89
4
- data.tar.gz: be832e5bfb8417794534ba11186a6175a2e0e28b06c702c095330f2818345973
3
+ metadata.gz: 6d8ca5b2b0c7f4ce54f7971d8de2f23f3ee0837d08d7d3c568c503308fcf82ab
4
+ data.tar.gz: d5f8b8fd2fcb4fef4240db58bf90f54a8bfd021c550a7bc9063c9087285f3921
5
5
  SHA512:
6
- metadata.gz: cb7ac3b1d73c1375434d0bfa14ed2e83a2b5fd4f2a5c917a656ff12bdf91028b62724ef9b3774092ac377941b7a6596828a4a87d1ea7e00d1e82a57893981505
7
- data.tar.gz: 8f00b43299f1dcc2989b256fbc917f12f0df9ee1b4a000752225a834a8d5a360aea2bf58cb80ec5888e114f14a0e7cd9cae8958be8ebde146c21953b12a74c6d
6
+ metadata.gz: 6a4de2b23f4dc0310a18568e0618c5d81fad54dfc8d57fe3c27b954c4bd21272fcc467c2c313f98f80fa127eab11ebe0d0fc55ceed7e6c5439764500e334df49
7
+ data.tar.gz: f4aff68138105ea19875bb7a51d4b6ced9ec2e7185ce725eca205ea1dc11beb9e6019c9dd89449866598ca6e4d3abb06b91f277f34e51ac7726d79a39fc40c67
data/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Release History
2
2
 
3
+ ### 1.13.1 (2025-01-24)
4
+
5
+ #### Bug Fixes
6
+
7
+ * Signet client subclasses no longer make the update! method private ([#516](https://github.com/googleapis/google-auth-library-ruby/issues/516))
8
+
9
+ ### 1.13.0 (2025-01-22)
10
+
11
+ #### Features
12
+
13
+ * create impersonated service credentials ([#499](https://github.com/googleapis/google-auth-library-ruby/issues/499))
14
+ #### Documentation
15
+
16
+ * Include note about validating externally-provided credentials ([#512](https://github.com/googleapis/google-auth-library-ruby/issues/512))
17
+
18
+ ### 1.12.2 (2024-12-19)
19
+
20
+ #### Bug Fixes
21
+
22
+ * GCECredentials lazily fetches from the metadata server to ensure a universe domain is known ([#509](https://github.com/googleapis/google-auth-library-ruby/issues/509))
23
+
24
+ ### 1.12.1 (2024-12-17)
25
+
26
+ #### Bug Fixes
27
+
28
+ * Restored previous behavior where the apply! method returns the auth header ([#506](https://github.com/googleapis/google-auth-library-ruby/issues/506))
29
+
30
+ ### 1.12.0 (2024-12-05)
31
+
32
+ #### Features
33
+
34
+ * provided opt-in debug logging ([#490](https://github.com/googleapis/google-auth-library-ruby/issues/490))
35
+
36
+ ### 1.11.2 (2024-10-23)
37
+
38
+ #### Bug Fixes
39
+
40
+ * Temporarily disable universe domain query from GCE metadata server ([#493](https://github.com/googleapis/google-auth-library-ruby/issues/493))
41
+ * Use updated metadata path for universe-domain ([#496](https://github.com/googleapis/google-auth-library-ruby/issues/496))
42
+
3
43
  ### 1.11.1 (2024-10-04)
4
44
 
5
45
  #### Bug Fixes
data/README.md CHANGED
@@ -64,6 +64,15 @@ well as a web variant tailored toward Rack-based applications.
64
64
  The authorizers are intended for authorization use cases. For sign-on,
65
65
  see [Google Identity Platform](https://developers.google.com/identity/)
66
66
 
67
+ ## Important notes
68
+
69
+ If you accept a credential configuration (credential JSON/File/Stream) from an
70
+ external source for authentication to Google Cloud, you must validate it before
71
+ providing it to any Google API or library. Providing an unvalidated credential
72
+ configuration to Google APIs can compromise the security of your systems and data.
73
+ For more information, refer to [Validate credential configurations from external
74
+ sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
75
+
67
76
  ### Example (Web)
68
77
 
69
78
  ```ruby
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ require "google/logging/message"
16
+
15
17
  module Google
16
18
  # Module Auth provides classes that provide Google-specific authorization
17
19
  # used to access Google APIs.
@@ -29,7 +31,14 @@ module Google
29
31
  # fetch the access token there is currently not one, or if the client
30
32
  # has expired
31
33
  fetch_access_token! opts if needs_access_token?
32
- a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
34
+ token = send token_type
35
+ a_hash[AUTH_METADATA_KEY] = "Bearer #{token}"
36
+ logger&.debug do
37
+ hash = Digest::SHA256.hexdigest token
38
+ Google::Logging::Message.from message: "Sending auth token. (sha256:#{hash})"
39
+ end
40
+
41
+ a_hash[AUTH_METADATA_KEY]
33
42
  end
34
43
 
35
44
  # Returns a clone of a_hash updated with the authentication token
@@ -66,6 +75,9 @@ module Google
66
75
  raise NoMethodError, "expires_within? not implemented"
67
76
  end
68
77
 
78
+ # The logger used to log operations on this client, such as token refresh.
79
+ attr_accessor :logger
80
+
69
81
  private
70
82
 
71
83
  def token_type
@@ -80,46 +80,139 @@ module Google
80
80
  alias unmemoize_all reset_cache
81
81
  end
82
82
 
83
+ # @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
84
+ attr_accessor :disable_universe_domain_check
85
+
83
86
  # Construct a GCECredentials
84
87
  def initialize options = {}
85
88
  # Override the constructor to remember whether the universe domain was
86
89
  # overridden by a constructor argument.
87
90
  @universe_domain_overridden = options["universe_domain"] || options[:universe_domain] ? true : false
91
+ # TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
92
+ @disable_universe_domain_check = true
88
93
  super options
89
94
  end
90
95
 
96
+ # Creates a duplicate of these credentials
97
+ # without the Signet::OAuth2::Client-specific
98
+ # transient state (e.g. cached tokens)
99
+ #
100
+ # @param options [Hash] Overrides for the credentials parameters.
101
+ # The following keys are recognized in addition to keys in the
102
+ # Signet::OAuth2::Client
103
+ # * `:universe_domain_overridden` Whether the universe domain was
104
+ # overriden during credentials creation
105
+ def duplicate options = {}
106
+ options = deep_hash_normalize options
107
+ super(
108
+ {
109
+ universe_domain_overridden: @universe_domain_overridden
110
+ }.merge(options)
111
+ )
112
+ end
113
+
114
+ # @private
115
+ # Overrides universe_domain getter to fetch lazily if it hasn't been
116
+ # fetched yet. This is necessary specifically for Compute Engine because
117
+ # the universe comes from the metadata service, and isn't known
118
+ # immediately on credential construction. All other credential types read
119
+ # the universe from their json key or other immediate input.
120
+ def universe_domain
121
+ value = super
122
+ return value unless value.nil?
123
+ fetch_access_token!
124
+ super
125
+ end
126
+
91
127
  # Overrides the super class method to change how access tokens are
92
128
  # fetched.
93
129
  def fetch_access_token _options = {}
94
- if token_type == :id_token
95
- query = { "audience" => target_audience, "format" => "full" }
96
- entry = "service-accounts/default/identity"
97
- else
98
- query = {}
99
- entry = "service-accounts/default/token"
100
- end
130
+ query, entry =
131
+ if token_type == :id_token
132
+ [{ "audience" => target_audience, "format" => "full" }, "service-accounts/default/identity"]
133
+ else
134
+ [{}, "service-accounts/default/token"]
135
+ end
101
136
  query[:scopes] = Array(scope).join "," if scope
102
137
  begin
138
+ log_fetch_query
103
139
  resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query
140
+ log_fetch_resp resp
104
141
  case resp.status
105
142
  when 200
106
143
  build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
107
144
  when 403, 500
108
- msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
109
- raise Signet::UnexpectedStatusError, msg
145
+ raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
110
146
  when 404
111
147
  raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
112
148
  else
113
- msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
114
- raise Signet::AuthorizationError, msg
149
+ raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
115
150
  end
116
151
  rescue Google::Cloud::Env::MetadataServerNotResponding => e
152
+ log_fetch_err e
117
153
  raise Signet::AuthorizationError, e.message
118
154
  end
119
155
  end
120
156
 
157
+ # Destructively updates these credentials.
158
+ #
159
+ # This method is called by `Signet::OAuth2::Client`'s constructor
160
+ #
161
+ # @param options [Hash] Overrides for the credentials parameters.
162
+ # The following keys are recognized in addition to keys in the
163
+ # Signet::OAuth2::Client
164
+ # * `:universe_domain_overridden` Whether the universe domain was
165
+ # overriden during credentials creation
166
+ # @return [Google::Auth::GCECredentials]
167
+ def update! options = {}
168
+ # Normalize all keys to symbols to allow indifferent access.
169
+ options = deep_hash_normalize options
170
+
171
+ @universe_domain_overridden = options[:universe_domain_overridden] if options.key? :universe_domain_overridden
172
+
173
+ super(options)
174
+
175
+ self
176
+ end
177
+
121
178
  private
122
179
 
180
+ def log_fetch_query
181
+ if token_type == :id_token
182
+ logger&.info do
183
+ Google::Logging::Message.from(
184
+ message: "Requesting id token from MDS with aud=#{target_audience}",
185
+ "credentialsId" => object_id
186
+ )
187
+ end
188
+ else
189
+ logger&.info do
190
+ Google::Logging::Message.from(
191
+ message: "Requesting access token from MDS",
192
+ "credentialsId" => object_id
193
+ )
194
+ end
195
+ end
196
+ end
197
+
198
+ def log_fetch_resp resp
199
+ logger&.info do
200
+ Google::Logging::Message.from(
201
+ message: "Received #{resp.status} from MDS",
202
+ "credentialsId" => object_id
203
+ )
204
+ end
205
+ end
206
+
207
+ def log_fetch_err _err
208
+ logger&.info do
209
+ Google::Logging::Message.from(
210
+ message: "MDS did not respond to token request",
211
+ "credentialsId" => object_id
212
+ )
213
+ end
214
+ end
215
+
123
216
  def build_token_hash body, content_type, retrieval_time
124
217
  hash =
125
218
  if ["text/html", "application/text"].include? content_type
@@ -127,20 +220,8 @@ module Google
127
220
  else
128
221
  Signet::OAuth2.parse_credentials body, content_type
129
222
  end
130
- unless @universe_domain_overridden
131
- universe_domain = Google::Cloud.env.lookup_metadata "universe", "universe_domain"
132
- universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
133
- hash["universe_domain"] = universe_domain.strip
134
- end
135
- # The response might have been cached, which means expires_in might be
136
- # stale. Update it based on the time since the data was retrieved.
137
- # We also ensure expires_in is conservative; subtracting at least 1
138
- # second to offset any skew from metadata server latency.
139
- if hash["expires_in"].is_a? Numeric
140
- offset = 1 + (Process.clock_gettime(Process::CLOCK_MONOTONIC) - retrieval_time).round
141
- hash["expires_in"] -= offset if offset.positive?
142
- hash["expires_in"] = 0 if hash["expires_in"].negative?
143
- end
223
+ add_universe_domain_to hash
224
+ adjust_for_stale_expires_in hash, retrieval_time
144
225
  hash
145
226
  end
146
227
 
@@ -152,6 +233,30 @@ module Google
152
233
  end
153
234
  hash
154
235
  end
236
+
237
+ def add_universe_domain_to hash
238
+ return if @universe_domain_overridden
239
+ universe_domain =
240
+ if disable_universe_domain_check
241
+ # TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
242
+ "googleapis.com"
243
+ else
244
+ Google::Cloud.env.lookup_metadata "universe", "universe-domain"
245
+ end
246
+ universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
247
+ hash["universe_domain"] = universe_domain.strip
248
+ end
249
+
250
+ # The response might have been cached, which means expires_in might be
251
+ # stale. Update it based on the time since the data was retrieved.
252
+ # We also ensure expires_in is conservative; subtracting at least 1
253
+ # second to offset any skew from metadata server latency.
254
+ def adjust_for_stale_expires_in hash, retrieval_time
255
+ return unless hash["expires_in"].is_a? Numeric
256
+ offset = 1 + (Process.clock_gettime(Process::CLOCK_MONOTONIC) - retrieval_time).round
257
+ hash["expires_in"] -= offset if offset.positive?
258
+ hash["expires_in"] = 0 if hash["expires_in"].negative?
259
+ end
155
260
  end
156
261
  end
157
262
  end
@@ -26,6 +26,14 @@ module Google
26
26
  # In most cases, it is subclassed by API-specific credential classes that
27
27
  # can be instantiated by clients.
28
28
  #
29
+ # **Important:** If you accept a credential configuration (credential
30
+ # JSON/File/Stream) from an external source for authentication to Google
31
+ # Cloud, you must validate it before providing it to any Google API or
32
+ # library. Providing an unvalidated credential configuration to Google APIs
33
+ # can compromise the security of your systems and data. For more
34
+ # information, refer to [Validate credential configurations from external
35
+ # sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
36
+ #
29
37
  # ## Options
30
38
  #
31
39
  # Credentials classes are configured with options that dictate default
@@ -299,6 +307,12 @@ module Google
299
307
  #
300
308
  attr_reader :quota_project_id
301
309
 
310
+ # @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
311
+ def disable_universe_domain_check
312
+ return false unless @client.respond_to? :disable_universe_domain_check
313
+ @client.disable_universe_domain_check
314
+ end
315
+
302
316
  # @private Delegate client methods to the client object.
303
317
  extend Forwardable
304
318
 
@@ -315,9 +329,6 @@ module Google
315
329
  # @return [String, Array<String>] The scope for this client. A scope is an access range
316
330
  # defined by the authorization server. The scope can be a single value or a list of values.
317
331
  #
318
- # @!attribute [r] target_audience
319
- # @return [String] The final target audience for ID tokens returned by this credential.
320
- #
321
332
  # @!attribute [r] issuer
322
333
  # @return [String] The issuer ID associated with this client.
323
334
  #
@@ -328,47 +339,74 @@ module Google
328
339
  # @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
329
340
  # suitable for passing as a closure.
330
341
  #
342
+ # @!attribute [r] target_audience
343
+ # @return [String] The final target audience for ID tokens returned by this credential.
344
+ #
331
345
  # @!attribute [rw] universe_domain
332
346
  # @return [String] The universe domain issuing these credentials.
333
347
  #
348
+ # @!attribute [rw] logger
349
+ # @return [Logger] The logger used to log credential operations such as token refresh.
350
+ #
334
351
  def_delegators :@client,
335
352
  :token_credential_uri, :audience,
336
353
  :scope, :issuer, :signing_key, :updater_proc, :target_audience,
337
- :universe_domain, :universe_domain=
354
+ :universe_domain, :universe_domain=, :logger, :logger=
338
355
 
339
356
  ##
340
357
  # Creates a new Credentials instance with the provided auth credentials, and with the default
341
358
  # values configured on the class.
342
359
  #
343
- # @param [String, Hash, Signet::OAuth2::Client] keyfile
344
- # The keyfile can be provided as one of the following:
360
+ # @param [String, Hash, Signet::OAuth2::Client] source_creds
361
+ # The source of credentials. It can be provided as one of the following:
362
+ #
363
+ # * The path to a JSON keyfile (as a `String`)
364
+ # * The contents of a JSON keyfile (as a `Hash`)
365
+ # * A `Signet::OAuth2::Client` credentials object
366
+ # * Any credentials object that supports the methods this wrapper delegates to an inner client.
367
+ #
368
+ # If this parameter is an object (`Signet::OAuth2::Client` or other) it will be used as an inner client.
369
+ # Otherwise the inner client will be constructed from the JSON keyfile or the contens of the hash.
345
370
  #
346
- # * The path to a JSON keyfile (as a +String+)
347
- # * The contents of a JSON keyfile (as a +Hash+)
348
- # * A +Signet::OAuth2::Client+ object
349
371
  # @param [Hash] options
350
- # The options for configuring the credentials instance. The following is supported:
372
+ # The options for configuring this wrapper credentials object and the inner client.
373
+ # The options hash is used in two ways:
351
374
  #
352
- # * +:scope+ - the scope for the client
353
- # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
354
- # * +:connection_builder+ - the connection builder to use for the client
355
- # * +:default_connection+ - the default connection to use for the client
375
+ # 1. **Configuring the wrapper object:** Some options are used to directly
376
+ # configure the wrapper `Credentials` instance. These include:
377
+ #
378
+ # * `:project_id` (and optionally `:project`) - the project identifier for the client
379
+ # * `:quota_project_id` - the quota project identifier for the client
380
+ # * `:logger` - the logger used to log credential operations such as token refresh.
356
381
  #
357
- def initialize keyfile, options = {}
358
- verify_keyfile_provided! keyfile
382
+ # 2. **Configuring the inner client:** When the `source_creds` parameter
383
+ # is a `String` or `Hash`, a new `Signet::OAuth2::Client` is created
384
+ # internally. The following options are used to configure this inner client:
385
+ #
386
+ # * `:scope` - the scope for the client
387
+ # * `:target_audience` - the target audience for the client
388
+ #
389
+ # Any other options in the `options` hash are passed directly to the
390
+ # inner client constructor. This allows you to configure additional
391
+ # parameters of the `Signet::OAuth2::Client`, such as connection parameters,
392
+ # timeouts, etc.
393
+ #
394
+ def initialize source_creds, options = {}
395
+ raise "The source credentials passed to Google::Auth::Credentials.new were nil." if source_creds.nil?
396
+
359
397
  options = symbolize_hash_keys options
360
398
  @project_id = options[:project_id] || options[:project]
361
399
  @quota_project_id = options[:quota_project_id]
362
- case keyfile
363
- when Google::Auth::BaseClient
364
- update_from_signet keyfile
400
+ case source_creds
401
+ when String
402
+ update_from_filepath source_creds, options
365
403
  when Hash
366
- update_from_hash keyfile, options
404
+ update_from_hash source_creds, options
367
405
  else
368
- update_from_filepath keyfile, options
406
+ update_from_client source_creds
369
407
  end
408
+ setup_logging logger: options.fetch(:logger, :default)
370
409
  @project_id ||= CredentialsLoader.load_gcloud_project_id
371
- @client.fetch_access_token! if @client.needs_access_token?
372
410
  @env_vars = nil
373
411
  @paths = nil
374
412
  @scope = nil
@@ -462,7 +500,8 @@ module Google
462
500
  audience: options[:audience] || audience
463
501
  }
464
502
  client = Google::Auth::DefaultCredentials.make_creds creds_input
465
- new client
503
+ options = options.select { |k, _v| k == :logger }
504
+ new client, options
466
505
  end
467
506
 
468
507
  private_class_method :from_env_vars,
@@ -470,14 +509,50 @@ module Google
470
509
  :from_application_default,
471
510
  :from_io
472
511
 
473
- protected
474
512
 
475
- # Verify that the keyfile argument is provided.
476
- def verify_keyfile_provided! keyfile
477
- return unless keyfile.nil?
478
- raise "The keyfile passed to Google::Auth::Credentials.new was nil."
513
+ # Creates a duplicate of these credentials. This method tries to create the duplicate of the
514
+ # wrapped credentials if they support duplication and use them as is if they don't.
515
+ #
516
+ # The wrapped credentials are typically `Signet::OAuth2::Client` objects and they keep
517
+ # the transient state (token, refresh token, etc). The duplication discards that state,
518
+ # allowing e.g. to get the token with a different scope.
519
+ #
520
+ # @param options [Hash] Overrides for the credentials parameters.
521
+ #
522
+ # The options hash is used in two ways:
523
+ #
524
+ # 1. **Configuring the duplicate of the wrapper object:** Some options are used to directly
525
+ # configure the wrapper `Credentials` instance. These include:
526
+ #
527
+ # * `:project_id` (and optionally `:project`) - the project identifier for the credentials
528
+ # * `:quota_project_id` - the quota project identifier for the credentials
529
+ #
530
+ # 2. **Configuring the duplicate of the inner client:** If the inner client supports duplication
531
+ # the options hash is passed to it. This allows for configuration of additional parameters,
532
+ # most importantly (but not limited to) the following:
533
+ #
534
+ # * `:scope` - the scope for the client
535
+ #
536
+ # @return [Credentials]
537
+ def duplicate options = {}
538
+ options = deep_hash_normalize options
539
+
540
+ options = {
541
+ project_id: @project_id,
542
+ quota_project_id: @quota_project_id
543
+ }.merge(options)
544
+
545
+ new_client = if @client.respond_to? :duplicate
546
+ @client.duplicate options
547
+ else
548
+ @client
549
+ end
550
+
551
+ self.class.new new_client, options
479
552
  end
480
553
 
554
+ protected
555
+
481
556
  # Verify that the keyfile argument is a file.
482
557
  def verify_keyfile_exists! keyfile
483
558
  exists = ::File.file? keyfile
@@ -519,11 +594,12 @@ module Google
519
594
  options
520
595
  end
521
596
 
522
- def update_from_signet client
597
+ def update_from_client client
523
598
  @project_id ||= client.project_id if client.respond_to? :project_id
524
599
  @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
525
600
  @client = client
526
601
  end
602
+ alias update_from_signet update_from_client
527
603
 
528
604
  def update_from_hash hash, options
529
605
  hash = stringify_hash_keys hash
@@ -543,6 +619,40 @@ module Google
543
619
  @quota_project_id ||= json["quota_project_id"]
544
620
  @client = init_client json, options
545
621
  end
622
+
623
+ def setup_logging logger: :default
624
+ return unless @client.respond_to? :logger=
625
+ logging_env = ENV["GOOGLE_SDK_RUBY_LOGGING_GEMS"].to_s.downcase
626
+ if ["false", "none"].include? logging_env
627
+ logger = nil
628
+ elsif @client.logger
629
+ logger = @client.logger
630
+ elsif logger == :default
631
+ logger = nil
632
+ if ["true", "all"].include?(logging_env) || logging_env.split(",").include?("googleauth")
633
+ formatter = Google::Logging::StructuredFormatter.new if Google::Cloud::Env.get.logging_agent_expected?
634
+ logger = Logger.new $stderr, progname: "googleauth", formatter: formatter
635
+ end
636
+ end
637
+ @client.logger = logger
638
+ end
639
+
640
+ private
641
+
642
+ # Convert all keys in this hash (nested) to symbols for uniform retrieval
643
+ def recursive_hash_normalize_keys val
644
+ if val.is_a? Hash
645
+ deep_hash_normalize val
646
+ else
647
+ val
648
+ end
649
+ end
650
+
651
+ def deep_hash_normalize old_hash
652
+ sym_hash = {}
653
+ old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
654
+ sym_hash
655
+ end
546
656
  end
547
657
  end
548
658
  end
@@ -19,6 +19,7 @@ require "googleauth/credentials_loader"
19
19
  require "googleauth/service_account"
20
20
  require "googleauth/user_refresh"
21
21
  require "googleauth/external_account"
22
+ require "googleauth/impersonated_service_account"
22
23
 
23
24
  module Google
24
25
  # Module Auth provides classes that provide Google-specific authorization
@@ -29,8 +30,18 @@ module Google
29
30
  class DefaultCredentials
30
31
  extend CredentialsLoader
31
32
 
32
- # override CredentialsLoader#make_creds to use the class determined by
33
+ ##
34
+ # Override CredentialsLoader#make_creds to use the class determined by
33
35
  # loading the json.
36
+ #
37
+ # **Important:** If you accept a credential configuration (credential
38
+ # JSON/File/Stream) from an external source for authentication to Google
39
+ # Cloud, you must validate it before providing it to any Google API or
40
+ # library. Providing an unvalidated credential configuration to Google
41
+ # APIs can compromise the security of your systems and data. For more
42
+ # information, refer to [Validate credential configurations from external
43
+ # sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
44
+ #
34
45
  def self.make_creds options = {}
35
46
  json_key_io = options[:json_key_io]
36
47
  if json_key_io
@@ -129,7 +129,7 @@ module Google
129
129
  if @client_id.nil? && @workforce_pool_user_project
130
130
  additional_options = { userProject: @workforce_pool_user_project }
131
131
  end
132
- @sts_client.exchange_token(
132
+ token_request = {
133
133
  audience: @audience,
134
134
  grant_type: STS_GRANT_TYPE,
135
135
  subject_token: retrieve_subject_token!,
@@ -137,10 +137,31 @@ module Google
137
137
  scopes: @service_account_impersonation_url ? IAM_SCOPE : @scope,
138
138
  requested_token_type: STS_REQUESTED_TOKEN_TYPE,
139
139
  additional_options: additional_options
140
- )
140
+ }
141
+ log_token_request token_request
142
+ @sts_client.exchange_token token_request
143
+ end
144
+
145
+ def log_token_request token_request
146
+ logger&.info do
147
+ Google::Logging::Message.from(
148
+ message: "Requesting access token from #{token_request[:grant_type]}",
149
+ "credentialsId" => object_id
150
+ )
151
+ end
152
+ logger&.debug do
153
+ digest = Digest::SHA256.hexdigest token_request[:subject_token].to_s
154
+ loggable_request = token_request.merge subject_token: "(sha256:#{digest})"
155
+ Google::Logging::Message.from(
156
+ message: "Request data",
157
+ "request" => loggable_request,
158
+ "credentialsId" => object_id
159
+ )
160
+ end
141
161
  end
142
162
 
143
163
  def get_impersonated_access_token token, _options = {}
164
+ log_impersonated_token_request token
144
165
  response = connection.post @service_account_impersonation_url do |req|
145
166
  req.headers["Authorization"] = "Bearer #{token}"
146
167
  req.headers["Content-Type"] = "application/json"
@@ -153,6 +174,16 @@ module Google
153
174
 
154
175
  MultiJson.load response.body
155
176
  end
177
+
178
+ def log_impersonated_token_request original_token
179
+ logger&.info do
180
+ digest = Digest::SHA256.hexdigest original_token
181
+ Google::Logging::Message.from(
182
+ message: "Requesting impersonated access token with original token (sha256:#{digest})",
183
+ "credentialsId" => object_id
184
+ )
185
+ end
186
+ end
156
187
  end
157
188
  end
158
189
  end
@@ -168,7 +168,6 @@ module Google
168
168
  aud: nil,
169
169
  azp: nil,
170
170
  iss: OIDC_ISSUERS
171
-
172
171
  verifier = Verifier.new key_source: oidc_key_source,
173
172
  aud: aud,
174
173
  azp: azp,
@@ -206,7 +205,6 @@ module Google
206
205
  aud: nil,
207
206
  azp: nil,
208
207
  iss: IAP_ISSUERS
209
-
210
208
  verifier = Verifier.new key_source: iap_key_source,
211
209
  aud: aud,
212
210
  azp: azp,