Skip to content

Commit 1d26a76

Browse files
committed
Add x-allowed-location header to all IAM requests.
1 parent 52b1fc5 commit 1d26a76

File tree

8 files changed

+210
-59
lines changed

8 files changed

+210
-59
lines changed

google/auth/credentials.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,17 +306,19 @@ def with_trust_boundary(self, trust_boundary):
306306
"""
307307
raise NotImplementedError("This credential does not support trust boundaries.")
308308

309-
def apply(self, headers, token=None):
310-
"""Apply the token to the authentication header."""
311-
super().apply(headers, token)
309+
def _get_trust_boundary_header(self):
312310
if self._trust_boundary is not None:
313311
if self._has_no_op_trust_boundary():
314312
# STS expects an empty string if the trust boundary value is no-op.
315-
headers["x-allowed-locations"] = ""
313+
return {"x-allowed-locations": ""}
316314
else:
317-
headers["x-allowed-locations"] = self._trust_boundary[
318-
"encodedLocations"
319-
]
315+
return {"x-allowed-locations": self._trust_boundary["encodedLocations"]}
316+
return {}
317+
318+
def apply(self, headers, token=None):
319+
"""Apply the token to the authentication header."""
320+
super().apply(headers, token)
321+
headers.update(self._get_trust_boundary_header())
320322

321323
def _refresh_trust_boundary(self, request):
322324
"""Triggers a refresh of the trust boundary and updates the cache if necessary.
@@ -378,7 +380,10 @@ def _lookup_trust_boundary(self, request):
378380
url = self._build_trust_boundary_lookup_url()
379381
if not url:
380382
raise exceptions.InvalidValue("Failed to build trust boundary lookup URL.")
381-
return _client.lookup_trust_boundary(request, url, self.token)
383+
384+
headers = {}
385+
self.apply(headers)
386+
return _client.lookup_trust_boundary(request, url, headers=headers)
382387

383388
@abc.abstractmethod
384389
def _build_trust_boundary_lookup_url(self):

google/auth/impersonated_credentials.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ def refresh(self, request):
607607
"Content-Type": "application/json",
608608
metrics.API_CLIENT_HEADER: metrics.token_request_id_token_impersonate(),
609609
}
610+
headers.update(self._target_credentials._get_trust_boundary_header())
610611

611612
authed_session = AuthorizedSession(
612613
self._target_credentials._source_credentials, auth_request=request

google/oauth2/_client.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ def call_iam_generate_id_token_endpoint(
327327
signer_email,
328328
audience,
329329
access_token,
330+
headers=None,
330331
universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
331332
):
332333
"""Call iam.generateIdToken endpoint to get ID token.
@@ -339,6 +340,9 @@ def call_iam_generate_id_token_endpoint(
339340
generateIdToken endpoint.
340341
audience (str): The audience for the ID token.
341342
access_token (str): The access token used to call the IAM endpoint.
343+
headers (Optional[Mapping[str, str]]): The headers for the request.
344+
universe_domain (str): The universe domain for the request. The
345+
default is ``googleapis.com``.
342346
343347
Returns:
344348
Tuple[str, datetime]: The ID token and expiration.
@@ -353,6 +357,7 @@ def call_iam_generate_id_token_endpoint(
353357
body,
354358
access_token=access_token,
355359
use_json=True,
360+
headers=headers,
356361
)
357362

358363
try:
@@ -510,7 +515,7 @@ def refresh_grant(
510515
return _handle_refresh_grant_response(response_data, refresh_token)
511516

512517

513-
def lookup_trust_boundary(request, url, access_token):
518+
def lookup_trust_boundary(request, url, headers=None):
514519
"""Implements the global lookup of a credential trust boundary.
515520
For the lookup, we send a request to the global lookup endpoint and then
516521
parse the response. Service account credentials, workload identity
@@ -519,15 +524,7 @@ def lookup_trust_boundary(request, url, access_token):
519524
request (google.auth.transport.Request): A callable used to make
520525
HTTP requests.
521526
url (str): The trust boundary lookup url.
522-
access_token (Optional(str)): The access token needed to make the request
523527
headers (Optional[Mapping[str, str]]): The headers for the request.
524-
kwargs: Additional arguments passed on to the request method. The
525-
kwargs will be passed to `requests.request` method, see:
526-
https://docs.python-requests.org/en/latest/api/#requests.request.
527-
For example, you can use `cert=("cert_pem_path", "key_pem_path")`
528-
to set up client side SSL certificate, and use
529-
`verify="ca_bundle_path"` to set up the CA certificates for sever
530-
side SSL certificate verification.
531528
Returns:
532529
Mapping[str,list|str]: A dictionary containing
533530
"locations" as a list of allowed locations as strings and
@@ -550,7 +547,7 @@ def lookup_trust_boundary(request, url, access_token):
550547
exceptions.MalformedError: If the response is not in a valid format.
551548
"""
552549

553-
response_data = _lookup_trust_boundary_request(request, url, access_token, True)
550+
response_data = _lookup_trust_boundary_request(request, url, headers=headers)
554551
# In case of no-op response, the "locations" list may or may not be present as an empty list.
555552
if "encodedLocations" not in response_data:
556553
raise exceptions.MalformedError(
@@ -560,16 +557,16 @@ def lookup_trust_boundary(request, url, access_token):
560557

561558

562559
def _lookup_trust_boundary_request(
563-
request, url, access_token, can_retry=True, **kwargs
560+
request, url, can_retry=True, headers=None, **kwargs
564561
):
565562
"""Makes a request to the trust boundary lookup endpoint.
566563
567564
Args:
568565
request (google.auth.transport.Request): A callable used to make
569566
HTTP requests.
570567
url (str): The trust boundary lookup url.
571-
access_token (Optional(str)): The access token needed to make the request
572568
can_retry (bool): Enable or disable request retry behavior. Defaults to true.
569+
headers (Optional[Mapping[str, str]]): The headers for the request.
573570
kwargs: Additional arguments passed on to the request method. The
574571
kwargs will be passed to `requests.request` method, see:
575572
https://docs.python-requests.org/en/latest/api/#requests.request.
@@ -587,7 +584,7 @@ def _lookup_trust_boundary_request(
587584
"""
588585
response_status_ok, response_data, retryable_error = (
589586
_lookup_trust_boundary_request_no_throw(
590-
request, url, access_token=access_token, can_retry=can_retry, **kwargs
587+
request, url, can_retry, headers, **kwargs
591588
)
592589
)
593590
if not response_status_ok:
@@ -596,7 +593,7 @@ def _lookup_trust_boundary_request(
596593

597594

598595
def _lookup_trust_boundary_request_no_throw(
599-
request, url, access_token=None, can_retry=True, **kwargs
596+
request, url, can_retry=True, headers=None, **kwargs
600597
):
601598
"""Makes a request to the trust boundary lookup endpoint. This
602599
function doesn't throw on response errors.
@@ -605,8 +602,8 @@ def _lookup_trust_boundary_request_no_throw(
605602
request (google.auth.transport.Request): A callable used to make
606603
HTTP requests.
607604
url (str): The trust boundary lookup url.
608-
access_token (Optional(str)): The access token needed to make the request
609605
can_retry (bool): Enable or disable request retry behavior. Defaults to true.
606+
headers (Optional[Mapping[str, str]]): The headers for the request.
610607
kwargs: Additional arguments passed on to the request method. The
611608
kwargs will be passed to `requests.request` method, see:
612609
https://docs.python-requests.org/en/latest/api/#requests.request.
@@ -622,14 +619,12 @@ def _lookup_trust_boundary_request_no_throw(
622619
is retryable.
623620
"""
624621

625-
headers_to_use = {"Authorization": "Bearer {}".format(access_token)}
626-
627622
response_data = {}
628623
retryable_error = False
629624

630625
retries = _exponential_backoff.ExponentialBackoff()
631626
for _ in retries:
632-
response = request(method="GET", url=url, headers=headers_to_use, **kwargs)
627+
response = request(method="GET", url=url, headers=headers, **kwargs)
633628
response_body = (
634629
response.data.decode("utf-8")
635630
if hasattr(response.data, "decode")

google/oauth2/service_account.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ class IDTokenCredentials(
554554
credentials.Signing,
555555
credentials.CredentialsWithQuotaProject,
556556
credentials.CredentialsWithTokenUri,
557+
credentials.CredentialsWithTrustBoundary,
557558
):
558559
"""Open ID Connect ID Token-based service account credentials.
559560
@@ -608,6 +609,7 @@ def __init__(
608609
additional_claims=None,
609610
quota_project_id=None,
610611
universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,
612+
trust_boundary=None,
611613
):
612614
"""
613615
Args:
@@ -625,6 +627,8 @@ def __init__(
625627
token endponint is used for token refresh. Note that
626628
iam.serviceAccountTokenCreator role is required to use the IAM
627629
endpoint.
630+
trust_boundary (Mapping[str,str]): A credential trust boundary.
631+
628632
.. note:: Typically one of the helper constructors
629633
:meth:`from_service_account_file` or
630634
:meth:`from_service_account_info` are used instead of calling the
@@ -637,6 +641,7 @@ def __init__(
637641
self._target_audience = target_audience
638642
self._quota_project_id = quota_project_id
639643
self._use_iam_endpoint = False
644+
self._trust_boundary = trust_boundary
640645

641646
if not universe_domain:
642647
self._universe_domain = credentials.DEFAULT_UNIVERSE_DOMAIN
@@ -674,6 +679,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs):
674679
kwargs.setdefault("token_uri", info["token_uri"])
675680
if "universe_domain" in info:
676681
kwargs["universe_domain"] = info["universe_domain"]
682+
if "trust_boundary" in info:
683+
kwargs["trust_boundary"] = info["trust_boundary"]
677684
return cls(signer, **kwargs)
678685

679686
@classmethod
@@ -723,6 +730,7 @@ def _make_copy(self):
723730
additional_claims=self._additional_claims.copy(),
724731
quota_project_id=self.quota_project_id,
725732
universe_domain=self._universe_domain,
733+
trust_boundary=self._trust_boundary,
726734
)
727735
# _use_iam_endpoint is not exposed in the constructor
728736
cred._use_iam_endpoint = self._use_iam_endpoint
@@ -784,6 +792,22 @@ def with_token_uri(self, token_uri):
784792
cred._token_uri = token_uri
785793
return cred
786794

795+
@_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary)
796+
def with_trust_boundary(self, trust_boundary):
797+
cred = self._make_copy()
798+
cred._trust_boundary = trust_boundary
799+
return cred
800+
801+
def _build_trust_boundary_lookup_url(self):
802+
"""Builds and returns the URL for the trust boundary lookup API.
803+
804+
This is not used by IDTokenCredentials as it does not perform trust
805+
boundary lookups. It is defined here to satisfy the abstract base class.
806+
"""
807+
raise NotImplementedError(
808+
"_build_trust_boundary_lookup_url is not implemented for IDTokenCredentials."
809+
)
810+
787811
def _make_authorization_grant_assertion(self):
788812
"""Create the OAuth 2.0 assertion.
789813
@@ -840,13 +864,17 @@ def _refresh_with_iam_endpoint(self, request):
840864
additional_claims={"scope": "https://www.googleapis.com/auth/iam"},
841865
)
842866
jwt_credentials.refresh(request)
867+
868+
headers = self._get_trust_boundary_header()
869+
843870
self.token, self.expiry = _client.call_iam_generate_id_token_endpoint(
844871
request,
845872
self._iam_id_token_endpoint,
846873
self.signer_email,
847874
self._target_audience,
848875
jwt_credentials.token.decode(),
849-
self._universe_domain,
876+
headers=headers,
877+
universe_domain=self._universe_domain,
850878
)
851879

852880
@_helpers.copy_docstring(credentials.Credentials)

0 commit comments

Comments
 (0)