From 1cc6dc171757649865cd0e42489c7326a398c32b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:20:03 +0000 Subject: [PATCH 1/6] feat: support mTLS in impersonated credentials Conditionally use mTLS endpoints in impersonated credentials if CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE or GOOGLE_API_USE_CLIENT_CERTIFICATE is true. Updated `_mtls_helper.check_use_client_cert` to check for `CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE`. Added mTLS endpoints to `google/auth/iam.py`. Modified `google/auth/impersonated_credentials.py` to use mTLS endpoints when appropriate. --- google/auth/iam.py | 20 +++++++++++ google/auth/impersonated_credentials.py | 44 ++++++++++++++++++------- google/auth/transport/_mtls_helper.py | 42 ++++++++++++----------- tests/transport/test__mtls_helper.py | 8 +++++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index 1e4cdffec..20b7e99a9 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -58,6 +58,26 @@ + "projects/-/serviceAccounts/{}:generateIdToken" ) +_IAM_ENDPOINT_MTLS = ( + "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken" +) + +_IAM_SIGN_ENDPOINT_MTLS = ( + "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signBlob" +) + +_IAM_SIGNJWT_ENDPOINT_MTLS = ( + "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signJwt" +) + +_IAM_IDTOKEN_ENDPOINT_MTLS = ( + "https://iamcredentials.mtls.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" +) + class Signer(crypt.Signer): """Signs messages using the IAM `signBlob API`_. diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py index 304f0606e..f0192e278 100644 --- a/google/auth/impersonated_credentials.py +++ b/google/auth/impersonated_credentials.py @@ -38,6 +38,7 @@ from google.auth import iam from google.auth import jwt from google.auth import metrics +from google.auth.transport import _mtls_helper from google.oauth2 import _client @@ -84,9 +85,16 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain - ).format(principal) + if iam_endpoint_override: + iam_endpoint = iam_endpoint_override + elif _mtls_helper.check_use_client_cert(): + iam_endpoint = iam._IAM_ENDPOINT_MTLS.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(principal) + else: + iam_endpoint = iam._IAM_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(principal) body = json.dumps(body).encode("utf-8") @@ -369,9 +377,14 @@ def _build_trust_boundary_lookup_url(self): def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain - ).format(self._target_principal) + if _mtls_helper.check_use_client_cert(): + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT_MTLS.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain + ).format(self._target_principal) + else: + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain + ).format(self._target_principal) body = { "payload": base64.b64encode(message).decode("utf-8"), @@ -606,10 +619,16 @@ def with_quota_project(self, quota_project_id): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, - self._target_credentials.universe_domain, - ).format(self._target_credentials.signer_email) + if _mtls_helper.check_use_client_cert(): + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT_MTLS.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, + self._target_credentials.universe_domain, + ).format(self._target_credentials.signer_email) + else: + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, + self._target_credentials.universe_domain, + ).format(self._target_credentials.signer_email) body = { "audience": self._target_audience, @@ -683,7 +702,10 @@ def _sign_jwt_request(request, principal, headers, payload, delegates=[]): `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) + if _mtls_helper.check_use_client_cert(): + iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT_MTLS.format(principal) + else: + iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) body = {"delegates": delegates, "payload": json.dumps(payload)} body = json.dumps(body).encode("utf-8") diff --git a/google/auth/transport/_mtls_helper.py b/google/auth/transport/_mtls_helper.py index 5b56b60b1..7e02df1d6 100644 --- a/google/auth/transport/_mtls_helper.py +++ b/google/auth/transport/_mtls_helper.py @@ -21,6 +21,7 @@ import subprocess from google.auth import _agent_identity_utils +from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions @@ -456,25 +457,28 @@ def check_use_client_cert(): # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. if use_client_cert: return use_client_cert.lower() == "true" - else: - # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. - cert_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") - if cert_path: - try: - with open(cert_path, "r") as f: - content = json.load(f) - # verify json has workload key - content["cert_configs"]["workload"] - return True - except ( - FileNotFoundError, - OSError, - KeyError, - TypeError, - json.JSONDecodeError, - ) as e: - _LOGGER.debug("error decoding certificate: %s", e) - return False + + if _helpers.get_bool_from_env("CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE"): + return True + + # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. + cert_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") + if cert_path: + try: + with open(cert_path, "r") as f: + content = json.load(f) + # verify json has workload key + content["cert_configs"]["workload"] + return True + except ( + FileNotFoundError, + OSError, + KeyError, + TypeError, + json.JSONDecodeError, + ) as e: + _LOGGER.debug("error decoding certificate: %s", e) + return False def check_parameters_for_unauthorized_response(cached_cert): diff --git a/tests/transport/test__mtls_helper.py b/tests/transport/test__mtls_helper.py index 1cdd91739..61947ebc0 100644 --- a/tests/transport/test__mtls_helper.py +++ b/tests/transport/test__mtls_helper.py @@ -756,6 +756,14 @@ def test_env_var_explicit_false(self): def test_env_var_explicit_garbage(self): assert _mtls_helper.check_use_client_cert() is False + @mock.patch.dict(os.environ, {"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "true"}) + def test_cloudsdk_env_var_explicit_true(self): + assert _mtls_helper.check_use_client_cert() is True + + @mock.patch.dict(os.environ, {"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "false"}) + def test_cloudsdk_env_var_explicit_false(self): + assert _mtls_helper.check_use_client_cert() is False + @mock.patch("builtins.open", autospec=True) @mock.patch.dict( os.environ, From 3282f2845211d91c23fde3110fcf1ab17325843e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:15:50 +0000 Subject: [PATCH 2/6] feat: support mTLS in impersonated credentials Conditionally use mTLS endpoints in impersonated credentials if CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE is true or google.auth.transport._mtls_helper.check_use_client_cert() is true. Added mTLS endpoints to `google/auth/iam.py`. Modified `google/auth/impersonated_credentials.py` to use mTLS endpoints when appropriate. --- google/auth/impersonated_credentials.py | 16 +++++++--- google/auth/transport/_mtls_helper.py | 42 +++++++++++-------------- tests/transport/test__mtls_helper.py | 8 ----- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py index f0192e278..8469630df 100644 --- a/google/auth/impersonated_credentials.py +++ b/google/auth/impersonated_credentials.py @@ -87,7 +87,9 @@ def _make_iam_token_request( """ if iam_endpoint_override: iam_endpoint = iam_endpoint_override - elif _mtls_helper.check_use_client_cert(): + elif _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" + ): iam_endpoint = iam._IAM_ENDPOINT_MTLS.replace( credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain ).format(principal) @@ -377,7 +379,9 @@ def _build_trust_boundary_lookup_url(self): def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - if _mtls_helper.check_use_client_cert(): + if _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" + ): iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT_MTLS.replace( credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain ).format(self._target_principal) @@ -619,7 +623,9 @@ def with_quota_project(self, quota_project_id): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - if _mtls_helper.check_use_client_cert(): + if _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" + ): iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT_MTLS.replace( credentials.DEFAULT_UNIVERSE_DOMAIN, self._target_credentials.universe_domain, @@ -702,7 +708,9 @@ def _sign_jwt_request(request, principal, headers, payload, delegates=[]): `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - if _mtls_helper.check_use_client_cert(): + if _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" + ): iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT_MTLS.format(principal) else: iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) diff --git a/google/auth/transport/_mtls_helper.py b/google/auth/transport/_mtls_helper.py index 7e02df1d6..5b56b60b1 100644 --- a/google/auth/transport/_mtls_helper.py +++ b/google/auth/transport/_mtls_helper.py @@ -21,7 +21,6 @@ import subprocess from google.auth import _agent_identity_utils -from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions @@ -457,28 +456,25 @@ def check_use_client_cert(): # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. if use_client_cert: return use_client_cert.lower() == "true" - - if _helpers.get_bool_from_env("CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE"): - return True - - # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. - cert_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") - if cert_path: - try: - with open(cert_path, "r") as f: - content = json.load(f) - # verify json has workload key - content["cert_configs"]["workload"] - return True - except ( - FileNotFoundError, - OSError, - KeyError, - TypeError, - json.JSONDecodeError, - ) as e: - _LOGGER.debug("error decoding certificate: %s", e) - return False + else: + # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. + cert_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") + if cert_path: + try: + with open(cert_path, "r") as f: + content = json.load(f) + # verify json has workload key + content["cert_configs"]["workload"] + return True + except ( + FileNotFoundError, + OSError, + KeyError, + TypeError, + json.JSONDecodeError, + ) as e: + _LOGGER.debug("error decoding certificate: %s", e) + return False def check_parameters_for_unauthorized_response(cached_cert): diff --git a/tests/transport/test__mtls_helper.py b/tests/transport/test__mtls_helper.py index 61947ebc0..1cdd91739 100644 --- a/tests/transport/test__mtls_helper.py +++ b/tests/transport/test__mtls_helper.py @@ -756,14 +756,6 @@ def test_env_var_explicit_false(self): def test_env_var_explicit_garbage(self): assert _mtls_helper.check_use_client_cert() is False - @mock.patch.dict(os.environ, {"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "true"}) - def test_cloudsdk_env_var_explicit_true(self): - assert _mtls_helper.check_use_client_cert() is True - - @mock.patch.dict(os.environ, {"CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE": "false"}) - def test_cloudsdk_env_var_explicit_false(self): - assert _mtls_helper.check_use_client_cert() is False - @mock.patch("builtins.open", autospec=True) @mock.patch.dict( os.environ, From 388ac2024e7e468efbe8f0a4df8244c26f109828 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:23:01 +0000 Subject: [PATCH 3/6] feat: support mTLS in impersonated credentials Conditionally use mTLS endpoints in impersonated credentials if google.auth.transport._mtls_helper.check_use_client_cert() is true. Added mTLS endpoints to `google/auth/iam.py`. Modified `google/auth/impersonated_credentials.py` to use mTLS endpoints when appropriate. --- google/auth/impersonated_credentials.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py index 8469630df..f0192e278 100644 --- a/google/auth/impersonated_credentials.py +++ b/google/auth/impersonated_credentials.py @@ -87,9 +87,7 @@ def _make_iam_token_request( """ if iam_endpoint_override: iam_endpoint = iam_endpoint_override - elif _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( - "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" - ): + elif _mtls_helper.check_use_client_cert(): iam_endpoint = iam._IAM_ENDPOINT_MTLS.replace( credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain ).format(principal) @@ -379,9 +377,7 @@ def _build_trust_boundary_lookup_url(self): def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - if _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( - "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" - ): + if _mtls_helper.check_use_client_cert(): iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT_MTLS.replace( credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain ).format(self._target_principal) @@ -623,9 +619,7 @@ def with_quota_project(self, quota_project_id): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - if _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( - "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" - ): + if _mtls_helper.check_use_client_cert(): iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT_MTLS.replace( credentials.DEFAULT_UNIVERSE_DOMAIN, self._target_credentials.universe_domain, @@ -708,9 +702,7 @@ def _sign_jwt_request(request, principal, headers, payload, delegates=[]): `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - if _mtls_helper.check_use_client_cert() or _helpers.get_bool_from_env( - "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" - ): + if _mtls_helper.check_use_client_cert(): iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT_MTLS.format(principal) else: iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) From 16c340934fdfd9923076915bbcc4757ebaa2dc9e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:42:36 +0000 Subject: [PATCH 4/6] feat: support mTLS in impersonated credentials Conditionally use mTLS endpoints in impersonated credentials if google.auth.transport._mtls_helper.check_use_client_cert() is true. Added mTLS endpoints to `google/auth/iam.py`. Modified `google/auth/impersonated_credentials.py` to use mTLS endpoints when appropriate. Removed logic checking for `CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE` environment variable as requested, relying solely on `check_use_client_cert()`. From 9da6b5ac3f506abb1d8a1186c607f70febebdd9f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:48:16 +0000 Subject: [PATCH 5/6] feat: support mTLS in impersonated credentials Conditionally define IAM endpoints in `google/auth/iam.py` based on `google.auth.transport._mtls_helper.check_use_client_cert()`. If mTLS is enabled, the endpoints will point to `iamcredentials.mtls.googleapis.com`. This ensures impersonated credentials (and other users of `google.auth.iam`) use mTLS endpoints when appropriate. Reverted changes to `google/auth/impersonated_credentials.py` as they are now handled by `google/auth/iam.py`. --- google/auth/iam.py | 80 +++++++++++++------------ google/auth/impersonated_credentials.py | 44 ++++---------- 2 files changed, 52 insertions(+), 72 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index 20b7e99a9..686f3b435 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -28,6 +28,7 @@ from google.auth import credentials from google.auth import crypt from google.auth import exceptions +from google.auth.transport import _mtls_helper IAM_RETRY_CODES = { http_client.INTERNAL_SERVER_ERROR, @@ -38,45 +39,46 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -_IAM_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken" -) - -_IAM_SIGN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:signBlob" -) - -_IAM_SIGNJWT_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:signJwt" -) - -_IAM_IDTOKEN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/" - + "projects/-/serviceAccounts/{}:generateIdToken" -) - -_IAM_ENDPOINT_MTLS = ( - "https://iamcredentials.mtls.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken" -) - -_IAM_SIGN_ENDPOINT_MTLS = ( - "https://iamcredentials.mtls.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:signBlob" -) - -_IAM_SIGNJWT_ENDPOINT_MTLS = ( - "https://iamcredentials.mtls.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:signJwt" -) - -_IAM_IDTOKEN_ENDPOINT_MTLS = ( - "https://iamcredentials.mtls.googleapis.com/v1/" - + "projects/-/serviceAccounts/{}:generateIdToken" -) +if _mtls_helper.check_use_client_cert(): + _IAM_ENDPOINT = ( + "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken" + ) + + _IAM_SIGN_ENDPOINT = ( + "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signBlob" + ) + + _IAM_SIGNJWT_ENDPOINT = ( + "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signJwt" + ) + + _IAM_IDTOKEN_ENDPOINT = ( + "https://iamcredentials.mtls.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" + ) +else: + _IAM_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken" + ) + + _IAM_SIGN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signBlob" + ) + + _IAM_SIGNJWT_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signJwt" + ) + + _IAM_IDTOKEN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" + ) class Signer(crypt.Signer): diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py index f0192e278..304f0606e 100644 --- a/google/auth/impersonated_credentials.py +++ b/google/auth/impersonated_credentials.py @@ -38,7 +38,6 @@ from google.auth import iam from google.auth import jwt from google.auth import metrics -from google.auth.transport import _mtls_helper from google.oauth2 import _client @@ -85,16 +84,9 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - if iam_endpoint_override: - iam_endpoint = iam_endpoint_override - elif _mtls_helper.check_use_client_cert(): - iam_endpoint = iam._IAM_ENDPOINT_MTLS.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain - ).format(principal) - else: - iam_endpoint = iam._IAM_ENDPOINT.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain - ).format(principal) + iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(principal) body = json.dumps(body).encode("utf-8") @@ -377,14 +369,9 @@ def _build_trust_boundary_lookup_url(self): def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - if _mtls_helper.check_use_client_cert(): - iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT_MTLS.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain - ).format(self._target_principal) - else: - iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain - ).format(self._target_principal) + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain + ).format(self._target_principal) body = { "payload": base64.b64encode(message).decode("utf-8"), @@ -619,16 +606,10 @@ def with_quota_project(self, quota_project_id): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - if _mtls_helper.check_use_client_cert(): - iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT_MTLS.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, - self._target_credentials.universe_domain, - ).format(self._target_credentials.signer_email) - else: - iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( - credentials.DEFAULT_UNIVERSE_DOMAIN, - self._target_credentials.universe_domain, - ).format(self._target_credentials.signer_email) + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, + self._target_credentials.universe_domain, + ).format(self._target_credentials.signer_email) body = { "audience": self._target_audience, @@ -702,10 +683,7 @@ def _sign_jwt_request(request, principal, headers, payload, delegates=[]): `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - if _mtls_helper.check_use_client_cert(): - iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT_MTLS.format(principal) - else: - iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) + iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) body = {"delegates": delegates, "payload": json.dumps(payload)} body = json.dumps(body).encode("utf-8") From 5164a9c8ba1dc2a0bacee3b88483270a643c0fb6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 03:01:05 +0000 Subject: [PATCH 6/6] feat: support mTLS in impersonated credentials Conditionally define IAM endpoints in `google/auth/iam.py` based on `google.auth.transport._mtls_helper.check_use_client_cert()`. If mTLS is enabled, the endpoints will point to `iamcredentials.mtls.googleapis.com`. Added a safety check using `hasattr` to avoid `AttributeError` if `_mtls_helper` does not have `check_use_client_cert` (e.g. in some bundled environments). This ensures impersonated credentials (and other users of `google.auth.iam`) use mTLS endpoints when appropriate. --- google/auth/iam.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index 686f3b435..3a93f84ec 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -39,7 +39,12 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -if _mtls_helper.check_use_client_cert(): +if hasattr(_mtls_helper, "check_use_client_cert"): + use_client_cert = _mtls_helper.check_use_client_cert() +else: + use_client_cert = False + +if use_client_cert: _IAM_ENDPOINT = ( "https://iamcredentials.mtls.googleapis.com/v1/projects/-" + "/serviceAccounts/{}:generateAccessToken"