googleauth 1.8.0 → 1.15.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 +117 -0
- data/Credentials.md +106 -0
- data/Errors.md +152 -0
- data/README.md +49 -1
- data/lib/googleauth/api_key.rb +164 -0
- data/lib/googleauth/application_default.rb +6 -8
- data/lib/googleauth/base_client.rb +21 -4
- data/lib/googleauth/bearer_token.rb +162 -0
- data/lib/googleauth/client_id.rb +9 -6
- data/lib/googleauth/compute_engine.rb +231 -49
- data/lib/googleauth/credentials.rb +187 -58
- data/lib/googleauth/credentials_loader.rb +11 -20
- data/lib/googleauth/default_credentials.rb +29 -8
- data/lib/googleauth/errors.rb +117 -0
- data/lib/googleauth/external_account/aws_credentials.rb +85 -18
- data/lib/googleauth/external_account/base_credentials.rb +67 -6
- data/lib/googleauth/external_account/external_account_utils.rb +15 -4
- data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
- data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
- data/lib/googleauth/external_account.rb +32 -7
- data/lib/googleauth/helpers/connection.rb +7 -1
- data/lib/googleauth/iam.rb +19 -3
- data/lib/googleauth/id_tokens/errors.rb +13 -7
- data/lib/googleauth/id_tokens/key_sources.rb +13 -7
- data/lib/googleauth/id_tokens/verifier.rb +2 -3
- data/lib/googleauth/id_tokens.rb +4 -6
- data/lib/googleauth/impersonated_service_account.rb +329 -0
- data/lib/googleauth/json_key_reader.rb +13 -3
- data/lib/googleauth/oauth2/sts_client.rb +9 -4
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +84 -104
- data/lib/googleauth/service_account_jwt_header.rb +187 -0
- data/lib/googleauth/signet.rb +169 -4
- data/lib/googleauth/token_store.rb +3 -3
- data/lib/googleauth/user_authorizer.rb +89 -11
- data/lib/googleauth/user_refresh.rb +72 -9
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +65 -17
- data/lib/googleauth.rb +8 -0
- metadata +45 -13
@@ -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
|
-
|
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
|
@@ -63,17 +72,25 @@ module Google
|
|
63
72
|
end
|
64
73
|
|
65
74
|
def expires_within?
|
66
|
-
raise
|
75
|
+
raise NoMethodError, "expires_within? not implemented"
|
76
|
+
end
|
77
|
+
|
78
|
+
# The logger used to log operations on this client, such as token refresh.
|
79
|
+
attr_accessor :logger
|
80
|
+
|
81
|
+
# @private
|
82
|
+
def principal
|
83
|
+
raise NoMethodError, "principal not implemented"
|
67
84
|
end
|
68
85
|
|
69
86
|
private
|
70
87
|
|
71
88
|
def token_type
|
72
|
-
raise
|
89
|
+
raise NoMethodError, "token_type not implemented"
|
73
90
|
end
|
74
91
|
|
75
92
|
def fetch_access_token!
|
76
|
-
raise
|
93
|
+
raise NoMethodError, "fetch_access_token! not implemented"
|
77
94
|
end
|
78
95
|
end
|
79
96
|
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
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 "googleauth/base_client"
|
16
|
+
require "googleauth/errors"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Auth
|
20
|
+
##
|
21
|
+
# Implementation of Bearer Token authentication scenario.
|
22
|
+
#
|
23
|
+
# Bearer tokens are strings representing an authorization grant.
|
24
|
+
# They can be OAuth2 ("ya.29") tokens, JWTs, IDTokens -- anything
|
25
|
+
# that is sent as a `Bearer` in an `Authorization` header.
|
26
|
+
#
|
27
|
+
# Not all 'authentication' strings can be used with this class,
|
28
|
+
# e.g. an API key cannot since API keys are sent in a
|
29
|
+
# `x-goog-api-key` header or as a query parameter.
|
30
|
+
#
|
31
|
+
# This class should be used when the end-user is managing the
|
32
|
+
# authentication token separately, e.g. with a separate service.
|
33
|
+
# This means that tasks like tracking the lifetime of and
|
34
|
+
# refreshing the token are outside the scope of this class.
|
35
|
+
#
|
36
|
+
# There is no JSON representation for this type of credentials.
|
37
|
+
# If the end-user has credentials in JSON format they should typically
|
38
|
+
# use the corresponding credentials type, e.g. ServiceAccountCredentials
|
39
|
+
# with the service account JSON.
|
40
|
+
#
|
41
|
+
class BearerTokenCredentials
|
42
|
+
include Google::Auth::BaseClient
|
43
|
+
|
44
|
+
# @private Authorization header name
|
45
|
+
AUTH_METADATA_KEY = Google::Auth::BaseClient::AUTH_METADATA_KEY
|
46
|
+
|
47
|
+
# @return [String] The token to be sent as a part of Bearer claim
|
48
|
+
attr_reader :token
|
49
|
+
# The following aliasing is needed for BaseClient since it sends :token_type
|
50
|
+
alias bearer_token token
|
51
|
+
|
52
|
+
# @return [Time, nil] The token expiration time provided by the end-user.
|
53
|
+
attr_reader :expires_at
|
54
|
+
|
55
|
+
# @return [String] The universe domain of the universe
|
56
|
+
# this token is for
|
57
|
+
attr_accessor :universe_domain
|
58
|
+
|
59
|
+
class << self
|
60
|
+
# Create the BearerTokenCredentials.
|
61
|
+
#
|
62
|
+
# @param [Hash] options The credentials options
|
63
|
+
# @option options [String] :token The bearer token to use.
|
64
|
+
# @option options [Time, Numeric, nil] :expires_at The token expiration time provided by the end-user.
|
65
|
+
# Optional, for the end-user's convenience. Can be a Time object, a number of seconds since epoch.
|
66
|
+
# If `expires_at` is `nil`, it is treated as "token never expires".
|
67
|
+
# @option options [String] :universe_domain The universe domain of the universe
|
68
|
+
# this token is for (defaults to googleapis.com)
|
69
|
+
# @return [Google::Auth::BearerTokenCredentials]
|
70
|
+
def make_creds options = {}
|
71
|
+
new options
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Initialize the BearerTokenCredentials.
|
76
|
+
#
|
77
|
+
# @param [Hash] options The credentials options
|
78
|
+
# @option options [String] :token The bearer token to use.
|
79
|
+
# @option options [Time, Numeric, nil] :expires_at The token expiration time provided by the end-user.
|
80
|
+
# Optional, for the end-user's convenience. Can be a Time object, a number of seconds since epoch.
|
81
|
+
# If `expires_at` is `nil`, it is treated as "token never expires".
|
82
|
+
# @option options [String] :universe_domain The universe domain of the universe
|
83
|
+
# this token is for (defaults to googleapis.com)
|
84
|
+
# @raise [ArgumentError] If the bearer token is nil or empty
|
85
|
+
def initialize options = {}
|
86
|
+
raise ArgumentError, "Bearer token must be provided" if options[:token].nil? || options[:token].empty?
|
87
|
+
@token = options[:token]
|
88
|
+
@expires_at = case options[:expires_at]
|
89
|
+
when Time
|
90
|
+
options[:expires_at]
|
91
|
+
when Numeric
|
92
|
+
Time.at options[:expires_at]
|
93
|
+
end
|
94
|
+
|
95
|
+
@universe_domain = options[:universe_domain] || "googleapis.com"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Determines if the credentials object has expired.
|
99
|
+
#
|
100
|
+
# @param [Numeric] seconds The optional timeout in seconds.
|
101
|
+
# @return [Boolean] True if the token has expired, false otherwise, or
|
102
|
+
# if the expires_at was not provided.
|
103
|
+
def expires_within? seconds
|
104
|
+
return false if @expires_at.nil? # Treat nil expiration as "never expires"
|
105
|
+
Time.now + seconds >= @expires_at
|
106
|
+
end
|
107
|
+
|
108
|
+
# Creates a duplicate of these credentials.
|
109
|
+
#
|
110
|
+
# @param [Hash] options Additional options for configuring the credentials
|
111
|
+
# @option options [String] :token The bearer token to use.
|
112
|
+
# @option options [Time, Numeric] :expires_at The token expiration time. Can be a Time
|
113
|
+
# object or a number of seconds since epoch.
|
114
|
+
# @option options [String] :universe_domain The universe domain (defaults to googleapis.com)
|
115
|
+
# @return [Google::Auth::BearerTokenCredentials]
|
116
|
+
def duplicate options = {}
|
117
|
+
self.class.new(
|
118
|
+
token: options[:token] || @token,
|
119
|
+
expires_at: options[:expires_at] || @expires_at,
|
120
|
+
universe_domain: options[:universe_domain] || @universe_domain
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
# For credentials that are initialized with a token without a principal,
|
125
|
+
# the type of that token should be returned as a principal instead
|
126
|
+
# @private
|
127
|
+
# @return [Symbol] the token type in lieu of the principal
|
128
|
+
def principal
|
129
|
+
token_type
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
##
|
135
|
+
# BearerTokenCredentials do not support fetching a new token.
|
136
|
+
#
|
137
|
+
# If the token has an expiration time and is expired, this method will
|
138
|
+
# raise an error.
|
139
|
+
#
|
140
|
+
# @param [Hash] _options Options for fetching a new token (not used).
|
141
|
+
# @return [nil] Always returns nil.
|
142
|
+
# @raise [Google::Auth::CredentialsError] If the token is expired.
|
143
|
+
def fetch_access_token! _options = {}
|
144
|
+
if @expires_at && Time.now >= @expires_at
|
145
|
+
raise CredentialsError.with_details(
|
146
|
+
"Bearer token has expired.",
|
147
|
+
credential_type_name: self.class.name,
|
148
|
+
principal: principal
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def token_type
|
158
|
+
:bearer_token
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/lib/googleauth/client_id.rb
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
require "multi_json"
|
16
16
|
require "googleauth/credentials_loader"
|
17
|
+
require "googleauth/errors"
|
17
18
|
|
18
19
|
module Google
|
19
20
|
module Auth
|
@@ -62,11 +63,11 @@ module Google
|
|
62
63
|
# @note Direct instantiation is discouraged to avoid embedding IDs
|
63
64
|
# and secrets in source. See {#from_file} to load from
|
64
65
|
# `client_secrets.json` files.
|
66
|
+
# @raise [Google::Auth::InitializationError] If id or secret is nil
|
65
67
|
#
|
66
68
|
def initialize id, secret
|
67
|
-
|
68
|
-
raise "Client
|
69
|
-
raise "Client secret can not be nil" if secret.nil?
|
69
|
+
raise InitializationError, "Client id can not be nil" if id.nil?
|
70
|
+
raise InitializationError, "Client secret can not be nil" if secret.nil?
|
70
71
|
@id = id
|
71
72
|
@secret = secret
|
72
73
|
end
|
@@ -78,9 +79,10 @@ module Google
|
|
78
79
|
# @param [String, File] file
|
79
80
|
# Path of file to read from
|
80
81
|
# @return [Google::Auth::ClientID]
|
82
|
+
# @raise [Google::Auth::InitializationError] If file is nil
|
81
83
|
#
|
82
84
|
def self.from_file file
|
83
|
-
raise "File can not be nil." if file.nil?
|
85
|
+
raise InitializationError, "File can not be nil." if file.nil?
|
84
86
|
File.open file.to_s do |f|
|
85
87
|
json = f.read
|
86
88
|
config = MultiJson.load json
|
@@ -95,11 +97,12 @@ module Google
|
|
95
97
|
# @param [hash] config
|
96
98
|
# Parsed contents of the JSON file
|
97
99
|
# @return [Google::Auth::ClientID]
|
100
|
+
# @raise [Google::Auth::InitializationError] If config is nil or missing required elements
|
98
101
|
#
|
99
102
|
def self.from_hash config
|
100
|
-
raise "Hash can not be nil." if config.nil?
|
103
|
+
raise InitializationError, "Hash can not be nil." if config.nil?
|
101
104
|
raw_detail = config[INSTALLED_APP] || config[WEB_APP]
|
102
|
-
raise MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
|
105
|
+
raise InitializationError, MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
|
103
106
|
ClientId.new raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]
|
104
107
|
end
|
105
108
|
end
|
@@ -12,7 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require "
|
15
|
+
require "google-cloud-env"
|
16
|
+
require "googleauth/errors"
|
16
17
|
require "googleauth/signet"
|
17
18
|
|
18
19
|
module Google
|
@@ -33,93 +34,274 @@ module Google
|
|
33
34
|
# Extends Signet::OAuth2::Client so that the auth token is obtained from
|
34
35
|
# the GCE metadata server.
|
35
36
|
class GCECredentials < Signet::OAuth2::Client
|
36
|
-
#
|
37
|
-
# systems.
|
37
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
38
38
|
DEFAULT_METADATA_HOST = "169.254.169.254".freeze
|
39
39
|
|
40
|
-
# @private Unused and deprecated
|
40
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
41
41
|
COMPUTE_AUTH_TOKEN_URI =
|
42
42
|
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
43
|
-
# @private Unused and deprecated
|
43
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
44
44
|
COMPUTE_ID_TOKEN_URI =
|
45
45
|
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
46
|
-
# @private Unused and deprecated
|
46
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
47
47
|
COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
|
48
48
|
|
49
|
-
@on_gce_cache = {}
|
50
|
-
|
51
49
|
class << self
|
50
|
+
# @private Unused and deprecated
|
52
51
|
def metadata_host
|
53
52
|
ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST
|
54
53
|
end
|
55
54
|
|
55
|
+
# @private Unused and deprecated
|
56
56
|
def compute_check_uri
|
57
57
|
"http://#{metadata_host}".freeze
|
58
58
|
end
|
59
59
|
|
60
|
+
# @private Unused and deprecated
|
60
61
|
def compute_auth_token_uri
|
61
62
|
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
62
63
|
end
|
63
64
|
|
65
|
+
# @private Unused and deprecated
|
64
66
|
def compute_id_token_uri
|
65
67
|
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
66
68
|
end
|
67
69
|
|
68
70
|
# Detect if this appear to be a GCE instance, by checking if metadata
|
69
71
|
# is available.
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@on_gce_cache.fetch options do
|
74
|
-
@on_gce_cache[options] = begin
|
75
|
-
# TODO: This should use google-cloud-env instead.
|
76
|
-
c = options[:connection] || Faraday.default_connection
|
77
|
-
headers = { "Metadata-Flavor" => "Google" }
|
78
|
-
resp = c.get compute_check_uri, nil, headers do |req|
|
79
|
-
req.options.timeout = 1.0
|
80
|
-
req.options.open_timeout = 0.1
|
81
|
-
end
|
82
|
-
return false unless resp.status == 200
|
83
|
-
resp.headers["Metadata-Flavor"] == "Google"
|
84
|
-
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
85
|
-
false
|
86
|
-
end
|
87
|
-
end
|
72
|
+
# The parameters are deprecated and unused.
|
73
|
+
def on_gce? _options = {}, _reload = false # rubocop:disable Style/OptionalBooleanParameter
|
74
|
+
Google::Cloud.env.metadata?
|
88
75
|
end
|
89
76
|
|
90
77
|
def reset_cache
|
91
|
-
|
78
|
+
Google::Cloud.env.compute_metadata.reset_existence!
|
79
|
+
Google::Cloud.env.compute_metadata.cache.expire_all!
|
92
80
|
end
|
93
81
|
alias unmemoize_all reset_cache
|
94
82
|
end
|
95
83
|
|
84
|
+
# @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
|
85
|
+
attr_accessor :disable_universe_domain_check
|
86
|
+
|
87
|
+
# Construct a GCECredentials
|
88
|
+
def initialize options = {}
|
89
|
+
# Override the constructor to remember whether the universe domain was
|
90
|
+
# overridden by a constructor argument.
|
91
|
+
@universe_domain_overridden = options["universe_domain"] || options[:universe_domain]
|
92
|
+
# TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
|
93
|
+
@disable_universe_domain_check = true
|
94
|
+
super options
|
95
|
+
end
|
96
|
+
|
97
|
+
# Creates a duplicate of these credentials
|
98
|
+
# without the Signet::OAuth2::Client-specific
|
99
|
+
# transient state (e.g. cached tokens)
|
100
|
+
#
|
101
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
102
|
+
# The following keys are recognized in addition to keys in the
|
103
|
+
# Signet::OAuth2::Client
|
104
|
+
# * `:universe_domain_overridden` Whether the universe domain was
|
105
|
+
# overriden during credentials creation
|
106
|
+
def duplicate options = {}
|
107
|
+
options = deep_hash_normalize options
|
108
|
+
super(
|
109
|
+
{
|
110
|
+
universe_domain_overridden: @universe_domain_overridden
|
111
|
+
}.merge(options)
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @private
|
116
|
+
# Overrides universe_domain getter to fetch lazily if it hasn't been
|
117
|
+
# fetched yet. This is necessary specifically for Compute Engine because
|
118
|
+
# the universe comes from the metadata service, and isn't known
|
119
|
+
# immediately on credential construction. All other credential types read
|
120
|
+
# the universe from their json key or other immediate input.
|
121
|
+
def universe_domain
|
122
|
+
value = super
|
123
|
+
return value unless value.nil?
|
124
|
+
fetch_access_token!
|
125
|
+
super
|
126
|
+
end
|
127
|
+
|
96
128
|
# Overrides the super class method to change how access tokens are
|
97
129
|
# fetched.
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
130
|
+
#
|
131
|
+
# @param [Hash] _options Options for token fetch (not used)
|
132
|
+
# @return [Hash] The token data hash
|
133
|
+
# @raise [Google::Auth::UnexpectedStatusError] On unexpected HTTP status codes
|
134
|
+
# @raise [Google::Auth::AuthorizationError] If metadata server is unavailable or returns error
|
135
|
+
def fetch_access_token _options = {}
|
136
|
+
query, entry = build_metadata_request_params
|
137
|
+
begin
|
138
|
+
log_fetch_query
|
139
|
+
resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query
|
140
|
+
log_fetch_resp resp
|
141
|
+
handle_metadata_response resp
|
142
|
+
rescue Google::Cloud::Env::MetadataServerNotResponding => e
|
143
|
+
log_fetch_err e
|
144
|
+
raise AuthorizationError.with_details(
|
145
|
+
e.message,
|
146
|
+
credential_type_name: self.class.name,
|
147
|
+
principal: principal
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Destructively updates these credentials.
|
153
|
+
#
|
154
|
+
# This method is called by `Signet::OAuth2::Client`'s constructor
|
155
|
+
#
|
156
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
157
|
+
# The following keys are recognized in addition to keys in the
|
158
|
+
# Signet::OAuth2::Client
|
159
|
+
# * `:universe_domain_overridden` Whether the universe domain was
|
160
|
+
# overriden during credentials creation
|
161
|
+
# @return [Google::Auth::GCECredentials]
|
162
|
+
def update! options = {}
|
163
|
+
# Normalize all keys to symbols to allow indifferent access.
|
164
|
+
options = deep_hash_normalize options
|
165
|
+
|
166
|
+
@universe_domain_overridden = options[:universe_domain_overridden] if options.key? :universe_domain_overridden
|
167
|
+
|
168
|
+
super(options)
|
169
|
+
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns the principal identifier for GCE credentials
|
174
|
+
# @private
|
175
|
+
# @return [Symbol] :gce to represent Google Compute Engine identity
|
176
|
+
def principal
|
177
|
+
:gce_metadata
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# @private
|
183
|
+
# Builds query parameters and endpoint for metadata request
|
184
|
+
# @return [Array] The query parameters and endpoint path
|
185
|
+
def build_metadata_request_params
|
186
|
+
query, entry =
|
187
|
+
if token_type == :id_token
|
188
|
+
[{ "audience" => target_audience, "format" => "full" }, "service-accounts/default/identity"]
|
189
|
+
else
|
190
|
+
[{}, "service-accounts/default/token"]
|
191
|
+
end
|
192
|
+
query[:scopes] = Array(scope).join "," if scope
|
193
|
+
[query, entry]
|
194
|
+
end
|
195
|
+
|
196
|
+
# @private
|
197
|
+
# Handles the response from the metadata server
|
198
|
+
# @param [Google::Cloud::Env::MetadataResponse] resp The metadata server response
|
199
|
+
# @return [Hash] The token hash on success
|
200
|
+
# @raise [Google::Auth::UnexpectedStatusError, Google::Auth::AuthorizationError] On error
|
201
|
+
def handle_metadata_response resp
|
202
|
+
case resp.status
|
203
|
+
when 200
|
204
|
+
build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
|
205
|
+
when 403, 500
|
206
|
+
raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
207
|
+
when 404
|
208
|
+
raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
|
209
|
+
else
|
210
|
+
raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def log_fetch_query
|
215
|
+
if token_type == :id_token
|
216
|
+
logger&.info do
|
217
|
+
Google::Logging::Message.from(
|
218
|
+
message: "Requesting id token from MDS with aud=#{target_audience}",
|
219
|
+
"credentialsId" => object_id
|
220
|
+
)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
logger&.info do
|
224
|
+
Google::Logging::Message.from(
|
225
|
+
message: "Requesting access token from MDS",
|
226
|
+
"credentialsId" => object_id
|
227
|
+
)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def log_fetch_resp resp
|
233
|
+
logger&.info do
|
234
|
+
Google::Logging::Message.from(
|
235
|
+
message: "Received #{resp.status} from MDS",
|
236
|
+
"credentialsId" => object_id
|
237
|
+
)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def log_fetch_err _err
|
242
|
+
logger&.info do
|
243
|
+
Google::Logging::Message.from(
|
244
|
+
message: "MDS did not respond to token request",
|
245
|
+
"credentialsId" => object_id
|
246
|
+
)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Constructs a token hash from the metadata server response
|
251
|
+
#
|
252
|
+
# @private
|
253
|
+
# @param [String] body The response body from the metadata server
|
254
|
+
# @param [String] content_type The content type of the response
|
255
|
+
# @param [Float] retrieval_time The monotonic time when the response was retrieved
|
256
|
+
#
|
257
|
+
# @return [Hash] A hash containing:
|
258
|
+
# - access_token/id_token: The actual token depending on what was requested
|
259
|
+
# - token_type: The type of token (usually "Bearer")
|
260
|
+
# - expires_in: Seconds until token expiration (adjusted for freshness)
|
261
|
+
# - universe_domain: The universe domain for the token (if not overridden)
|
262
|
+
def build_token_hash body, content_type, retrieval_time
|
263
|
+
hash =
|
264
|
+
if ["text/html", "application/text"].include? content_type
|
265
|
+
parse_encoded_token body
|
118
266
|
else
|
119
|
-
|
120
|
-
raise Signet::AuthorizationError, msg
|
267
|
+
Signet::OAuth2.parse_credentials body, content_type
|
121
268
|
end
|
269
|
+
add_universe_domain_to hash
|
270
|
+
adjust_for_stale_expires_in hash, retrieval_time
|
271
|
+
hash
|
272
|
+
end
|
273
|
+
|
274
|
+
def parse_encoded_token body
|
275
|
+
hash = { token_type.to_s => body }
|
276
|
+
if token_type == :id_token
|
277
|
+
expires_at = expires_at_from_id_token body
|
278
|
+
hash["expires_at"] = expires_at if expires_at
|
122
279
|
end
|
280
|
+
hash
|
281
|
+
end
|
282
|
+
|
283
|
+
def add_universe_domain_to hash
|
284
|
+
return if @universe_domain_overridden
|
285
|
+
universe_domain =
|
286
|
+
if disable_universe_domain_check
|
287
|
+
# TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
|
288
|
+
"googleapis.com"
|
289
|
+
else
|
290
|
+
Google::Cloud.env.lookup_metadata "universe", "universe-domain"
|
291
|
+
end
|
292
|
+
universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
|
293
|
+
hash["universe_domain"] = universe_domain.strip
|
294
|
+
end
|
295
|
+
|
296
|
+
# The response might have been cached, which means expires_in might be
|
297
|
+
# stale. Update it based on the time since the data was retrieved.
|
298
|
+
# We also ensure expires_in is conservative; subtracting at least 1
|
299
|
+
# second to offset any skew from metadata server latency.
|
300
|
+
def adjust_for_stale_expires_in hash, retrieval_time
|
301
|
+
return unless hash["expires_in"].is_a? Numeric
|
302
|
+
offset = 1 + (Process.clock_gettime(Process::CLOCK_MONOTONIC) - retrieval_time).round
|
303
|
+
hash["expires_in"] -= offset if offset.positive?
|
304
|
+
hash["expires_in"] = 0 if hash["expires_in"].negative?
|
123
305
|
end
|
124
306
|
end
|
125
307
|
end
|