Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit 67456b4

Browse files
kryzthovtheacodes
authored andcommitted
Add compute engine-based IDTokenCredentials (#236)
1 parent b61cecd commit 67456b4

3 files changed

Lines changed: 408 additions & 1 deletion

File tree

google/auth/compute_engine/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
"""Google Compute Engine authentication."""
1616

1717
from google.auth.compute_engine.credentials import Credentials
18+
from google.auth.compute_engine.credentials import IDTokenCredentials
1819

1920

2021
__all__ = [
21-
'Credentials'
22+
'Credentials',
23+
'IDTokenCredentials',
2224
]

google/auth/compute_engine/credentials.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@
1919
2020
"""
2121

22+
import datetime
23+
2224
import six
2325

26+
from google.auth import _helpers
2427
from google.auth import credentials
2528
from google.auth import exceptions
29+
from google.auth import iam
30+
from google.auth import jwt
2631
from google.auth.compute_engine import _metadata
32+
from google.oauth2 import _client
2733

2834

2935
class Credentials(credentials.ReadOnlyScoped, credentials.Credentials):
@@ -108,3 +114,126 @@ def service_account_email(self):
108114
def requires_scopes(self):
109115
"""False: Compute Engine credentials can not be scoped."""
110116
return False
117+
118+
119+
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
120+
_DEFAULT_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'
121+
122+
123+
class IDTokenCredentials(credentials.Credentials, credentials.Signing):
124+
"""Open ID Connect ID Token-based service account credentials.
125+
126+
These credentials relies on the default service account of a GCE instance.
127+
128+
In order for this to work, the GCE instance must have been started with
129+
a service account that has access to the IAM Cloud API.
130+
"""
131+
def __init__(self, request, target_audience,
132+
token_uri=_DEFAULT_TOKEN_URI,
133+
additional_claims=None,
134+
service_account_email=None):
135+
"""
136+
Args:
137+
request (google.auth.transport.Request): The object used to make
138+
HTTP requests.
139+
target_audience (str): The intended audience for these credentials,
140+
used when requesting the ID Token. The ID Token's ``aud`` claim
141+
will be set to this string.
142+
token_uri (str): The OAuth 2.0 Token URI.
143+
additional_claims (Mapping[str, str]): Any additional claims for
144+
the JWT assertion used in the authorization grant.
145+
service_account_email (str): Optional explicit service account to
146+
use to sign JWT tokens.
147+
By default, this is the default GCE service account.
148+
"""
149+
super(IDTokenCredentials, self).__init__()
150+
151+
if service_account_email is None:
152+
sa_info = _metadata.get_service_account_info(request)
153+
service_account_email = sa_info['email']
154+
self._service_account_email = service_account_email
155+
156+
self._signer = iam.Signer(
157+
request=request,
158+
credentials=Credentials(),
159+
service_account_email=service_account_email)
160+
161+
self._token_uri = token_uri
162+
self._target_audience = target_audience
163+
164+
if additional_claims is not None:
165+
self._additional_claims = additional_claims
166+
else:
167+
self._additional_claims = {}
168+
169+
def with_target_audience(self, target_audience):
170+
"""Create a copy of these credentials with the specified target
171+
audience.
172+
Args:
173+
target_audience (str): The intended audience for these credentials,
174+
used when requesting the ID Token.
175+
Returns:
176+
google.auth.service_account.IDTokenCredentials: A new credentials
177+
instance.
178+
"""
179+
return self.__class__(
180+
self._signer,
181+
service_account_email=self._service_account_email,
182+
token_uri=self._token_uri,
183+
target_audience=target_audience,
184+
additional_claims=self._additional_claims.copy())
185+
186+
def _make_authorization_grant_assertion(self):
187+
"""Create the OAuth 2.0 assertion.
188+
This assertion is used during the OAuth 2.0 grant to acquire an
189+
ID token.
190+
Returns:
191+
bytes: The authorization grant assertion.
192+
"""
193+
now = _helpers.utcnow()
194+
lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
195+
expiry = now + lifetime
196+
197+
payload = {
198+
'iat': _helpers.datetime_to_secs(now),
199+
'exp': _helpers.datetime_to_secs(expiry),
200+
# The issuer must be the service account email.
201+
'iss': self.service_account_email,
202+
# The audience must be the auth token endpoint's URI
203+
'aud': self._token_uri,
204+
# The target audience specifies which service the ID token is
205+
# intended for.
206+
'target_audience': self._target_audience
207+
}
208+
209+
payload.update(self._additional_claims)
210+
211+
token = jwt.encode(self._signer, payload)
212+
213+
return token
214+
215+
@_helpers.copy_docstring(credentials.Credentials)
216+
def refresh(self, request):
217+
assertion = self._make_authorization_grant_assertion()
218+
access_token, expiry, _ = _client.id_token_jwt_grant(
219+
request, self._token_uri, assertion)
220+
self.token = access_token
221+
self.expiry = expiry
222+
223+
@property
224+
@_helpers.copy_docstring(credentials.Signing)
225+
def signer(self):
226+
return self._signer
227+
228+
@_helpers.copy_docstring(credentials.Signing)
229+
def sign_bytes(self, message):
230+
return self._signer.sign(message)
231+
232+
@property
233+
def service_account_email(self):
234+
"""The service account email."""
235+
return self._service_account_email
236+
237+
@property
238+
def signer_email(self):
239+
return self._service_account_email

0 commit comments

Comments
 (0)