googleauth 1.11.1 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
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,