Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions packages/google-auth/google/auth/transport/_mtls_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,22 @@ def client_cert_callback():
return crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)


def _get_use_client_cert_env():
"""Returns the configured client certificate opt-in environment value."""
use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE)
if use_client_cert is None or use_client_cert == "":
use_client_cert = getenv(
environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE
)
return use_client_cert
Comment on lines +453 to +458
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This logic for falling back between environment variables can be simplified using the or operator, which is more idiomatic in Python for handling None or empty string fallbacks.

    return getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE) or getenv(
        environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE
    )



def _is_client_cert_explicitly_enabled():
"""Returns True if an environment variable explicitly enables client certs."""
use_client_cert = _get_use_client_cert_env()
return bool(use_client_cert and use_client_cert.lower() == "true")
Comment on lines +463 to +464
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This can be simplified by inlining the environment variable check and using a default empty string to handle the None case, making the logic more concise.

Suggested change
use_client_cert = _get_use_client_cert_env()
return bool(use_client_cert and use_client_cert.lower() == "true")
return (_get_use_client_cert_env() or "").lower() == "true"



def check_use_client_cert():
"""Returns boolean for whether the client certificate should be used for mTLS.

Expand All @@ -462,11 +478,7 @@ def check_use_client_cert():
Returns:
bool: Whether the client certificate should be used for mTLS connection.
"""
use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE)
if use_client_cert is None or use_client_cert == "":
use_client_cert = getenv(
environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE
)
use_client_cert = _get_use_client_cert_env()

# Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set.
if use_client_cert:
Expand Down
23 changes: 14 additions & 9 deletions packages/google-auth/google/auth/transport/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,10 @@ class AuthorizedSession(requests.Session):
credentials' headers to the request and refreshing credentials as needed.

This class also supports mutual TLS via :meth:`configure_mtls_channel`
method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
environment variable must be explicitly set to ``true``, otherwise it does
nothing. Assume the environment is set to ``true``, the method behaves in the
following manner:
method. Client certificate use is enabled when
`GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to ``true`` or when it is inferred
from a certificate configuration. When client certificate use is enabled, the
method behaves in the following manner:

If client_cert_callback is provided, client certificate and private
key are loaded using the callback; if client_cert_callback is None,
Expand Down Expand Up @@ -428,11 +428,12 @@ def __init__(
def configure_mtls_channel(self, client_cert_callback=None):
"""Configure the client certificate and key for SSL connection.

The function does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is
explicitly set to `true`. In this case if client certificate and key are
successfully obtained (from the given client_cert_callback or from application
default SSL credentials), a :class:`_MutualTlsAdapter` instance will be mounted
to "https://" prefix.
The function does nothing unless client certificate use is enabled by
`GOOGLE_API_USE_CLIENT_CERTIFICATE` or inferred from certificate
configuration. In this case if client certificate and key are
successfully obtained (from the given client_cert_callback or from
application default SSL credentials), a :class:`_MutualTlsAdapter`
instance will be mounted to "https://" prefix.

Args:
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
Expand All @@ -452,6 +453,10 @@ def configure_mtls_channel(self, client_cert_callback=None):
try:
import OpenSSL
except ImportError as caught_exc:
if not _mtls_helper._is_client_cert_explicitly_enabled():
_LOGGER.debug("pyOpenSSL is unavailable; disabling inferred mTLS.")
self._is_mtls = False
return
new_exc = exceptions.MutualTLSChannelError(caught_exc)
raise new_exc from caught_exc

Expand Down
31 changes: 18 additions & 13 deletions packages/google-auth/google/auth/transport/urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ class AuthorizedHttp(RequestMethods): # type: ignore
credentials' headers to the request and refreshing credentials as needed.

This class also supports mutual TLS via :meth:`configure_mtls_channel`
method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
environment variable must be explicitly set to `true`, otherwise it does
nothing. Assume the environment is set to `true`, the method behaves in the
following manner:
method. Client certificate use is enabled when
`GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to `true` or when it is inferred
from a certificate configuration. When client certificate use is enabled, the
method behaves in the following manner:
If client_cert_callback is provided, client certificate and private
key are loaded using the callback; if client_cert_callback is None,
application default SSL credentials will be used. Exceptions are raised if
Expand Down Expand Up @@ -313,13 +313,14 @@ def __init__(

def configure_mtls_channel(self, client_cert_callback=None):
"""Configures mutual TLS channel using the given client_cert_callback or
application default SSL credentials. The behavior is controlled by
`GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable.
(1) If the environment variable value is `true`, the function returns True
if the channel is mutual TLS and False otherwise. The `http` provided
in the constructor will be overwritten.
(2) If the environment variable is not set or `false`, the function does
nothing and it always return False.
application default SSL credentials. Client certificate use is enabled
when `GOOGLE_API_USE_CLIENT_CERTIFICATE` is set to `true` or when it is
inferred from a certificate configuration.
(1) If client certificate use is enabled, the function returns True if
the channel is mutual TLS and False otherwise. The `http` provided in
the constructor will be overwritten.
(2) If client certificate use is disabled, the function does nothing and
it always returns False.

Args:
client_cert_callback (Optional[Callable[[], (bytes, bytes)]]):
Expand All @@ -335,7 +336,7 @@ def configure_mtls_channel(self, client_cert_callback=None):
google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
creation failed for any reason.
"""
use_client_cert = transport._mtls_helper.check_use_client_cert()
use_client_cert = _mtls_helper.check_use_client_cert()
if not use_client_cert:
self._is_mtls = False
return False
Expand All @@ -344,11 +345,15 @@ def configure_mtls_channel(self, client_cert_callback=None):
try:
import OpenSSL
except ImportError as caught_exc:
if not _mtls_helper._is_client_cert_explicitly_enabled():
_LOGGER.debug("pyOpenSSL is unavailable; disabling inferred mTLS.")
self._is_mtls = False
return False
new_exc = exceptions.MutualTLSChannelError(caught_exc)
raise new_exc from caught_exc

try:
found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key(
found_cert_key, cert, key = _mtls_helper.get_client_cert_and_key(
client_cert_callback
)

Expand Down
26 changes: 26 additions & 0 deletions packages/google-auth/tests/transport/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,32 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key):
):
auth_session.configure_mtls_channel()

@mock.patch("builtins.open", autospec=True)
@mock.patch(
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
)
def test_configure_mtls_channel_inferred_missing_openssl(
self, mock_get_client_cert_and_key, mock_file
):
mock_file.side_effect = mock.mock_open(
read_data='{"cert_configs": {"workload": "exists"}}'
)
auth_session = google.auth.transport.requests.AuthorizedSession(
credentials=mock.Mock()
)

with mock.patch.dict("sys.modules"):
sys.modules["OpenSSL"] = None
with mock.patch.dict(
os.environ,
{environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: "/path/to/config"},
clear=True,
):
auth_session.configure_mtls_channel()

assert not auth_session.is_mtls
mock_get_client_cert_and_key.assert_not_called()

@mock.patch(
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
)
Expand Down
27 changes: 27 additions & 0 deletions packages/google-auth/tests/transport/test_urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,33 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key):
):
authed_http.configure_mtls_channel()

@mock.patch("builtins.open", autospec=True)
@mock.patch(
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
)
def test_configure_mtls_channel_inferred_missing_openssl(
self, mock_get_client_cert_and_key, mock_file
):
mock_file.side_effect = mock.mock_open(
read_data='{"cert_configs": {"workload": "exists"}}'
)
authed_http = google.auth.transport.urllib3.AuthorizedHttp(
credentials=mock.Mock()
)

with mock.patch.dict("sys.modules"):
sys.modules["OpenSSL"] = None
with mock.patch.dict(
os.environ,
{environment_vars.GOOGLE_API_CERTIFICATE_CONFIG: "/path/to/config"},
clear=True,
):
is_mtls = authed_http.configure_mtls_channel()

assert not is_mtls
assert not authed_http._is_mtls
mock_get_client_cert_and_key.assert_not_called()

@mock.patch(
"google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True
)
Expand Down
Loading