Skip to content

Commit 929bd1f

Browse files
committed
implement trust boundary in compute_engine to support GCE instances.
1 parent fb08254 commit 929bd1f

File tree

4 files changed

+68
-4
lines changed

4 files changed

+68
-4
lines changed

google/auth/compute_engine/credentials.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@
3030
from google.auth.compute_engine import _metadata
3131
from google.oauth2 import _client
3232

33+
_TRUST_BOUNDARY_LOOKUP_ENDPOINT = (
34+
"https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}/allowedLocations"
35+
)
36+
3337

3438
class Credentials(
3539
credentials.Scoped,
3640
credentials.CredentialsWithQuotaProject,
3741
credentials.CredentialsWithUniverseDomain,
42+
credentials.CredentialsWithTrustBoundary,
3843
):
3944
"""Compute Engine Credentials.
4045
@@ -61,6 +66,7 @@ def __init__(
6166
scopes=None,
6267
default_scopes=None,
6368
universe_domain=None,
69+
trust_boundary=None,
6470
):
6571
"""
6672
Args:
@@ -76,6 +82,7 @@ def __init__(
7682
provided or None, credential will attempt to fetch the value
7783
from metadata server. If metadata server doesn't have universe
7884
domain endpoint, then the default googleapis.com will be used.
85+
trust_boundary (Mapping[str,str]): A credential trust boundary.
7986
"""
8087
super(Credentials, self).__init__()
8188
self._service_account_email = service_account_email
@@ -86,6 +93,7 @@ def __init__(
8693
if universe_domain:
8794
self._universe_domain = universe_domain
8895
self._universe_domain_cached = True
96+
self._trust_boundary = trust_boundary
8997

9098
def _metric_header_for_usage(self):
9199
return metrics.CRED_TYPE_SA_MDS
@@ -111,6 +119,33 @@ def refresh(self, request):
111119
new_exc = exceptions.RefreshError(caught_exc)
112120
raise new_exc from caught_exc
113121

122+
self._refresh_trust_boundary(request)
123+
124+
def _build_trust_boundary_lookup_url(self):
125+
"""Builds and returns the URL for the trust boundary lookup API for GCE."""
126+
# If the service account email is 'default', we need to get the
127+
# actual email address from the metadata server.
128+
if self._service_account_email == "default":
129+
from google.auth.transport import requests as google_auth_requests
130+
131+
request = google_auth_requests.Request()
132+
try:
133+
info = _metadata.get_service_account_info(request, "default")
134+
# Cache the fetched email so we don't have to do this again.
135+
self._service_account_email = info["email"]
136+
137+
except exceptions.TransportError as e:
138+
# If fetching the service account email fails due to a transport error,
139+
# it means we cannot build the trust boundary lookup URL.
140+
# Wrap this in a RefreshError so it's caught by _refresh_trust_boundary.
141+
raise exceptions.RefreshError(
142+
f"Failed to get service account email for trust boundary lookup: {e}"
143+
) from e
144+
145+
return _TRUST_BOUNDARY_LOOKUP_ENDPOINT.format(
146+
self.universe_domain, self.service_account_email
147+
)
148+
114149
@property
115150
def service_account_email(self):
116151
"""The service account email.
@@ -152,6 +187,7 @@ def with_quota_project(self, quota_project_id):
152187
quota_project_id=quota_project_id,
153188
scopes=self._scopes,
154189
default_scopes=self._default_scopes,
190+
trust_boundary=self._trust_boundary,
155191
)
156192
creds._universe_domain = self._universe_domain
157193
creds._universe_domain_cached = self._universe_domain_cached
@@ -167,6 +203,7 @@ def with_scopes(self, scopes, default_scopes=None):
167203
default_scopes=default_scopes,
168204
service_account_email=self._service_account_email,
169205
quota_project_id=self._quota_project_id,
206+
trust_boundary=self._trust_boundary,
170207
)
171208
creds._universe_domain = self._universe_domain
172209
creds._universe_domain_cached = self._universe_domain_cached
@@ -179,9 +216,23 @@ def with_universe_domain(self, universe_domain):
179216
default_scopes=self._default_scopes,
180217
service_account_email=self._service_account_email,
181218
quota_project_id=self._quota_project_id,
219+
trust_boundary=self._trust_boundary,
182220
universe_domain=universe_domain,
183221
)
184222

223+
@_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary)
224+
def with_trust_boundary(self, trust_boundary):
225+
creds = self.__class__(
226+
service_account_email=self._service_account_email,
227+
quota_project_id=self._quota_project_id,
228+
scopes=self._scopes,
229+
default_scopes=self._default_scopes,
230+
trust_boundary=trust_boundary,
231+
)
232+
creds._universe_domain = self._universe_domain
233+
creds._universe_domain_cached = self._universe_domain_cached
234+
return creds
235+
185236

186237
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
187238
_DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token"

google/auth/impersonated_credentials.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,18 @@ def _make_copy(self):
432432
lifetime=self._lifetime,
433433
quota_project_id=self._quota_project_id,
434434
iam_endpoint_override=self._iam_endpoint_override,
435+
trust_boundary=self._trust_boundary,
435436
)
436437
cred._cred_file_path = self._cred_file_path
437438
return cred
438439

440+
@_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary)
441+
def with_trust_boundary(self, trust_boundary):
442+
"""Returns a copy of these credentials with a modified trust boundary."""
443+
cred = self._make_copy()
444+
cred._trust_boundary = trust_boundary
445+
return cred
446+
439447
@_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
440448
def with_quota_project(self, quota_project_id):
441449
cred = self._make_copy()
@@ -519,9 +527,7 @@ def from_impersonated_service_account_info(cls, info, scopes=None):
519527

520528

521529
class IDTokenCredentials(credentials.CredentialsWithQuotaProject):
522-
"""Open ID Connect ID Token-based service account credentials.
523-
524-
"""
530+
"""Open ID Connect ID Token-based service account credentials."""
525531

526532
def __init__(
527533
self,

google/oauth2/_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ def _lookup_trust_boundary_request_no_throw(
622622
is retryable.
623623
"""
624624

625-
headers_to_use = {"Authorization", "Bearer {}".format(access_token)}
625+
headers_to_use = {"Authorization": "Bearer {}".format(access_token)}
626626

627627
response_data = {}
628628
retryable_error = False

google/oauth2/service_account.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,13 @@ def with_token_uri(self, token_uri):
386386
cred._token_uri = token_uri
387387
return cred
388388

389+
@_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary)
390+
def with_trust_boundary(self, trust_boundary):
391+
"""Returns a copy of these credentials with a modified trust boundary."""
392+
cred = self._make_copy()
393+
cred._trust_boundary = trust_boundary
394+
return cred
395+
389396
def _make_authorization_grant_assertion(self):
390397
"""Create the OAuth 2.0 assertion.
391398

0 commit comments

Comments
 (0)