Skip to content

Commit 7bfa41a

Browse files
authored
fix(auth): Agentic Identites mTLS gaps fix _is_mtls and SslCredentials. (#17387)
Resolve some mTLS transport state and gRPC workload certificate issues - Fix inconsistent `_is_mtls` flag in `urllib3` on fallback to default HTTP. - Reset `_is_mtls` to `False` in async `sessions` if a custom request handler cannot be reconfigured. - Update gRPC `SslCredentials` to recognize workload certificates by checking `has_default_client_cert_source` instead of only SecureConnect.
1 parent 1c24692 commit 7bfa41a

8 files changed

Lines changed: 661 additions & 94 deletions

File tree

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

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,16 @@ async def _do_configure():
182182
google.auth.transport._mtls_helper.check_use_client_cert
183183
)
184184
if not use_client_cert:
185-
self._is_mtls = False
186185
return
187186

188187
try:
189188
(
190-
self._is_mtls,
189+
is_mtls,
191190
cert,
192191
key,
193192
) = await mtls.get_client_cert_and_key(client_cert_callback)
194193

195-
if self._is_mtls:
196-
self._cached_cert = cert
194+
if is_mtls:
197195
ssl_context = await mtls._run_in_executor(
198196
mtls.make_client_cert_ssl_context, cert, key
199197
)
@@ -208,9 +206,13 @@ async def _do_configure():
208206
old_auth_request = self._auth_request
209207
self._auth_request = AiohttpRequest(session=new_session)
210208

211-
await old_auth_request.close()
209+
try:
210+
await old_auth_request.close()
211+
except Exception:
212+
# Suppress so it doesn't abort the mTLS configuration
213+
pass
212214
else:
213-
self._is_mtls = False
215+
is_mtls = False
214216
warnings.warn(
215217
"Attempted to establish mTLS, but a custom async transport was provided. "
216218
"google-auth cannot automatically configure custom transports for mTLS. "
@@ -220,12 +222,13 @@ async def _do_configure():
220222
UserWarning,
221223
)
222224

223-
except (
224-
exceptions.ClientCertError,
225-
ImportError,
226-
OSError,
227-
) as caught_exc:
228-
self._is_mtls = False
225+
self._is_mtls = is_mtls
226+
if is_mtls:
227+
self._cached_cert = cert
228+
else:
229+
self._cached_cert = None
230+
231+
except Exception as caught_exc:
229232
new_exc = exceptions.MutualTLSChannelError(caught_exc)
230233
raise new_exc from caught_exc
231234

@@ -586,4 +589,10 @@ async def close(self) -> None:
586589
"""
587590
Close the underlying auth request session.
588591
"""
592+
if self._mtls_init_task and not self._mtls_init_task.done():
593+
self._mtls_init_task.cancel()
594+
try:
595+
await self._mtls_init_task
596+
except asyncio.CancelledError:
597+
pass
589598
await self._auth_request.close()

packages/google-auth/google/auth/transport/grpc.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from google.auth import exceptions
2222
from google.auth.transport import _mtls_helper
23+
from google.auth.transport import mtls
2324
from google.oauth2 import service_account
2425

2526
try:
@@ -279,14 +280,19 @@ def my_client_cert_callback():
279280
class SslCredentials:
280281
"""Class for application default SSL credentials.
281282
282-
The behavior is controlled by `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment
283-
variable whose default value is `false`. Client certificate will not be used
284-
unless the environment variable is explicitly set to `true`. See
285-
https://google.aip.dev/auth/4114
283+
Mutual TLS (mTLS) is enabled if either:
286284
287-
If the environment variable is `true`, then for devices with endpoint verification
288-
support, a device certificate will be automatically loaded and mutual TLS will
289-
be established.
285+
1. The `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is explicitly
286+
set to `"true"`.
287+
2. The `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset or empty,
288+
but a valid workload certificate configuration is found (e.g., via the
289+
`GOOGLE_API_CERTIFICATE_CONFIG` environment variable or the default gcloud config path).
290+
291+
See https://google.aip.dev/auth/4114 for client certificate discovery details.
292+
293+
If client certificate usage is enabled, then for devices with endpoint
294+
verification support, a device certificate will be automatically loaded and
295+
mutual TLS will be established.
290296
See https://cloud.google.com/endpoint-verification/docs/overview.
291297
"""
292298

@@ -295,11 +301,7 @@ def __init__(self):
295301
if not use_client_cert:
296302
self._is_mtls = False
297303
else:
298-
# Load client SSL credentials.
299-
metadata_path = _mtls_helper._check_config_path(
300-
_mtls_helper.CONTEXT_AWARE_METADATA_PATH
301-
)
302-
self._is_mtls = metadata_path is not None
304+
self._is_mtls = mtls.has_default_client_cert_source()
303305

304306
@property
305307
def ssl_credentials(self):
@@ -319,11 +321,15 @@ def ssl_credentials(self):
319321
"""
320322
if self._is_mtls:
321323
try:
322-
_, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
323-
self._ssl_credentials = grpc.ssl_channel_credentials(
324-
certificate_chain=cert, private_key=key
325-
)
326-
except exceptions.ClientCertError as caught_exc:
324+
has_cert, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
325+
if has_cert:
326+
self._ssl_credentials = grpc.ssl_channel_credentials(
327+
certificate_chain=cert, private_key=key
328+
)
329+
else:
330+
self._ssl_credentials = grpc.ssl_channel_credentials()
331+
self._is_mtls = False
332+
except (exceptions.ClientCertError, OSError) as caught_exc:
327333
new_exc = exceptions.MutualTLSChannelError(caught_exc)
328334
raise new_exc from caught_exc
329335
else:

packages/google-auth/google/auth/transport/requests.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -443,42 +443,49 @@ def configure_mtls_channel(self, client_cert_callback=None):
443443
444444
Raises:
445445
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
446-
creation failed for any reason.
446+
creation failed for any reason. The existing session state (such
447+
as adapter mounts) remains unmodified if this error is raised.
447448
"""
448449
use_client_cert = google.auth.transport._mtls_helper.check_use_client_cert()
449450
if not use_client_cert:
450-
self._is_mtls = False
451451
return
452+
452453
try:
453454
import OpenSSL
454455
except ImportError as caught_exc:
455-
self._is_mtls = False
456456
new_exc = exceptions.MutualTLSChannelError(caught_exc)
457457
raise new_exc from caught_exc
458458

459459
try:
460460
(
461-
self._is_mtls,
461+
is_mtls,
462462
cert,
463463
key,
464464
) = google.auth.transport._mtls_helper.get_client_cert_and_key(
465465
client_cert_callback
466466
)
467467

468-
if self._is_mtls:
469-
mtls_adapter = _MutualTlsAdapter(cert, key)
470-
self._cached_cert = cert
471-
self.mount("https://", mtls_adapter)
468+
if is_mtls:
469+
new_adapter = _MutualTlsAdapter(cert, key)
470+
else:
471+
new_adapter = requests.adapters.HTTPAdapter()
472472
except (
473473
exceptions.ClientCertError,
474474
ImportError,
475475
OSError,
476476
OpenSSL.crypto.Error,
477477
) as caught_exc:
478-
self._is_mtls = False
479478
new_exc = exceptions.MutualTLSChannelError(caught_exc)
480479
raise new_exc from caught_exc
481480

481+
self.mount("https://", new_adapter)
482+
self._is_mtls = is_mtls
483+
if is_mtls:
484+
self._cached_cert = cert
485+
else:
486+
if hasattr(self, "_cached_cert"):
487+
del self._cached_cert
488+
482489
def request(
483490
self,
484491
method,

packages/google-auth/google/auth/transport/urllib3.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -332,18 +332,16 @@ def configure_mtls_channel(self, client_cert_callback=None):
332332
333333
Raises:
334334
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
335-
creation failed for any reason.
335+
creation failed for any reason. The existing channel state (the
336+
HTTP client) remains unmodified if this error is raised.
336337
"""
337338
use_client_cert = transport._mtls_helper.check_use_client_cert()
338339
if not use_client_cert:
339-
self._is_mtls = False
340340
return False
341-
else:
342-
self._is_mtls = True
341+
343342
try:
344343
import OpenSSL
345344
except ImportError as caught_exc:
346-
self._is_mtls = False
347345
new_exc = exceptions.MutualTLSChannelError(caught_exc)
348346
raise new_exc from caught_exc
349347

@@ -353,21 +351,29 @@ def configure_mtls_channel(self, client_cert_callback=None):
353351
)
354352

355353
if found_cert_key:
356-
self.http = _make_mutual_tls_http(cert, key)
357-
self._cached_cert = cert
354+
new_http = _make_mutual_tls_http(cert, key)
355+
new_is_mtls = True
358356
else:
359-
self.http = _make_default_http()
360-
self._is_mtls = False
357+
new_http = _make_default_http()
358+
new_is_mtls = False
361359
except (
362360
exceptions.ClientCertError,
363361
ImportError,
364362
OSError,
365363
OpenSSL.crypto.Error,
366364
) as caught_exc:
367-
self._is_mtls = False
368365
new_exc = exceptions.MutualTLSChannelError(caught_exc)
369366
raise new_exc from caught_exc
370367

368+
self.http = new_http
369+
self._is_mtls = new_is_mtls
370+
self._request.http = new_http
371+
if new_is_mtls:
372+
self._cached_cert = cert
373+
else:
374+
if hasattr(self, "_cached_cert"):
375+
del self._cached_cert
376+
371377
if self._has_user_provided_http:
372378
self._has_user_provided_http = False
373379
warnings.warn(

0 commit comments

Comments
 (0)