Skip to content

Commit 2cdfe2d

Browse files
chore: Add mTLS rotation lock for certificate management
Implement mTLS rotation lock to prevent race conditions during certificate reconfiguration.
1 parent 6fb1e86 commit 2cdfe2d

1 file changed

Lines changed: 43 additions & 31 deletions

File tree

  • packages/google-auth/google/auth/aio/transport

packages/google-auth/google/auth/aio/transport/sessions.py

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def __init__(
153153
"`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value."
154154
)
155155
self._auth_request = _auth_request
156+
self._mtls_rotation_lock = asyncio.Lock()
156157

157158
async def configure_mtls_channel(self, client_cert_callback=None):
158159
"""Configure the client certificate and key for SSL connection.
@@ -319,43 +320,54 @@ async def request(
319320
if getattr(self, "is_mtls", False) and any(
320321
prefix in url for prefix in MTLS_URL_PREFIXES
321322
):
322-
try:
323-
(
324-
call_cert_bytes,
325-
call_key_bytes,
326-
cached_fingerprint,
327-
current_cert_fingerprint,
328-
) = await mtls._run_in_executor(
329-
google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response,
330-
self._cached_cert,
331-
)
332-
if cached_fingerprint != current_cert_fingerprint:
323+
# Snapshot the stale certificate state BEFORE acquiring the lock.
324+
# This represents the cert that caused the 401 rejection.
325+
stale_cert = self._cached_cert
326+
327+
# Wait in line to acquire the lock
328+
async with self._mtls_rotation_lock:
329+
# Check Did another coroutine already reconfigure mTLS
330+
if self._cached_cert != stale_cert:
331+
# Yes! Another request already updated the channel
332+
pass
333+
else:
333334
try:
334-
_LOGGER.info(
335-
"Client certificate has changed, reconfiguring mTLS "
336-
"channel."
335+
(
336+
call_cert_bytes,
337+
call_key_bytes,
338+
cached_fingerprint,
339+
current_cert_fingerprint,
340+
) = await mtls._run_in_executor(
341+
google.auth.transport._mtls_helper.check_parameters_for_unauthorized_response,
342+
self._cached_cert,
337343
)
338-
if self._mtls_init_task and self._mtls_init_task.done():
339-
self._mtls_init_task = None
340-
await self.configure_mtls_channel(
341-
lambda: (call_cert_bytes, call_key_bytes)
342-
)
343-
continue
344+
if cached_fingerprint != current_cert_fingerprint:
345+
try:
346+
_LOGGER.info(
347+
"Client certificate has changed, reconfiguring mTLS "
348+
"channel."
349+
)
350+
if self._mtls_init_task and self._mtls_init_task.done():
351+
self._mtls_init_task = None
352+
await self.configure_mtls_channel(
353+
lambda: (call_cert_bytes, call_key_bytes)
354+
)
355+
continue
356+
except Exception as e:
357+
_LOGGER.warning(
358+
"Failed to reconfigure mTLS channel: %s. Proceeding with original response.",
359+
e,
360+
)
361+
else:
362+
_LOGGER.info(
363+
"Skipping reconfiguration of mTLS channel because the client"
364+
" certificate has not changed."
365+
)
344366
except Exception as e:
345367
_LOGGER.warning(
346-
"Failed to reconfigure mTLS channel: %s. Proceeding with original response.",
368+
"Failed to check client certificate parameters: %s. Proceeding with original response.",
347369
e,
348370
)
349-
else:
350-
_LOGGER.info(
351-
"Skipping reconfiguration of mTLS channel because the client"
352-
" certificate has not changed."
353-
)
354-
except Exception as e:
355-
_LOGGER.warning(
356-
"Failed to check client certificate parameters: %s. Proceeding with original response.",
357-
e,
358-
)
359371

360372
if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES:
361373
break

0 commit comments

Comments
 (0)