From 94a1d956a78acbc9e3e2391b8335a0907191a435 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Tue, 25 Aug 2026 20:56:24 -0700 Subject: [PATCH 01/11] feat: Add retry for cert rotation handling feat: Add retry for cert rotation handling --- .../google/auth/aio/transport/sessions.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index d88162667bda..c55be4670549 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -15,6 +15,8 @@ import asyncio from contextlib import asynccontextmanager import functools +import http.client as http_client +import logging import time from typing import Mapping, Optional, TYPE_CHECKING, Union import warnings @@ -26,6 +28,8 @@ from google.auth.exceptions import TimeoutError import google.auth.transport._mtls_helper +_LOGGER = logging.getLogger(__name__) + if TYPE_CHECKING: # pragma: NO COVER import aiohttp from aiohttp import ClientTimeout # type: ignore @@ -310,6 +314,32 @@ async def request( url, method, data, headers, actual_timeout, **kwargs ) ) + if response.status_code == http_client.UNAUTHORIZED: + if self.is_mtls: + call_cert_bytes, call_key_bytes, cached_fingerprint, current_cert_fingerprint = google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response( + self._cached_cert + ) + if cached_fingerprint != current_cert_fingerprint: + try: + _LOGGER.info( + "Client certificate has changed, reconfiguring mTLS " + "channel." + ) + await self.configure_mtls_channel( + lambda: (call_cert_bytes, call_key_bytes) + ) + continue + except Exception as e: + _LOGGER.error("Failed to reconfigure mTLS channel: %s", e) + raise exceptions.MutualTLSChannelError( + "Failed to reconfigure mTLS channel" + ) from e + else: + _LOGGER.info( + "Skipping reconfiguration of mTLS channel because the client" + " certificate has not changed." + ) + if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES: break return response From 420447c1d1bc71f29dc765c391f4bfb5ce6008bf Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Tue, 25 Aug 2026 20:58:25 -0700 Subject: [PATCH 02/11] chore: Add tests for MTLS certificate rotation behavior --- .../tests/transport/aio/test_sessions_mtls.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index b68766ca5b5d..740c6ac84ba2 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -344,3 +344,79 @@ async def test_configure_mtls_channel_close_exception_does_not_abort(self): assert session._is_mtls is True assert session._cached_cert == b"fake_cert_data" await session.close() + + @pytest.mark.asyncio + async def test_cert_rotation_failure_raises_error(self): + mock_creds = mock.AsyncMock(spec=credentials.Credentials) + mock_creds.before_request = mock.AsyncMock(return_value=None) + + mock_auth_req = mock.AsyncMock() + mock_resp = mock.Mock() + import http.client as http_client + mock_resp.status_code = http_client.UNAUTHORIZED + mock_auth_req.return_value = mock_resp + + session = sessions.AsyncAuthorizedSession(mock_creds, auth_request=mock_auth_req) + session._is_mtls = True + session._cached_cert = b"old_cert" + + new_cert = b"new_cert" + new_key = b"new_key" + + with mock.patch("google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response") as mock_check, \ + mock.patch.object(session, "configure_mtls_channel", new_callable=mock.AsyncMock) as mock_conf: + + mock_check.return_value = (new_cert, new_key, b"old_fp", b"new_fp") + mock_conf.side_effect = Exception("Failed to reconfigure") + + with pytest.raises(exceptions.MutualTLSChannelError): + await session.request("GET", "http://example.com") + + mock_check.assert_called_once() + mock_conf.assert_called_once() + + @pytest.mark.asyncio + async def test_cert_rotation_check_params_fails(self): + mock_creds = mock.AsyncMock(spec=credentials.Credentials) + mock_auth_req = mock.AsyncMock() + mock_resp = mock.Mock() + import http.client as http_client + mock_resp.status_code = http_client.UNAUTHORIZED + mock_auth_req.return_value = mock_resp + + session = sessions.AsyncAuthorizedSession(mock_creds, auth_request=mock_auth_req) + session._is_mtls = True + session._cached_cert = b"cached_cert" + + with mock.patch( + "google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response", + side_effect=Exception("check_params failed"), + ) as mock_check_params: + with pytest.raises(Exception, match="check_params failed"): + await session.request("GET", "http://example.com") + + mock_check_params.assert_called_once() + + @pytest.mark.asyncio + async def test_no_cert_rotation_when_cert_match_and_mTLS_enabled(self): + mock_creds = mock.AsyncMock(spec=credentials.Credentials) + mock_auth_req = mock.AsyncMock() + mock_resp = mock.Mock() + import http.client as http_client + mock_resp.status_code = http_client.UNAUTHORIZED + mock_auth_req.return_value = mock_resp + + session = sessions.AsyncAuthorizedSession(mock_creds, auth_request=mock_auth_req) + session._is_mtls = True + session._cached_cert = b"old_cert" + + with mock.patch( + "google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response", + ) as mock_check, mock.patch.object(session, "configure_mtls_channel", new_callable=mock.AsyncMock) as mock_conf: + # same fingerprint, so no call to configure_mtls_channel + mock_check.return_value = (b"new_cert", b"new_key", b"same_fp", b"same_fp") + + await session.request("GET", "http://example.com") + + mock_check.assert_called_once() + mock_conf.assert_not_called() From 907cf0083a8120259ab10b2b7a503c8e8ea139b1 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Wed, 26 Aug 2026 10:34:10 -0700 Subject: [PATCH 03/11] Update packages/google-auth/tests/transport/aio/test_sessions_mtls.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../google-auth/tests/transport/aio/test_sessions_mtls.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index 740c6ac84ba2..f448c57073b2 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -392,9 +392,8 @@ async def test_cert_rotation_check_params_fails(self): "google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response", side_effect=Exception("check_params failed"), ) as mock_check_params: - with pytest.raises(Exception, match="check_params failed"): - await session.request("GET", "http://example.com") - + resp = await session.request("GET", "http://example.com") + assert resp == mock_resp mock_check_params.assert_called_once() @pytest.mark.asyncio From cc850b13e16e2ed1b297a00d4e2ecd958eaa5b6c Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Wed, 26 Aug 2026 10:38:02 -0700 Subject: [PATCH 04/11] Improve error handling for mTLS reconfiguration Handle exceptions during mTLS reconfiguration with warnings instead of errors. --- .../google/auth/aio/transport/sessions.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index c55be4670549..6ace722c2814 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -315,7 +315,7 @@ async def request( ) ) if response.status_code == http_client.UNAUTHORIZED: - if self.is_mtls: + try: call_cert_bytes, call_key_bytes, cached_fingerprint, current_cert_fingerprint = google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response( self._cached_cert ) @@ -330,15 +330,20 @@ async def request( ) continue except Exception as e: - _LOGGER.error("Failed to reconfigure mTLS channel: %s", e) - raise exceptions.MutualTLSChannelError( - "Failed to reconfigure mTLS channel" - ) from e + _LOGGER.warning( + "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", + e + ) else: - _LOGGER.info( + _LOGGER.info( "Skipping reconfiguration of mTLS channel because the client" " certificate has not changed." ) + except Exception as e: + _LOGGER.warning( + "Failed to check client certificate parameters: %s. Proceeding with original response.", + e, + ) if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES: break From a44acb0654b0ec21cbe49e98aa8ad1c638001d38 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Wed, 26 Aug 2026 11:19:57 -0700 Subject: [PATCH 05/11] fix: Rename test_cert_rotation_failure to test_cert_rotation_failure_logs Updated test logic to assert response instead of expecting an error. --- .../google-auth/tests/transport/aio/test_sessions_mtls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index f448c57073b2..0b0a0ec87229 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -346,7 +346,7 @@ async def test_configure_mtls_channel_close_exception_does_not_abort(self): await session.close() @pytest.mark.asyncio - async def test_cert_rotation_failure_raises_error(self): + async def test_cert_rotation_failure_logs(self): mock_creds = mock.AsyncMock(spec=credentials.Credentials) mock_creds.before_request = mock.AsyncMock(return_value=None) @@ -369,8 +369,8 @@ async def test_cert_rotation_failure_raises_error(self): mock_check.return_value = (new_cert, new_key, b"old_fp", b"new_fp") mock_conf.side_effect = Exception("Failed to reconfigure") - with pytest.raises(exceptions.MutualTLSChannelError): - await session.request("GET", "http://example.com") + resp = await session.request("GET", "http://example.com") + assert resp == mock_resp mock_check.assert_called_once() mock_conf.assert_called_once() From 984e47c3388abd43271ac9e0d7c6f198534db176 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Wed, 26 Aug 2026 14:54:48 -0700 Subject: [PATCH 06/11] chore: Refactor MTLS parameter check on unauthorized response o use async executor Refactor unauthorized response handling to use async executor for MTLS parameter checks. --- .../google-auth/google/auth/aio/transport/sessions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index 6ace722c2814..1a4b9c468f9a 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -316,7 +316,13 @@ async def request( ) if response.status_code == http_client.UNAUTHORIZED: try: - call_cert_bytes, call_key_bytes, cached_fingerprint, current_cert_fingerprint = google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response( + ( + call_cert_bytes, + call_key_bytes, + cached_fingerprint, + current_cert_fingerprint + ) = await mtls._run_in_executor( + google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response, self._cached_cert ) if cached_fingerprint != current_cert_fingerprint: From 30341bc5acfbc6155165c8062852e670acd7dedb Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Wed, 26 Aug 2026 22:03:28 -0700 Subject: [PATCH 07/11] chore: Reset mTLS init task upon client certificate change chore: Reset mTLS init task upon client certificate change --- packages/google-auth/google/auth/aio/transport/sessions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index 1a4b9c468f9a..c4ef00528d0a 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -331,6 +331,8 @@ async def request( "Client certificate has changed, reconfiguring mTLS " "channel." ) + if self._mtls_init_task and self._mtls_init_task.done(): + self._mtls_init_task = None await self.configure_mtls_channel( lambda: (call_cert_bytes, call_key_bytes) ) From 1c068dce457d288fa3a98a3534158cc3a5b53656 Mon Sep 17 00:00:00 2001 From: Radhika Agrawal Date: Thu, 27 Aug 2026 17:48:12 +0000 Subject: [PATCH 08/11] fix: fix the lint errors Signed-off-by: Radhika Agrawal --- .../google/auth/aio/transport/sessions.py | 18 ++++++------- .../tests/transport/aio/test_sessions_mtls.py | 27 ++++++++++++++----- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index c4ef00528d0a..22e11c74f577 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -28,8 +28,6 @@ from google.auth.exceptions import TimeoutError import google.auth.transport._mtls_helper -_LOGGER = logging.getLogger(__name__) - if TYPE_CHECKING: # pragma: NO COVER import aiohttp from aiohttp import ClientTimeout # type: ignore @@ -41,6 +39,8 @@ except (ImportError, AttributeError): ClientTimeout = None +_LOGGER = logging.getLogger(__name__) + # Tracks the internal aiohttp installation and usage try: @@ -317,13 +317,13 @@ async def request( if response.status_code == http_client.UNAUTHORIZED: try: ( - call_cert_bytes, - call_key_bytes, - cached_fingerprint, - current_cert_fingerprint + call_cert_bytes, + call_key_bytes, + cached_fingerprint, + current_cert_fingerprint, ) = await mtls._run_in_executor( google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response, - self._cached_cert + self._cached_cert, ) if cached_fingerprint != current_cert_fingerprint: try: @@ -340,10 +340,10 @@ async def request( except Exception as e: _LOGGER.warning( "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", - e + e, ) else: - _LOGGER.info( + _LOGGER.info( "Skipping reconfiguration of mTLS channel because the client" " certificate has not changed." ) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index 0b0a0ec87229..764bfc8c40e3 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -353,19 +353,24 @@ async def test_cert_rotation_failure_logs(self): mock_auth_req = mock.AsyncMock() mock_resp = mock.Mock() import http.client as http_client + mock_resp.status_code = http_client.UNAUTHORIZED mock_auth_req.return_value = mock_resp - session = sessions.AsyncAuthorizedSession(mock_creds, auth_request=mock_auth_req) + session = sessions.AsyncAuthorizedSession( + mock_creds, auth_request=mock_auth_req + ) session._is_mtls = True session._cached_cert = b"old_cert" new_cert = b"new_cert" new_key = b"new_key" - with mock.patch("google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response") as mock_check, \ - mock.patch.object(session, "configure_mtls_channel", new_callable=mock.AsyncMock) as mock_conf: - + with mock.patch( + "google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response" + ) as mock_check, mock.patch.object( + session, "configure_mtls_channel", new_callable=mock.AsyncMock + ) as mock_conf: mock_check.return_value = (new_cert, new_key, b"old_fp", b"new_fp") mock_conf.side_effect = Exception("Failed to reconfigure") @@ -381,10 +386,13 @@ async def test_cert_rotation_check_params_fails(self): mock_auth_req = mock.AsyncMock() mock_resp = mock.Mock() import http.client as http_client + mock_resp.status_code = http_client.UNAUTHORIZED mock_auth_req.return_value = mock_resp - session = sessions.AsyncAuthorizedSession(mock_creds, auth_request=mock_auth_req) + session = sessions.AsyncAuthorizedSession( + mock_creds, auth_request=mock_auth_req + ) session._is_mtls = True session._cached_cert = b"cached_cert" @@ -402,16 +410,21 @@ async def test_no_cert_rotation_when_cert_match_and_mTLS_enabled(self): mock_auth_req = mock.AsyncMock() mock_resp = mock.Mock() import http.client as http_client + mock_resp.status_code = http_client.UNAUTHORIZED mock_auth_req.return_value = mock_resp - session = sessions.AsyncAuthorizedSession(mock_creds, auth_request=mock_auth_req) + session = sessions.AsyncAuthorizedSession( + mock_creds, auth_request=mock_auth_req + ) session._is_mtls = True session._cached_cert = b"old_cert" with mock.patch( "google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response", - ) as mock_check, mock.patch.object(session, "configure_mtls_channel", new_callable=mock.AsyncMock) as mock_conf: + ) as mock_check, mock.patch.object( + session, "configure_mtls_channel", new_callable=mock.AsyncMock + ) as mock_conf: # same fingerprint, so no call to configure_mtls_channel mock_check.return_value = (b"new_cert", b"new_key", b"same_fp", b"same_fp") From 6fb1e863826bfc34c76f0d3bfbbf5610766fd07f Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Thu, 27 Aug 2026 13:00:32 -0700 Subject: [PATCH 09/11] chore: Refactor mTLS channel reconfiguration logic for adding mTLS check after 401 check chore: Refactor mTLS channel reconfiguration logic for adding mTLS check after 401 check --- .../google/auth/aio/transport/sessions.py | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index 22e11c74f577..29d22eaa0e1d 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -40,6 +40,7 @@ ClientTimeout = None _LOGGER = logging.getLogger(__name__) +MTLS_URL_PREFIXES = ["mtls.googleapis.com", "mtls.sandbox.googleapis.com"] # Tracks the internal aiohttp installation and usage @@ -315,43 +316,46 @@ async def request( ) ) if response.status_code == http_client.UNAUTHORIZED: - try: - ( - call_cert_bytes, - call_key_bytes, - cached_fingerprint, - current_cert_fingerprint, - ) = await mtls._run_in_executor( - google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response, - self._cached_cert, - ) - if cached_fingerprint != current_cert_fingerprint: - try: + if getattr(self, "is_mtls", False) and any( + prefix in url for prefix in MTLS_URL_PREFIXES + ): + try: + ( + call_cert_bytes, + call_key_bytes, + cached_fingerprint, + current_cert_fingerprint, + ) = await mtls._run_in_executor( + google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response, + self._cached_cert, + ) + if cached_fingerprint != current_cert_fingerprint: + try: + _LOGGER.info( + "Client certificate has changed, reconfiguring mTLS " + "channel." + ) + if self._mtls_init_task and self._mtls_init_task.done(): + self._mtls_init_task = None + await self.configure_mtls_channel( + lambda: (call_cert_bytes, call_key_bytes) + ) + continue + except Exception as e: + _LOGGER.warning( + "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", + e, + ) + else: _LOGGER.info( - "Client certificate has changed, reconfiguring mTLS " - "channel." - ) - if self._mtls_init_task and self._mtls_init_task.done(): - self._mtls_init_task = None - await self.configure_mtls_channel( - lambda: (call_cert_bytes, call_key_bytes) + "Skipping reconfiguration of mTLS channel because the client" + " certificate has not changed." ) - continue - except Exception as e: - _LOGGER.warning( - "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", - e, - ) - else: - _LOGGER.info( - "Skipping reconfiguration of mTLS channel because the client" - " certificate has not changed." + except Exception as e: + _LOGGER.warning( + "Failed to check client certificate parameters: %s. Proceeding with original response.", + e, ) - except Exception as e: - _LOGGER.warning( - "Failed to check client certificate parameters: %s. Proceeding with original response.", - e, - ) if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES: break From 2cdfe2d5197dcf29a1ffa613ff993169107a8a70 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Thu, 27 Aug 2026 16:05:18 -0700 Subject: [PATCH 10/11] chore: Add mTLS rotation lock for certificate management Implement mTLS rotation lock to prevent race conditions during certificate reconfiguration. --- .../google/auth/aio/transport/sessions.py | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index 29d22eaa0e1d..b9bc251053b4 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -153,6 +153,7 @@ def __init__( "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value." ) self._auth_request = _auth_request + self._mtls_rotation_lock = asyncio.Lock() async def configure_mtls_channel(self, client_cert_callback=None): """Configure the client certificate and key for SSL connection. @@ -319,43 +320,54 @@ async def request( if getattr(self, "is_mtls", False) and any( prefix in url for prefix in MTLS_URL_PREFIXES ): - try: - ( - call_cert_bytes, - call_key_bytes, - cached_fingerprint, - current_cert_fingerprint, - ) = await mtls._run_in_executor( - google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response, - self._cached_cert, - ) - if cached_fingerprint != current_cert_fingerprint: + # Snapshot the stale certificate state BEFORE acquiring the lock. + # This represents the cert that caused the 401 rejection. + stale_cert = self._cached_cert + + # Wait in line to acquire the lock + async with self._mtls_rotation_lock: + # Check Did another coroutine already reconfigure mTLS + if self._cached_cert != stale_cert: + # Yes! Another request already updated the channel + pass + else: try: - _LOGGER.info( - "Client certificate has changed, reconfiguring mTLS " - "channel." + ( + call_cert_bytes, + call_key_bytes, + cached_fingerprint, + current_cert_fingerprint, + ) = await mtls._run_in_executor( + google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response, + self._cached_cert, ) - if self._mtls_init_task and self._mtls_init_task.done(): - self._mtls_init_task = None - await self.configure_mtls_channel( - lambda: (call_cert_bytes, call_key_bytes) - ) - continue + if cached_fingerprint != current_cert_fingerprint: + try: + _LOGGER.info( + "Client certificate has changed, reconfiguring mTLS " + "channel." + ) + if self._mtls_init_task and self._mtls_init_task.done(): + self._mtls_init_task = None + await self.configure_mtls_channel( + lambda: (call_cert_bytes, call_key_bytes) + ) + continue + except Exception as e: + _LOGGER.warning( + "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", + e, + ) + else: + _LOGGER.info( + "Skipping reconfiguration of mTLS channel because the client" + " certificate has not changed." + ) except Exception as e: _LOGGER.warning( - "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", + "Failed to check client certificate parameters: %s. Proceeding with original response.", e, ) - else: - _LOGGER.info( - "Skipping reconfiguration of mTLS channel because the client" - " certificate has not changed." - ) - except Exception as e: - _LOGGER.warning( - "Failed to check client certificate parameters: %s. Proceeding with original response.", - e, - ) if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES: break From d734731ca21a37b137ff2c0c65c9fd54d4e55458 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Thu, 27 Aug 2026 16:21:53 -0700 Subject: [PATCH 11/11] chore: Log mTLS channel reconfiguration failure as error chore: Change warning to error log for mTLS channel reconfiguration failure. --- .../google-auth/google/auth/aio/transport/sessions.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index b9bc251053b4..6d7c8d5199ea 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -283,6 +283,8 @@ async def request( google.auth.exceptions.TimeoutError: If the method does not complete within the configured `max_allowed_time` or the request exceeds the configured `timeout`. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS + channel reconfiguration fails for any reason during certificate rotation. """ if self._mtls_init_task: try: @@ -354,10 +356,10 @@ async def request( ) continue except Exception as e: - _LOGGER.warning( - "Failed to reconfigure mTLS channel: %s. Proceeding with original response.", - e, - ) + _LOGGER.error("Failed to reconfigure mTLS channel: %s", e) + raise exceptions.MutualTLSChannelError( + "Failed to reconfigure mTLS channel" + ) from e else: _LOGGER.info( "Skipping reconfiguration of mTLS channel because the client"