@@ -125,18 +125,24 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing):
125125
126126 These credentials relies on the default service account of a GCE instance.
127127
128- In order for this to work, the GCE instance must have been started with
128+ ID token can be requested from `GCE metadata server identity endpoint`_, IAM
129+ token endpoint or other token endpoints you specify. If metadata server
130+ identity endpoint is not used, the GCE instance must have been started with
129131 a service account that has access to the IAM Cloud API.
132+
133+ .. _GCE metadata server identity endpoint:
134+ https://cloud.google.com/compute/docs/instances/verifying-instance-identity
130135 """
131136
132137 def __init__ (
133138 self ,
134139 request ,
135140 target_audience ,
136- token_uri = _DEFAULT_TOKEN_URI ,
141+ token_uri = None ,
137142 additional_claims = None ,
138143 service_account_email = None ,
139144 signer = None ,
145+ use_metadata_identity_endpoint = False ,
140146 ):
141147 """
142148 Args:
@@ -154,29 +160,54 @@ def __init__(
154160 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
155161 In case the signer is specified, the request argument will be
156162 ignored.
163+ use_metadata_identity_endpoint (bool): Whether to use GCE metadata
164+ identity endpoint. For backward compatibility the default value
165+ is False. If set to True, ``token_uri``, ``additional_claims``,
166+ ``service_account_email``, ``signer`` argument should not be set;
167+ otherwise ValueError will be raised.
168+
169+ Raises:
170+ ValueError:
171+ If ``use_metadata_identity_endpoint`` is set to True, and one of
172+ ``token_uri``, ``additional_claims``, ``service_account_email``,
173+ ``signer`` arguments is set.
157174 """
158175 super (IDTokenCredentials , self ).__init__ ()
159176
160- if service_account_email is None :
161- sa_info = _metadata .get_service_account_info (request )
162- service_account_email = sa_info ["email" ]
163- self ._service_account_email = service_account_email
164-
165- if signer is None :
166- signer = iam .Signer (
167- request = request ,
168- credentials = Credentials (),
169- service_account_email = service_account_email ,
170- )
171- self ._signer = signer
172-
173- self ._token_uri = token_uri
177+ self ._use_metadata_identity_endpoint = use_metadata_identity_endpoint
174178 self ._target_audience = target_audience
175179
176- if additional_claims is not None :
177- self ._additional_claims = additional_claims
180+ if use_metadata_identity_endpoint :
181+ if token_uri or additional_claims or service_account_email or signer :
182+ raise ValueError (
183+ "If use_metadata_identity_endpoint is set, token_uri, "
184+ "additional_claims, service_account_email, signer arguments"
185+ " must not be set"
186+ )
187+ self ._token_uri = None
188+ self ._additional_claims = None
189+ self ._signer = None
190+
191+ if service_account_email is None :
192+ sa_info = _metadata .get_service_account_info (request )
193+ self ._service_account_email = sa_info ["email" ]
178194 else :
179- self ._additional_claims = {}
195+ self ._service_account_email = service_account_email
196+
197+ if not use_metadata_identity_endpoint :
198+ if signer is None :
199+ signer = iam .Signer (
200+ request = request ,
201+ credentials = Credentials (),
202+ service_account_email = self ._service_account_email ,
203+ )
204+ self ._signer = signer
205+ self ._token_uri = token_uri or _DEFAULT_TOKEN_URI
206+
207+ if additional_claims is not None :
208+ self ._additional_claims = additional_claims
209+ else :
210+ self ._additional_claims = {}
180211
181212 def with_target_audience (self , target_audience ):
182213 """Create a copy of these credentials with the specified target
@@ -190,14 +221,22 @@ def with_target_audience(self, target_audience):
190221 """
191222 # since the signer is already instantiated,
192223 # the request is not needed
193- return self .__class__ (
194- None ,
195- service_account_email = self ._service_account_email ,
196- token_uri = self ._token_uri ,
197- target_audience = target_audience ,
198- additional_claims = self ._additional_claims .copy (),
199- signer = self .signer ,
200- )
224+ if self ._use_metadata_identity_endpoint :
225+ return self .__class__ (
226+ None ,
227+ target_audience = target_audience ,
228+ use_metadata_identity_endpoint = True ,
229+ )
230+ else :
231+ return self .__class__ (
232+ None ,
233+ service_account_email = self ._service_account_email ,
234+ token_uri = self ._token_uri ,
235+ target_audience = target_audience ,
236+ additional_claims = self ._additional_claims .copy (),
237+ signer = self .signer ,
238+ use_metadata_identity_endpoint = False ,
239+ )
201240
202241 def _make_authorization_grant_assertion (self ):
203242 """Create the OAuth 2.0 assertion.
@@ -228,22 +267,76 @@ def _make_authorization_grant_assertion(self):
228267
229268 return token
230269
231- @_helpers .copy_docstring (credentials .Credentials )
270+ def _call_metadata_identity_endpoint (self , request ):
271+ """Request ID token from metadata identity endpoint.
272+
273+ Args:
274+ request (google.auth.transport.Request): The object used to make
275+ HTTP requests.
276+
277+ Raises:
278+ google.auth.exceptions.RefreshError: If the Compute Engine metadata
279+ service can't be reached or if the instance has no credentials.
280+ ValueError: If extracting expiry from the obtained ID token fails.
281+ """
282+ try :
283+ id_token = _metadata .get (
284+ request ,
285+ "instance/service-accounts/default/identity?audience={}&format=full" .format (
286+ self ._target_audience
287+ ),
288+ )
289+ except exceptions .TransportError as caught_exc :
290+ new_exc = exceptions .RefreshError (caught_exc )
291+ six .raise_from (new_exc , caught_exc )
292+
293+ _ , payload , _ , _ = jwt ._unverified_decode (id_token )
294+ return id_token , payload ["exp" ]
295+
232296 def refresh (self , request ):
233- assertion = self ._make_authorization_grant_assertion ()
234- access_token , expiry , _ = _client .id_token_jwt_grant (
235- request , self ._token_uri , assertion
236- )
237- self .token = access_token
238- self .expiry = expiry
297+ """Refreshes the ID token.
298+
299+ Args:
300+ request (google.auth.transport.Request): The object used to make
301+ HTTP requests.
302+
303+ Raises:
304+ google.auth.exceptions.RefreshError: If the credentials could
305+ not be refreshed.
306+ ValueError: If extracting expiry from the obtained ID token fails.
307+ """
308+ if self ._use_metadata_identity_endpoint :
309+ self .token , self .expiry = self ._call_metadata_identity_endpoint (request )
310+ else :
311+ assertion = self ._make_authorization_grant_assertion ()
312+ access_token , expiry , _ = _client .id_token_jwt_grant (
313+ request , self ._token_uri , assertion
314+ )
315+ self .token = access_token
316+ self .expiry = expiry
239317
240318 @property
241319 @_helpers .copy_docstring (credentials .Signing )
242320 def signer (self ):
243321 return self ._signer
244322
245- @_helpers .copy_docstring (credentials .Signing )
246323 def sign_bytes (self , message ):
324+ """Signs the given message.
325+
326+ Args:
327+ message (bytes): The message to sign.
328+
329+ Returns:
330+ bytes: The message's cryptographic signature.
331+
332+ Raises:
333+ ValueError:
334+ Signer is not available if metadata identity endpoint is used.
335+ """
336+ if self ._use_metadata_identity_endpoint :
337+ raise ValueError (
338+ "Signer is not available if metadata identity endpoint is used"
339+ )
247340 return self ._signer .sign (message )
248341
249342 @property
0 commit comments