diff --git a/google/auth/exceptions.py b/google/auth/exceptions.py index 4f66dc2a0..95013860a 100644 --- a/google/auth/exceptions.py +++ b/google/auth/exceptions.py @@ -34,3 +34,7 @@ class UserAccessTokenError(GoogleAuthError): class DefaultCredentialsError(GoogleAuthError): """Used to indicate that acquiring default credentials failed.""" + + +class MutualTLSChannelError(GoogleAuthError): + """Used to indicate that mutual TLS channel creation is failed.""" diff --git a/google/auth/transport/grpc.py b/google/auth/transport/grpc.py index 32ffabcae..d62c41502 100644 --- a/google/auth/transport/grpc.py +++ b/google/auth/transport/grpc.py @@ -20,6 +20,7 @@ import six +from google.auth import exceptions from google.auth.transport import _mtls_helper try: @@ -217,17 +218,8 @@ def my_client_cert_callback(): grpc.Channel: The created gRPC channel. Raises: - OSError: If the cert provider command launch fails during the application - default SSL credentials loading process on devices with endpoint - verification support. - RuntimeError: If the cert provider command has a runtime error during the - application default SSL credentials loading process on devices with - endpoint verification support. - ValueError: - If the context aware metadata file is malformed or if the cert provider - command doesn't produce both client certificate and key during the - application default SSL credentials loading process on devices with - endpoint verification support. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ # Create the metadata plugin for inserting the authorization header. metadata_plugin = AuthMetadataPlugin(credentials, request) @@ -293,20 +285,21 @@ def ssl_credentials(self): grpc.ChannelCredentials: The created grpc channel credentials. Raises: - OSError: If the cert provider command launch fails. - RuntimeError: If the cert provider command has a runtime error. - ValueError: - If the context aware metadata file is malformed or if the cert provider - command doesn't produce both the client certificate and key. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ if self._context_aware_metadata_path: - metadata = _mtls_helper._read_dca_metadata_file( - self._context_aware_metadata_path - ) - cert, key = _mtls_helper.get_client_ssl_credentials(metadata) - self._ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) + try: + metadata = _mtls_helper._read_dca_metadata_file( + self._context_aware_metadata_path + ) + cert, key = _mtls_helper.get_client_ssl_credentials(metadata) + self._ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + except (OSError, RuntimeError, ValueError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) else: self._ssl_credentials = grpc.ssl_channel_credentials() diff --git a/google/auth/transport/requests.py b/google/auth/transport/requests.py index 2d31d962e..26096e213 100644 --- a/google/auth/transport/requests.py +++ b/google/auth/transport/requests.py @@ -355,23 +355,32 @@ def configure_mtls_channel(self, client_cert_callback=None): will be used. Raises: - ImportError: If certifi or pyOpenSSL is not installed. - OpenSSL.crypto.Error: If client cert or key is invalid. - OSError: If the cert provider command launch fails during the - application default SSL credentials loading process. - RuntimeError: If the cert provider command has a runtime error during - the application default SSL credentials loading process. - ValueError: If the context aware metadata file is malformed or the - cert provider command doesn't produce both client certicate and - key during the application default SSL credentials loading process. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ - self._is_mtls, cert, key = google.auth.transport._mtls_helper.get_client_cert_and_key( - client_cert_callback - ) + try: + import OpenSSL + except ImportError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) - if self._is_mtls: - mtls_adapter = _MutualTlsAdapter(cert, key) - self.mount("https://", mtls_adapter) + try: + self._is_mtls, cert, key = google.auth.transport._mtls_helper.get_client_cert_and_key( + client_cert_callback + ) + + if self._is_mtls: + mtls_adapter = _MutualTlsAdapter(cert, key) + self.mount("https://", mtls_adapter) + except ( + ImportError, + OpenSSL.crypto.Error, + OSError, + RuntimeError, + ValueError, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) def request( self, diff --git a/google/auth/transport/urllib3.py b/google/auth/transport/urllib3.py index 3b2ba28bc..c359f3592 100644 --- a/google/auth/transport/urllib3.py +++ b/google/auth/transport/urllib3.py @@ -297,24 +297,33 @@ def configure_mtls_channel(self, client_cert_callabck=None): True if the channel is mutual TLS and False otherwise. Raises: - ImportError: If certifi or pyOpenSSL is not installed. - OpenSSL.crypto.Error: If client cert or key is invalid. - OSError: If the cert provider command launch fails during the - application default SSL credentials loading process. - RuntimeError: If the cert provider command has a runtime error during - the application default SSL credentials loading process. - ValueError: If the context aware metadata file is malformed or the - cert provider command doesn't produce both client certicate and - key during the application default SSL credentials loading process. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ - found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( - client_cert_callabck - ) + try: + import OpenSSL + except ImportError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) - if found_cert_key: - self.http = _make_mutual_tls_http(cert, key) - else: - self.http = _make_default_http() + try: + found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( + client_cert_callabck + ) + + if found_cert_key: + self.http = _make_mutual_tls_http(cert, key) + else: + self.http = _make_default_http() + except ( + ImportError, + OpenSSL.crypto.Error, + OSError, + RuntimeError, + ValueError, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) if self._has_user_provided_http: self._has_user_provided_http = False diff --git a/tests/transport/test_grpc.py b/tests/transport/test_grpc.py index 23e62a213..5c61f96e1 100644 --- a/tests/transport/test_grpc.py +++ b/tests/transport/test_grpc.py @@ -21,6 +21,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import exceptions from google.auth import transport try: @@ -315,7 +316,7 @@ def test_get_client_ssl_credentials_failure( # Mock that client cert and key are not loaded and exception is raised. mock_get_client_ssl_credentials.side_effect = ValueError() - with pytest.raises(ValueError): + with pytest.raises(exceptions.MutualTLSChannelError): assert google.auth.transport.grpc.SslCredentials().ssl_credentials def test_get_client_ssl_credentials_success( diff --git a/tests/transport/test_requests.py b/tests/transport/test_requests.py index 3f3e14c05..d6770de73 100644 --- a/tests/transport/test_requests.py +++ b/tests/transport/test_requests.py @@ -14,6 +14,7 @@ import datetime import functools +import sys import freezegun import mock @@ -23,6 +24,7 @@ import requests.adapters from six.moves import http_client +from google.auth import exceptions import google.auth.credentials import google.auth.transport._mtls_helper import google.auth.transport.requests @@ -414,3 +416,21 @@ def test_configure_mtls_channel_non_mtls( # Assert _MutualTlsAdapter constructor is not called. mock_adapter_ctor.assert_not_called() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): + mock_get_client_cert_and_key.side_effect = ValueError() + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + with pytest.raises(exceptions.MutualTLSChannelError): + auth_session.configure_mtls_channel() + + mock_get_client_cert_and_key.return_value = (False, None, None) + with mock.patch.dict("sys.modules"): + sys.modules["OpenSSL"] = None + with pytest.raises(exceptions.MutualTLSChannelError): + auth_session.configure_mtls_channel() diff --git a/tests/transport/test_urllib3.py b/tests/transport/test_urllib3.py index 0452e9187..a25fcd7d9 100644 --- a/tests/transport/test_urllib3.py +++ b/tests/transport/test_urllib3.py @@ -12,12 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + import mock import OpenSSL import pytest from six.moves import http_client import urllib3 +from google.auth import exceptions import google.auth.credentials import google.auth.transport._mtls_helper import google.auth.transport.urllib3 @@ -221,3 +224,21 @@ def test_configure_mtls_channel_non_mtls( assert not is_mtls mock_get_client_cert_and_key.assert_called_once() mock_make_mutual_tls_http.assert_not_called() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock() + ) + + mock_get_client_cert_and_key.side_effect = ValueError() + with pytest.raises(exceptions.MutualTLSChannelError): + authed_http.configure_mtls_channel() + + mock_get_client_cert_and_key.return_value = (False, None, None) + with mock.patch.dict("sys.modules"): + sys.modules["OpenSSL"] = None + with pytest.raises(exceptions.MutualTLSChannelError): + authed_http.configure_mtls_channel()