Skip to content

Commit 701f4d7

Browse files
Handle missing pyOpenSSL for inferred mTLS
1 parent adbabae commit 701f4d7

5 files changed

Lines changed: 100 additions & 25 deletions

File tree

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,22 @@ def client_cert_callback():
448448
return crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
449449

450450

451+
def _get_use_client_cert_env():
452+
"""Returns the configured client certificate opt-in environment value."""
453+
use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE)
454+
if use_client_cert is None or use_client_cert == "":
455+
use_client_cert = getenv(
456+
environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE
457+
)
458+
return use_client_cert
459+
460+
461+
def _is_client_cert_explicitly_enabled():
462+
"""Returns True if an environment variable explicitly enables client certs."""
463+
use_client_cert = _get_use_client_cert_env()
464+
return bool(use_client_cert and use_client_cert.lower() == "true")
465+
466+
451467
def check_use_client_cert():
452468
"""Returns boolean for whether the client certificate should be used for mTLS.
453469
@@ -462,11 +478,7 @@ def check_use_client_cert():
462478
Returns:
463479
bool: Whether the client certificate should be used for mTLS connection.
464480
"""
465-
use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE)
466-
if use_client_cert is None or use_client_cert == "":
467-
use_client_cert = getenv(
468-
environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE
469-
)
481+
use_client_cert = _get_use_client_cert_env()
470482

471483
# Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set.
472484
if use_client_cert:

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,10 @@ class AuthorizedSession(requests.Session):
313313
credentials' headers to the request and refreshing credentials as needed.
314314
315315
This class also supports mutual TLS via :meth:`configure_mtls_channel`
316-
method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
317-
environment variable must be explicitly set to ``true``, otherwise it does
318-
nothing. Assume the environment is set to ``true``, the method behaves in the
319-
following manner:
316+
method. Client certificate use is enabled when
317+
`GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to ``true`` or when it is inferred
318+
from a certificate configuration. When client certificate use is enabled, the
319+
method behaves in the following manner:
320320
321321
If client_cert_callback is provided, client certificate and private
322322
key are loaded using the callback; if client_cert_callback is None,
@@ -428,11 +428,12 @@ def __init__(
428428
def configure_mtls_channel(self, client_cert_callback=None):
429429
"""Configure the client certificate and key for SSL connection.
430430
431-
The function does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is
432-
explicitly set to `true`. In this case if client certificate and key are
433-
successfully obtained (from the given client_cert_callback or from application
434-
default SSL credentials), a :class:`_MutualTlsAdapter` instance will be mounted
435-
to "https://" prefix.
431+
The function does nothing unless client certificate use is enabled by
432+
`GOOGLE_API_USE_CLIENT_CERTIFICATE` or inferred from certificate
433+
configuration. In this case if client certificate and key are
434+
successfully obtained (from the given client_cert_callback or from
435+
application default SSL credentials), a :class:`_MutualTlsAdapter`
436+
instance will be mounted to "https://" prefix.
436437
437438
Args:
438439
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
@@ -452,6 +453,10 @@ def configure_mtls_channel(self, client_cert_callback=None):
452453
try:
453454
import OpenSSL
454455
except ImportError as caught_exc:
456+
if not _mtls_helper._is_client_cert_explicitly_enabled():
457+
_LOGGER.debug("pyOpenSSL is unavailable; disabling inferred mTLS.")
458+
self._is_mtls = False
459+
return
455460
new_exc = exceptions.MutualTLSChannelError(caught_exc)
456461
raise new_exc from caught_exc
457462

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ class AuthorizedHttp(RequestMethods): # type: ignore
215215
credentials' headers to the request and refreshing credentials as needed.
216216
217217
This class also supports mutual TLS via :meth:`configure_mtls_channel`
218-
method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
219-
environment variable must be explicitly set to `true`, otherwise it does
220-
nothing. Assume the environment is set to `true`, the method behaves in the
221-
following manner:
218+
method. Client certificate use is enabled when
219+
`GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to `true` or when it is inferred
220+
from a certificate configuration. When client certificate use is enabled, the
221+
method behaves in the following manner:
222222
If client_cert_callback is provided, client certificate and private
223223
key are loaded using the callback; if client_cert_callback is None,
224224
application default SSL credentials will be used. Exceptions are raised if
@@ -313,13 +313,14 @@ def __init__(
313313

314314
def configure_mtls_channel(self, client_cert_callback=None):
315315
"""Configures mutual TLS channel using the given client_cert_callback or
316-
application default SSL credentials. The behavior is controlled by
317-
`GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable.
318-
(1) If the environment variable value is `true`, the function returns True
319-
if the channel is mutual TLS and False otherwise. The `http` provided
320-
in the constructor will be overwritten.
321-
(2) If the environment variable is not set or `false`, the function does
322-
nothing and it always return False.
316+
application default SSL credentials. Client certificate use is enabled
317+
when `GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to `true` or when it is
318+
inferred from a certificate configuration.
319+
(1) If client certificate use is enabled, the function returns True if
320+
the channel is mutual TLS and False otherwise. The `http` provided in
321+
the constructor will be overwritten.
322+
(2) If client certificate use is disabled, the function does nothing and
323+
it always returns False.
323324
324325
Args:
325326
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
@@ -344,6 +345,10 @@ def configure_mtls_channel(self, client_cert_callback=None):
344345
try:
345346
import OpenSSL
346347
except ImportError as caught_exc:
348+
if not transport._mtls_helper._is_client_cert_explicitly_enabled():
349+
_LOGGER.debug("pyOpenSSL is unavailable; disabling inferred mTLS.")
350+
self._is_mtls = False
351+
return False
347352
new_exc = exceptions.MutualTLSChannelError(caught_exc)
348353
raise new_exc from caught_exc
349354

packages/google-auth/tests/transport/test_requests.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,32 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key):
501501
):
502502
auth_session.configure_mtls_channel()
503503

504+
@mock.patch("builtins.open", autospec=True)
505+
@mock.patch(
506+
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
507+
)
508+
def test_configure_mtls_channel_inferred_missing_openssl(
509+
self, mock_get_client_cert_and_key, mock_file
510+
):
511+
mock_file.side_effect = mock.mock_open(
512+
read_data='{"cert_configs": {"workload": "exists"}}'
513+
)
514+
auth_session = google.auth.transport.requests.AuthorizedSession(
515+
credentials=mock.Mock()
516+
)
517+
518+
with mock.patch.dict("sys.modules"):
519+
sys.modules["OpenSSL"] = None
520+
with mock.patch.dict(
521+
os.environ,
522+
{environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: "/path/to/config"},
523+
clear=True,
524+
):
525+
auth_session.configure_mtls_channel()
526+
527+
assert not auth_session.is_mtls
528+
mock_get_client_cert_and_key.assert_not_called()
529+
504530
@mock.patch(
505531
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
506532
)

packages/google-auth/tests/transport/test_urllib3.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,33 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key):
290290
):
291291
authed_http.configure_mtls_channel()
292292

293+
@mock.patch("builtins.open", autospec=True)
294+
@mock.patch(
295+
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
296+
)
297+
def test_configure_mtls_channel_inferred_missing_openssl(
298+
self, mock_get_client_cert_and_key, mock_file
299+
):
300+
mock_file.side_effect = mock.mock_open(
301+
read_data='{"cert_configs": {"workload": "exists"}}'
302+
)
303+
authed_http = google.auth.transport.urllib3.AuthorizedHttp(
304+
credentials=mock.Mock()
305+
)
306+
307+
with mock.patch.dict("sys.modules"):
308+
sys.modules["OpenSSL"] = None
309+
with mock.patch.dict(
310+
os.environ,
311+
{environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: "/path/to/config"},
312+
clear=True,
313+
):
314+
is_mtls = authed_http.configure_mtls_channel()
315+
316+
assert not is_mtls
317+
assert not authed_http._is_mtls
318+
mock_get_client_cert_and_key.assert_not_called()
319+
293320
@mock.patch(
294321
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
295322
)

0 commit comments

Comments
 (0)