From 92b42a183ee51a1a86b2c72c9296a26cfbcd20d4 Mon Sep 17 00:00:00 2001 From: amtk3 Date: Thu, 15 Jan 2026 15:07:02 +1100 Subject: [PATCH 01/12] Support the mTLS IAM domain for Certificate based Access --- google/auth/iam.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index 1e4cdffec..e828af33d 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,26 +39,20 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -_IAM_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken" -) +# 1. Determine if the IAM mTLS domain should be used +if hasattr(_mtls_helper, "check_use_client_cert") and _mtls_helper.check_use_client_cert(): + domain = "iamcredentials.mtls.googleapis.com" +else: + domain = "iamcredentials.googleapis.com" -_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" -) +# 2. Create the common base URL +base_url = f"https://{domain}/v1/projects/-/serviceAccounts/{{}}" +# 3. Define the endpoints +_IAM_ENDPOINT = base_url + ":generateAccessToken" +_IAM_SIGN_ENDPOINT = base_url + ":signBlob" +_IAM_SIGNJWT_ENDPOINT = base_url + ":signJwt" +_IAM_IDTOKEN_ENDPOINT = base_url + ":generateIdToken" class Signer(crypt.Signer): """Signs messages using the IAM `signBlob API`_. From af469a3bca7f7f6a8c4d2d3b46b778f8ba07a16c Mon Sep 17 00:00:00 2001 From: amtk3 Date: Thu, 15 Jan 2026 15:50:09 +1100 Subject: [PATCH 02/12] Update google/auth/iam.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- google/auth/iam.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index e828af33d..fdd5c8381 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -49,8 +49,8 @@ base_url = f"https://{domain}/v1/projects/-/serviceAccounts/{{}}" # 3. Define the endpoints -_IAM_ENDPOINT = base_url + ":generateAccessToken" -_IAM_SIGN_ENDPOINT = base_url + ":signBlob" +_IAM_ENDPOINT = base_url + ":generateAccessToken" +_IAM_SIGN_ENDPOINT = base_url + ":signBlob" _IAM_SIGNJWT_ENDPOINT = base_url + ":signJwt" _IAM_IDTOKEN_ENDPOINT = base_url + ":generateIdToken" From b52742ba25d0fc6c57908c3d923236af2c8c443d Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 16 Jan 2026 11:41:23 -0500 Subject: [PATCH 03/12] updates handling of mtls and universe domain --- google/auth/iam.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index fdd5c8381..fec4b0219 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -28,7 +28,7 @@ from google.auth import credentials from google.auth import crypt from google.auth import exceptions -from google.auth.transport import _mtls_helper +from google.auth.transport import mtls IAM_RETRY_CODES = { http_client.INTERNAL_SERVER_ERROR, @@ -39,20 +39,31 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -# 1. Determine if the IAM mTLS domain should be used -if hasattr(_mtls_helper, "check_use_client_cert") and _mtls_helper.check_use_client_cert(): - domain = "iamcredentials.mtls.googleapis.com" +# 1. Determine if we should use mTLS. +# Note: We only support automatic mTLS on the default googleapis.com universe. +if hasattr(mtls, "should_use_client_cert"): + use_client_cert = mtls.should_use_client_cert() +else: # pragma: NO COVER + # if unsupported, fallback to reading from env var + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() == "true" + +# 2. Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. +# This ensures that the .replace() calls in the classes will work correctly. +if use_client_cert: + # We use the .mtls. prefix only for the default universe template + _IAM_DOMAIN = f"iamcredentials.mtls.{credentials.DEFAULT_UNIVERSE_DOMAIN}" else: - domain = "iamcredentials.googleapis.com" + _IAM_DOMAIN = f"iamcredentials.{credentials.DEFAULT_UNIVERSE_DOMAIN}" -# 2. Create the common base URL -base_url = f"https://{domain}/v1/projects/-/serviceAccounts/{{}}" +# 3. Create the common base URL template +# We use double brackets {{}} so .format() can be called later for the email. +_IAM_BASE_URL = f"https://{_IAM_DOMAIN}/v1/projects/-/serviceAccounts/{{}}" -# 3. Define the endpoints -_IAM_ENDPOINT = base_url + ":generateAccessToken" -_IAM_SIGN_ENDPOINT = base_url + ":signBlob" -_IAM_SIGNJWT_ENDPOINT = base_url + ":signJwt" -_IAM_IDTOKEN_ENDPOINT = base_url + ":generateIdToken" +# 4. Define the endpoints as templates +_IAM_ENDPOINT = _IAM_BASE_URL + ":generateAccessToken" +_IAM_SIGN_ENDPOINT = _IAM_BASE_URL + ":signBlob" +_IAM_SIGNJWT_ENDPOINT = _IAM_BASE_URL + ":signJwt" +_IAM_IDTOKEN_ENDPOINT = _IAM_BASE_URL + ":generateIdToken" class Signer(crypt.Signer): """Signs messages using the IAM `signBlob API`_. From 64cd681a65f97161fbf48619aa0c99bd6f1324d6 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 16 Jan 2026 12:00:59 -0500 Subject: [PATCH 04/12] updates linting --- google/auth/iam.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index fec4b0219..22684a9d1 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -22,6 +22,7 @@ import base64 import http.client as http_client import json +import os from google.auth import _exponential_backoff from google.auth import _helpers @@ -39,13 +40,15 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -# 1. Determine if we should use mTLS. +# 1. Determine if we should use mTLS. # Note: We only support automatic mTLS on the default googleapis.com universe. if hasattr(mtls, "should_use_client_cert"): use_client_cert = mtls.should_use_client_cert() else: # pragma: NO COVER # if unsupported, fallback to reading from env var - use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() == "true" + use_client_cert = ( + os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() == "true" + ) # 2. Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. # This ensures that the .replace() calls in the classes will work correctly. @@ -65,6 +68,7 @@ _IAM_SIGNJWT_ENDPOINT = _IAM_BASE_URL + ":signJwt" _IAM_IDTOKEN_ENDPOINT = _IAM_BASE_URL + ":generateIdToken" + class Signer(crypt.Signer): """Signs messages using the IAM `signBlob API`_. From e2e2b9bf06ad0e18486b3f8c9bc264da66c35cee Mon Sep 17 00:00:00 2001 From: amtk3 Date: Mon, 19 Jan 2026 13:59:48 +1100 Subject: [PATCH 05/12] Support an alternative env to decide if mtls should be enabled --- google/auth/iam.py | 1 + 1 file changed, 1 insertion(+) diff --git a/google/auth/iam.py b/google/auth/iam.py index 22684a9d1..2696efe09 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -48,6 +48,7 @@ # if unsupported, fallback to reading from env var use_client_cert = ( os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() == "true" + or os.getenv("CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE", "false").lower() == "true" ) # 2. Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. From f540519b6eacef4e33940fc60f54a8b48d39f731 Mon Sep 17 00:00:00 2001 From: amtk3 Date: Mon, 19 Jan 2026 14:09:01 +1100 Subject: [PATCH 06/12] feat(iam): support an alternative env to decide if mtls should be enabled --- google/auth/iam.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index 2696efe09..2bc73718c 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -46,9 +46,12 @@ use_client_cert = mtls.should_use_client_cert() else: # pragma: NO COVER # if unsupported, fallback to reading from env var - use_client_cert = ( - os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() == "true" - or os.getenv("CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE", "false").lower() == "true" + use_client_cert = any( + os.getenv(var, "false").lower() == "true" + for var in ( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE", + ) ) # 2. Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. From 6a3bf2617942ad2c1ce566b47aad24f0e9a5be3e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 08:19:19 +0000 Subject: [PATCH 07/12] feat: add fallback env vars for mTLS config Added `CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE` and `CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH` as fallback environment variables for mTLS configuration. Updated `google/auth/transport/_mtls_helper.py` to check these variables if the primary `GOOGLE_API_...` variables are not set. Added tests to verify precedence and fallback logic. --- google/auth/environment_vars.py | 12 ++++ google/auth/transport/_mtls_helper.py | 23 ++++++- tests/transport/test_mtls_env_vars.py | 97 +++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 tests/transport/test_mtls_env_vars.py diff --git a/google/auth/environment_vars.py b/google/auth/environment_vars.py index ca041ca16..c7d706467 100644 --- a/google/auth/environment_vars.py +++ b/google/auth/environment_vars.py @@ -113,6 +113,18 @@ """Environment variable defining the location of Google API certificate config file.""" +CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE = ( + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" +) +"""Environment variable controlling whether to use client certificate or not. +This variable is the fallback of GOOGLE_API_USE_CLIENT_CERTIFICATE.""" + +CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH = ( + "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH" +) +"""Environment variable defining the location of Google API certificate config +file. This variable is the fallback of GOOGLE_API_CERTIFICATE_CONFIG.""" + GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES = ( "GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES" ) diff --git a/google/auth/transport/_mtls_helper.py b/google/auth/transport/_mtls_helper.py index 623b435ee..d5ccbdc4e 100644 --- a/google/auth/transport/_mtls_helper.py +++ b/google/auth/transport/_mtls_helper.py @@ -151,7 +151,14 @@ def _get_cert_config_path(certificate_config_path=None): if env_path is not None and env_path != "": certificate_config_path = env_path else: - certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH + env_path = environ.get( + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, + None, + ) + if env_path is not None and env_path != "": + certificate_config_path = env_path + else: + certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH certificate_config_path = path.expanduser(certificate_config_path) if not path.exists(certificate_config_path): @@ -452,13 +459,23 @@ def check_use_client_cert(): Returns: bool: Whether the client certificate should be used for mTLS connection. """ - use_client_cert = getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE") + use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE) + if use_client_cert is None: + use_client_cert = getenv( + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE + ) + # 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") + cert_path = getenv(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) + if cert_path is None: + cert_path = getenv( + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH + ) + if cert_path: try: with open(cert_path, "r") as f: diff --git a/tests/transport/test_mtls_env_vars.py b/tests/transport/test_mtls_env_vars.py new file mode 100644 index 000000000..3f62a3a51 --- /dev/null +++ b/tests/transport/test_mtls_env_vars.py @@ -0,0 +1,97 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from unittest import mock +import pytest +from google.auth.transport import _mtls_helper +from google.auth import environment_vars + +class TestEnvVarsPrecedence: + def test_use_client_cert_precedence(self): + # GOOGLE_API_USE_CLIENT_CERTIFICATE takes precedence + with mock.patch.dict(os.environ, { + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true", + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" + }): + assert _mtls_helper.check_use_client_cert() is True + + with mock.patch.dict(os.environ, { + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "false", + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" + }): + assert _mtls_helper.check_use_client_cert() is False + + def test_use_client_cert_fallback(self): + # Fallback to CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE if GOOGLE_API_USE_CLIENT_CERTIFICATE is unset + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" + }): + # Ensure GOOGLE_API_USE_CLIENT_CERTIFICATE is not set + if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] + assert _mtls_helper.check_use_client_cert() is True + + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" + }): + if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] + assert _mtls_helper.check_use_client_cert() is False + + def test_cert_config_path_precedence(self): + # GOOGLE_API_CERTIFICATE_CONFIG takes precedence + google_path = "/path/to/google/config" + cloudsdk_path = "/path/to/cloudsdk/config" + + with mock.patch.dict(os.environ, { + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: google_path, + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }): + with mock.patch("os.path.exists", return_value=True): + assert _mtls_helper._get_cert_config_path() == google_path + + def test_cert_config_path_fallback(self): + # Fallback to CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH if GOOGLE_API_CERTIFICATE_CONFIG is unset + cloudsdk_path = "/path/to/cloudsdk/config" + + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }): + if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: + del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] + + with mock.patch("os.path.exists", return_value=True): + assert _mtls_helper._get_cert_config_path() == cloudsdk_path + + @mock.patch("builtins.open", autospec=True) + def test_check_use_client_cert_config_fallback(self, mock_file): + # Test fallback for config file when determining if client cert should be used + cloudsdk_path = "/path/to/cloudsdk/config" + + mock_file.side_effect = mock.mock_open( + read_data='{"cert_configs": {"workload": "exists"}}' + ) + + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }): + if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: + del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] + if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] + if environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE] + + assert _mtls_helper.check_use_client_cert() is True From 90793f19763bf87dbe537b55ffffb018914cf683 Mon Sep 17 00:00:00 2001 From: amtk3 Date: Thu, 22 Jan 2026 19:33:43 +1100 Subject: [PATCH 08/12] Use "_mtls_helper" class --- google/auth/iam.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index 22684a9d1..b9ab0bacc 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -22,14 +22,13 @@ import base64 import http.client as http_client import json -import os from google.auth import _exponential_backoff from google.auth import _helpers from google.auth import credentials from google.auth import crypt from google.auth import exceptions -from google.auth.transport import mtls +from google.auth.transport import _mtls_helper IAM_RETRY_CODES = { http_client.INTERNAL_SERVER_ERROR, @@ -40,20 +39,9 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -# 1. Determine if we should use mTLS. -# Note: We only support automatic mTLS on the default googleapis.com universe. -if hasattr(mtls, "should_use_client_cert"): - use_client_cert = mtls.should_use_client_cert() -else: # pragma: NO COVER - # if unsupported, fallback to reading from env var - use_client_cert = ( - os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() == "true" - ) - -# 2. Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. -# This ensures that the .replace() calls in the classes will work correctly. -if use_client_cert: - # We use the .mtls. prefix only for the default universe template +# Determine if we should use mTLS. +if hasattr(_mtls_helper, "check_use_client_cert") and _mtls_helper.check_use_client_cert(): + # Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. _IAM_DOMAIN = f"iamcredentials.mtls.{credentials.DEFAULT_UNIVERSE_DOMAIN}" else: _IAM_DOMAIN = f"iamcredentials.{credentials.DEFAULT_UNIVERSE_DOMAIN}" From 6995e43452da58329792556945a80f3400b34eb7 Mon Sep 17 00:00:00 2001 From: Chalmer Lowe Date: Fri, 30 Jan 2026 08:35:11 -0500 Subject: [PATCH 09/12] Apply suggestion from @chalmerlowe --- google/auth/transport/_mtls_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/auth/transport/_mtls_helper.py b/google/auth/transport/_mtls_helper.py index d5ccbdc4e..99078c9c7 100644 --- a/google/auth/transport/_mtls_helper.py +++ b/google/auth/transport/_mtls_helper.py @@ -460,7 +460,7 @@ def check_use_client_cert(): bool: Whether the client certificate should be used for mTLS connection. """ use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE) - if use_client_cert is None: + if use_client_cert is None or use_client_cert == "": use_client_cert = getenv( environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE ) From f62279f51c1b54dd3dcc99fba26236c6f6a3ac4e Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 30 Jan 2026 08:55:37 -0500 Subject: [PATCH 10/12] moves tests to a more appropriate file. --- tests/transport/test__mtls_helper.py | 79 +++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/tests/transport/test__mtls_helper.py b/tests/transport/test__mtls_helper.py index dd70ebc79..a23ae01d3 100644 --- a/tests/transport/test__mtls_helper.py +++ b/tests/transport/test__mtls_helper.py @@ -19,7 +19,7 @@ from OpenSSL import crypto import pytest # type: ignore -from google.auth import exceptions +from google.auth import exceptions, environment_vars from google.auth.transport import _mtls_helper CERT_MOCK_VAL = b"cert" @@ -681,6 +681,31 @@ def test_env_variable_file_does_not_exist(self, mock_path_exists): returned_path = _mtls_helper._get_cert_config_path() assert returned_path is None + def test_cert_config_path_precedence(self): + # GOOGLE_API_CERTIFICATE_CONFIG takes precedence + google_path = "/path/to/google/config" + cloudsdk_path = "/path/to/cloudsdk/config" + + with mock.patch.dict(os.environ, { + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: google_path, + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }): + with mock.patch("os.path.exists", return_value=True): + assert _mtls_helper._get_cert_config_path() == google_path + + def test_cert_config_path_fallback(self): + # Fallback to CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH if GOOGLE_API_CERTIFICATE_CONFIG is unset + cloudsdk_path = "/path/to/cloudsdk/config" + + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }): + if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: + del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] + + with mock.patch("os.path.exists", return_value=True): + assert _mtls_helper._get_cert_config_path() == cloudsdk_path + @mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": "path/to/config/file"} ) @@ -811,6 +836,58 @@ def test_config_file_not_found(self, mock_file): def test_no_env_vars_set(self): assert _mtls_helper.check_use_client_cert() is False + def test_use_client_cert_precedence(self): + # GOOGLE_API_USE_CLIENT_CERTIFICATE takes precedence + with mock.patch.dict(os.environ, { + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true", + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" + }): + assert _mtls_helper.check_use_client_cert() is True + + with mock.patch.dict(os.environ, { + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "false", + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" + }): + assert _mtls_helper.check_use_client_cert() is False + + def test_use_client_cert_fallback(self): + # Fallback to CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE if GOOGLE_API_USE_CLIENT_CERTIFICATE is unset + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" + }): + # Ensure GOOGLE_API_USE_CLIENT_CERTIFICATE is not set + if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] + assert _mtls_helper.check_use_client_cert() is True + + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" + }): + if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] + assert _mtls_helper.check_use_client_cert() is False + + @mock.patch("builtins.open", autospec=True) + def test_check_use_client_cert_config_fallback(self, mock_file): + # Test fallback for config file when determining if client cert should be used + cloudsdk_path = "/path/to/cloudsdk/config" + + mock_file.side_effect = mock.mock_open( + read_data='{"cert_configs": {"workload": "exists"}}' + ) + + with mock.patch.dict(os.environ, { + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }): + if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: + del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] + if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] + if environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE in os.environ: + del os.environ[environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE] + + assert _mtls_helper.check_use_client_cert() is True + class TestMtlsHelper: @mock.patch.object(_mtls_helper, "call_client_cert_callback") From 47015a86775304b7b332d236e079b936438c44bd Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 30 Jan 2026 08:57:09 -0500 Subject: [PATCH 11/12] removes no longer needed file. --- tests/transport/test_mtls_env_vars.py | 97 --------------------------- 1 file changed, 97 deletions(-) delete mode 100644 tests/transport/test_mtls_env_vars.py diff --git a/tests/transport/test_mtls_env_vars.py b/tests/transport/test_mtls_env_vars.py deleted file mode 100644 index 3f62a3a51..000000000 --- a/tests/transport/test_mtls_env_vars.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from unittest import mock -import pytest -from google.auth.transport import _mtls_helper -from google.auth import environment_vars - -class TestEnvVarsPrecedence: - def test_use_client_cert_precedence(self): - # GOOGLE_API_USE_CLIENT_CERTIFICATE takes precedence - with mock.patch.dict(os.environ, { - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true", - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" - }): - assert _mtls_helper.check_use_client_cert() is True - - with mock.patch.dict(os.environ, { - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "false", - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" - }): - assert _mtls_helper.check_use_client_cert() is False - - def test_use_client_cert_fallback(self): - # Fallback to CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE if GOOGLE_API_USE_CLIENT_CERTIFICATE is unset - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" - }): - # Ensure GOOGLE_API_USE_CLIENT_CERTIFICATE is not set - if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: - del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] - assert _mtls_helper.check_use_client_cert() is True - - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" - }): - if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: - del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] - assert _mtls_helper.check_use_client_cert() is False - - def test_cert_config_path_precedence(self): - # GOOGLE_API_CERTIFICATE_CONFIG takes precedence - google_path = "/path/to/google/config" - cloudsdk_path = "/path/to/cloudsdk/config" - - with mock.patch.dict(os.environ, { - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: google_path, - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path - }): - with mock.patch("os.path.exists", return_value=True): - assert _mtls_helper._get_cert_config_path() == google_path - - def test_cert_config_path_fallback(self): - # Fallback to CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH if GOOGLE_API_CERTIFICATE_CONFIG is unset - cloudsdk_path = "/path/to/cloudsdk/config" - - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path - }): - if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: - del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] - - with mock.patch("os.path.exists", return_value=True): - assert _mtls_helper._get_cert_config_path() == cloudsdk_path - - @mock.patch("builtins.open", autospec=True) - def test_check_use_client_cert_config_fallback(self, mock_file): - # Test fallback for config file when determining if client cert should be used - cloudsdk_path = "/path/to/cloudsdk/config" - - mock_file.side_effect = mock.mock_open( - read_data='{"cert_configs": {"workload": "exists"}}' - ) - - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path - }): - if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: - del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] - if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: - del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] - if environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE in os.environ: - del os.environ[environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE] - - assert _mtls_helper.check_use_client_cert() is True From 2d7f263d9c0e4f493dde5696881267ddf85d88a0 Mon Sep 17 00:00:00 2001 From: chalmer lowe Date: Fri, 30 Jan 2026 09:33:14 -0500 Subject: [PATCH 12/12] updates linting --- google/auth/iam.py | 5 +- tests/transport/test__mtls_helper.py | 76 ++++++++++++++++++---------- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/google/auth/iam.py b/google/auth/iam.py index b9ab0bacc..1c913377f 100644 --- a/google/auth/iam.py +++ b/google/auth/iam.py @@ -40,7 +40,10 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] # Determine if we should use mTLS. -if hasattr(_mtls_helper, "check_use_client_cert") and _mtls_helper.check_use_client_cert(): +if ( + hasattr(_mtls_helper, "check_use_client_cert") + and _mtls_helper.check_use_client_cert() +): # Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. _IAM_DOMAIN = f"iamcredentials.mtls.{credentials.DEFAULT_UNIVERSE_DOMAIN}" else: diff --git a/tests/transport/test__mtls_helper.py b/tests/transport/test__mtls_helper.py index a23ae01d3..eeb5e1310 100644 --- a/tests/transport/test__mtls_helper.py +++ b/tests/transport/test__mtls_helper.py @@ -19,7 +19,7 @@ from OpenSSL import crypto import pytest # type: ignore -from google.auth import exceptions, environment_vars +from google.auth import environment_vars, exceptions from google.auth.transport import _mtls_helper CERT_MOCK_VAL = b"cert" @@ -686,10 +686,13 @@ def test_cert_config_path_precedence(self): google_path = "/path/to/google/config" cloudsdk_path = "/path/to/cloudsdk/config" - with mock.patch.dict(os.environ, { - environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: google_path, - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path - }): + with mock.patch.dict( + os.environ, + { + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: google_path, + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path, + }, + ): with mock.patch("os.path.exists", return_value=True): assert _mtls_helper._get_cert_config_path() == google_path @@ -697,9 +700,12 @@ def test_cert_config_path_fallback(self): # Fallback to CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH if GOOGLE_API_CERTIFICATE_CONFIG is unset cloudsdk_path = "/path/to/cloudsdk/config" - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path - }): + with mock.patch.dict( + os.environ, + { + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }, + ): if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] @@ -838,31 +844,39 @@ def test_no_env_vars_set(self): def test_use_client_cert_precedence(self): # GOOGLE_API_USE_CLIENT_CERTIFICATE takes precedence - with mock.patch.dict(os.environ, { - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true", - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" - }): + with mock.patch.dict( + os.environ, + { + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true", + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false", + }, + ): assert _mtls_helper.check_use_client_cert() is True - with mock.patch.dict(os.environ, { - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "false", - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" - }): + with mock.patch.dict( + os.environ, + { + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "false", + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true", + }, + ): assert _mtls_helper.check_use_client_cert() is False def test_use_client_cert_fallback(self): # Fallback to CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE if GOOGLE_API_USE_CLIENT_CERTIFICATE is unset - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true" - }): + with mock.patch.dict( + os.environ, + {environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "true"}, + ): # Ensure GOOGLE_API_USE_CLIENT_CERTIFICATE is not set if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] assert _mtls_helper.check_use_client_cert() is True - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false" - }): + with mock.patch.dict( + os.environ, + {environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE: "false"}, + ): if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] assert _mtls_helper.check_use_client_cert() is False @@ -876,15 +890,23 @@ def test_check_use_client_cert_config_fallback(self, mock_file): read_data='{"cert_configs": {"workload": "exists"}}' ) - with mock.patch.dict(os.environ, { - environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path - }): + with mock.patch.dict( + os.environ, + { + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH: cloudsdk_path + }, + ): if environment_vars.GOOGLE_API_CERTIFICATE_CONFIG in os.environ: del os.environ[environment_vars.GOOGLE_API_CERTIFICATE_CONFIG] if environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE in os.environ: del os.environ[environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE] - if environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE in os.environ: - del os.environ[environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE] + if ( + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE + in os.environ + ): + del os.environ[ + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE + ] assert _mtls_helper.check_use_client_cert() is True