From 633ab62092b7eb1c58525e23011cb0ced13ffc96 Mon Sep 17 00:00:00 2001 From: Radhika Agrawal Date: Fri, 21 Nov 2025 10:48:50 +0530 Subject: [PATCH 1/5] feat: Auto enable mTLS when supported certificates are detected Signed-off-by: Radhika Agrawal --- .../abstract_operations_base_client.py | 20 +++++++++++-------- .../test_operations_rest_client.py | 10 +++++++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/google/api_core/operations_v1/abstract_operations_base_client.py b/google/api_core/operations_v1/abstract_operations_base_client.py index 160c2a88f..a2877dcef 100644 --- a/google/api_core/operations_v1/abstract_operations_base_client.py +++ b/google/api_core/operations_v1/abstract_operations_base_client.py @@ -300,16 +300,20 @@ def __init__( client_options = client_options_lib.ClientOptions() # Create SSL credentials for mutual TLS if needed. - use_client_cert = os.getenv( - "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" - ).lower() - if use_client_cert not in ("true", "false"): - raise ValueError( - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" - ) + if hasattr(mtls, "should_use_client_cert"): + use_client_cert = mtls.should_use_client_cert() + else: + # if unsupported, fallback to reading from env var + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + use_client_cert = use_client_cert_str == "true" client_cert_source_func = None is_mtls = False - if use_client_cert == "true": + if use_client_cert: if client_options.client_cert_source: is_mtls = True client_cert_source_func = client_options.client_cert_source diff --git a/tests/unit/operations_v1/test_operations_rest_client.py b/tests/unit/operations_v1/test_operations_rest_client.py index 87523c5dd..785de6285 100644 --- a/tests/unit/operations_v1/test_operations_rest_client.py +++ b/tests/unit/operations_v1/test_operations_rest_client.py @@ -346,12 +346,16 @@ def test_operations_client_client_options( with pytest.raises(MutualTLSChannelError): client = client_class() - # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value and + # should_use_client_cert is unavailable. with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): - with pytest.raises(ValueError): - client = client_class() + if hasattr(google.auth.transport.mtls, "should_use_client_cert"): + pytest.skip( + "The should_use_client_cert function is available in this " + "version of google-auth. Skipping this test." + ) # Check the case quota_project_id is provided options = client_options.ClientOptions(quota_project_id="octopus") From da0747cdba4614afbd4e383ae166e6042df34a59 Mon Sep 17 00:00:00 2001 From: Radhika Agrawal Date: Mon, 1 Dec 2025 22:05:25 +0530 Subject: [PATCH 2/5] fix: fix lint errors Signed-off-by: Radhika Agrawal --- .../api_core/operations_v1/abstract_operations_base_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/google/api_core/operations_v1/abstract_operations_base_client.py b/google/api_core/operations_v1/abstract_operations_base_client.py index a2877dcef..f62f60b04 100644 --- a/google/api_core/operations_v1/abstract_operations_base_client.py +++ b/google/api_core/operations_v1/abstract_operations_base_client.py @@ -304,7 +304,9 @@ def __init__( use_client_cert = mtls.should_use_client_cert() else: # if unsupported, fallback to reading from env var - use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + use_client_cert_str = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" + ).lower() if use_client_cert_str not in ("true", "false"): raise ValueError( "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" From 56dc7627f8a57090ffe9f6cf1b9475589d3e2e53 Mon Sep 17 00:00:00 2001 From: Radhika Agrawal Date: Wed, 3 Dec 2025 22:25:42 +0530 Subject: [PATCH 3/5] fix: Update tests for coverage of all branches Signed-off-by: Radhika Agrawal --- .../operations_v1/test_operations_rest_client.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/unit/operations_v1/test_operations_rest_client.py b/tests/unit/operations_v1/test_operations_rest_client.py index 785de6285..5dfe55f48 100644 --- a/tests/unit/operations_v1/test_operations_rest_client.py +++ b/tests/unit/operations_v1/test_operations_rest_client.py @@ -351,11 +351,16 @@ def test_operations_client_client_options( with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): - if hasattr(google.auth.transport.mtls, "should_use_client_cert"): - pytest.skip( - "The should_use_client_cert function is available in this " - "version of google-auth. Skipping this test." - ) + if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + with pytest.raises(ValueError): + client = client_class() + else: + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport_name + ) # Check the case quota_project_id is provided options = client_options.ClientOptions(quota_project_id="octopus") From 8bfc4ba4b9a14ff577ee7b027d449b0ceb037d41 Mon Sep 17 00:00:00 2001 From: Radhika Agrawal Date: Wed, 3 Dec 2025 22:36:59 +0530 Subject: [PATCH 4/5] fix: fix the lint errors existing Signed-off-by: Radhika Agrawal --- tests/unit/operations_v1/test_operations_rest_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/operations_v1/test_operations_rest_client.py b/tests/unit/operations_v1/test_operations_rest_client.py index 5dfe55f48..63c144429 100644 --- a/tests/unit/operations_v1/test_operations_rest_client.py +++ b/tests/unit/operations_v1/test_operations_rest_client.py @@ -355,11 +355,11 @@ def test_operations_client_client_options( with pytest.raises(ValueError): client = client_class() else: - with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class( credentials=ga_credentials.AnonymousCredentials(), - transport=transport_name + transport=transport_name, ) # Check the case quota_project_id is provided From 4cb55e0878450c6b79a6e83b3d904e6ebdd05df9 Mon Sep 17 00:00:00 2001 From: Radhika Agrawal Date: Thu, 4 Dec 2025 11:34:02 +0530 Subject: [PATCH 5/5] chore: Update comments on test and update google.auth check to be done by version number Signed-off-by: Radhika Agrawal --- .../test_operations_rest_client.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/unit/operations_v1/test_operations_rest_client.py b/tests/unit/operations_v1/test_operations_rest_client.py index 63c144429..e513db7a4 100644 --- a/tests/unit/operations_v1/test_operations_rest_client.py +++ b/tests/unit/operations_v1/test_operations_rest_client.py @@ -35,6 +35,7 @@ from google.api_core import client_options from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 +from google.api_core import parse_version_to_tuple from google.api_core.operations_v1 import AbstractOperationsClient import google.auth @@ -42,6 +43,7 @@ from google.api_core.operations_v1 import pagers_async from google.api_core.operations_v1 import transports from google.auth import credentials as ga_credentials +from google.auth import __version__ as auth_version from google.auth.exceptions import MutualTLSChannelError from google.longrunning import operations_pb2 from google.oauth2 import service_account @@ -346,14 +348,23 @@ def test_operations_client_client_options( with pytest.raises(MutualTLSChannelError): client = client_class() - # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value and - # should_use_client_cert is unavailable. + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value with mock.patch.dict( os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} ): - if not hasattr(google.auth.transport.mtls, "should_use_client_cert"): + # Test behavior for google.auth versions < 2.43.0. + # These versions do not have the updated mtls.should_use_client_cert logic. + # Verify that a ValueError is raised when GOOGLE_API_USE_CLIENT_CERTIFICATE + # is set to an unsupported value, as expected in these older versions. + if parse_version_to_tuple(auth_version) < (2, 43, 0): with pytest.raises(ValueError): client = client_class() + # Test behavior for google.auth versions >= 2.43.0. + # In these versions, if GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an + # unsupported value (e.g., not 'true' or 'false'), the expected behavior + # of the internal google.auth.mtls.should_use_client_cert() function + # is to return False. Expect should_use_client_cert to return False, so + # client creation should proceed without requiring a client certificate. else: with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None