123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- # Copyright 2016 Google LLC
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import datetime
- import os
- import time
- import mock
- import pytest # type: ignore
- from google.auth import _helpers
- from google.auth import credentials
- from google.auth import environment_vars
- from google.auth import exceptions
- from google.auth import transport
- from google.oauth2 import service_account
- try:
- # pylint: disable=ungrouped-imports
- import grpc # type: ignore
- import google.auth.transport.grpc
- HAS_GRPC = True
- except ImportError: # pragma: NO COVER
- HAS_GRPC = False
- import yatest.common as yc
- DATA_DIR = os.path.join(os.path.dirname(yc.source_path(__file__)), "..", "data")
- METADATA_PATH = os.path.join(DATA_DIR, "context_aware_metadata.json")
- with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh:
- PRIVATE_KEY_BYTES = fh.read()
- with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh:
- PUBLIC_CERT_BYTES = fh.read()
- pytestmark = pytest.mark.skipif(not HAS_GRPC, reason="gRPC is unavailable.")
- class CredentialsStub(credentials.Credentials):
- def __init__(self, token="token"):
- super(CredentialsStub, self).__init__()
- self.token = token
- self.expiry = None
- def refresh(self, request):
- self.token += "1"
- def with_quota_project(self, quota_project_id):
- raise NotImplementedError()
- class TestAuthMetadataPlugin(object):
- def test_call_no_refresh(self):
- credentials = CredentialsStub()
- request = mock.create_autospec(transport.Request)
- plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
- context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
- context.method_name = mock.sentinel.method_name
- context.service_url = mock.sentinel.service_url
- callback = mock.create_autospec(grpc.AuthMetadataPluginCallback)
- plugin(context, callback)
- time.sleep(2)
- callback.assert_called_once_with(
- [("authorization", "Bearer {}".format(credentials.token))], None
- )
- def test_call_refresh(self):
- credentials = CredentialsStub()
- credentials.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
- request = mock.create_autospec(transport.Request)
- plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
- context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
- context.method_name = mock.sentinel.method_name
- context.service_url = mock.sentinel.service_url
- callback = mock.create_autospec(grpc.AuthMetadataPluginCallback)
- plugin(context, callback)
- time.sleep(2)
- assert credentials.token == "token1"
- callback.assert_called_once_with(
- [("authorization", "Bearer {}".format(credentials.token))], None
- )
- def test__get_authorization_headers_with_service_account(self):
- credentials = mock.create_autospec(service_account.Credentials)
- request = mock.create_autospec(transport.Request)
- plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
- context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
- context.method_name = "methodName"
- context.service_url = "https://pubsub.googleapis.com/methodName"
- plugin._get_authorization_headers(context)
- credentials._create_self_signed_jwt.assert_called_once_with(None)
- def test__get_authorization_headers_with_service_account_and_default_host(self):
- credentials = mock.create_autospec(service_account.Credentials)
- request = mock.create_autospec(transport.Request)
- default_host = "pubsub.googleapis.com"
- plugin = google.auth.transport.grpc.AuthMetadataPlugin(
- credentials, request, default_host=default_host
- )
- context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
- context.method_name = "methodName"
- context.service_url = "https://pubsub.googleapis.com/methodName"
- plugin._get_authorization_headers(context)
- credentials._create_self_signed_jwt.assert_called_once_with(
- "https://{}/".format(default_host)
- )
- @mock.patch(
- "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True
- )
- @mock.patch("grpc.composite_channel_credentials", autospec=True)
- @mock.patch("grpc.metadata_call_credentials", autospec=True)
- @mock.patch("grpc.ssl_channel_credentials", autospec=True)
- @mock.patch("grpc.secure_channel", autospec=True)
- class TestSecureAuthorizedChannel(object):
- @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True)
- @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
- def test_secure_authorized_channel_adc(
- self,
- check_config_path,
- load_json_file,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- credentials = CredentialsStub()
- request = mock.create_autospec(transport.Request)
- target = "example.com:80"
- # Mock the context aware metadata and client cert/key so mTLS SSL channel
- # will be used.
- check_config_path.return_value = METADATA_PATH
- load_json_file.return_value = {"cert_provider_command": ["some command"]}
- get_client_ssl_credentials.return_value = (
- True,
- PUBLIC_CERT_BYTES,
- PRIVATE_KEY_BYTES,
- None,
- )
- channel = None
- with mock.patch.dict(
- os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
- ):
- channel = google.auth.transport.grpc.secure_authorized_channel(
- credentials, request, target, options=mock.sentinel.options
- )
- # Check the auth plugin construction.
- auth_plugin = metadata_call_credentials.call_args[0][0]
- assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin)
- assert auth_plugin._credentials == credentials
- assert auth_plugin._request == request
- # Check the ssl channel call.
- ssl_channel_credentials.assert_called_once_with(
- certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
- )
- # Check the composite credentials call.
- composite_channel_credentials.assert_called_once_with(
- ssl_channel_credentials.return_value, metadata_call_credentials.return_value
- )
- # Check the channel call.
- secure_channel.assert_called_once_with(
- target,
- composite_channel_credentials.return_value,
- options=mock.sentinel.options,
- )
- assert channel == secure_channel.return_value
- @mock.patch("google.auth.transport.grpc.SslCredentials", autospec=True)
- def test_secure_authorized_channel_adc_without_client_cert_env(
- self,
- ssl_credentials_adc_method,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE
- # environment variable is not set.
- credentials = CredentialsStub()
- request = mock.create_autospec(transport.Request)
- target = "example.com:80"
- channel = google.auth.transport.grpc.secure_authorized_channel(
- credentials, request, target, options=mock.sentinel.options
- )
- # Check the auth plugin construction.
- auth_plugin = metadata_call_credentials.call_args[0][0]
- assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin)
- assert auth_plugin._credentials == credentials
- assert auth_plugin._request == request
- # Check the ssl channel call.
- ssl_channel_credentials.assert_called_once()
- ssl_credentials_adc_method.assert_not_called()
- # Check the composite credentials call.
- composite_channel_credentials.assert_called_once_with(
- ssl_channel_credentials.return_value, metadata_call_credentials.return_value
- )
- # Check the channel call.
- secure_channel.assert_called_once_with(
- target,
- composite_channel_credentials.return_value,
- options=mock.sentinel.options,
- )
- assert channel == secure_channel.return_value
- def test_secure_authorized_channel_explicit_ssl(
- self,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- credentials = mock.Mock()
- request = mock.Mock()
- target = "example.com:80"
- ssl_credentials = mock.Mock()
- google.auth.transport.grpc.secure_authorized_channel(
- credentials, request, target, ssl_credentials=ssl_credentials
- )
- # Since explicit SSL credentials are provided, get_client_ssl_credentials
- # shouldn't be called.
- assert not get_client_ssl_credentials.called
- # Check the ssl channel call.
- assert not ssl_channel_credentials.called
- # Check the composite credentials call.
- composite_channel_credentials.assert_called_once_with(
- ssl_credentials, metadata_call_credentials.return_value
- )
- def test_secure_authorized_channel_mutual_exclusive(
- self,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- credentials = mock.Mock()
- request = mock.Mock()
- target = "example.com:80"
- ssl_credentials = mock.Mock()
- client_cert_callback = mock.Mock()
- with pytest.raises(ValueError):
- google.auth.transport.grpc.secure_authorized_channel(
- credentials,
- request,
- target,
- ssl_credentials=ssl_credentials,
- client_cert_callback=client_cert_callback,
- )
- def test_secure_authorized_channel_with_client_cert_callback_success(
- self,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- credentials = mock.Mock()
- request = mock.Mock()
- target = "example.com:80"
- client_cert_callback = mock.Mock()
- client_cert_callback.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES)
- with mock.patch.dict(
- os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
- ):
- google.auth.transport.grpc.secure_authorized_channel(
- credentials, request, target, client_cert_callback=client_cert_callback
- )
- client_cert_callback.assert_called_once()
- # Check we are using the cert and key provided by client_cert_callback.
- ssl_channel_credentials.assert_called_once_with(
- certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
- )
- # Check the composite credentials call.
- composite_channel_credentials.assert_called_once_with(
- ssl_channel_credentials.return_value, metadata_call_credentials.return_value
- )
- @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True)
- @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
- def test_secure_authorized_channel_with_client_cert_callback_failure(
- self,
- check_config_path,
- load_json_file,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- credentials = mock.Mock()
- request = mock.Mock()
- target = "example.com:80"
- client_cert_callback = mock.Mock()
- client_cert_callback.side_effect = Exception("callback exception")
- with pytest.raises(Exception) as excinfo:
- with mock.patch.dict(
- os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
- ):
- google.auth.transport.grpc.secure_authorized_channel(
- credentials,
- request,
- target,
- client_cert_callback=client_cert_callback,
- )
- assert str(excinfo.value) == "callback exception"
- def test_secure_authorized_channel_cert_callback_without_client_cert_env(
- self,
- secure_channel,
- ssl_channel_credentials,
- metadata_call_credentials,
- composite_channel_credentials,
- get_client_ssl_credentials,
- ):
- # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE
- # environment variable is not set.
- credentials = mock.Mock()
- request = mock.Mock()
- target = "example.com:80"
- client_cert_callback = mock.Mock()
- google.auth.transport.grpc.secure_authorized_channel(
- credentials, request, target, client_cert_callback=client_cert_callback
- )
- # Check client_cert_callback is not called because GOOGLE_API_USE_CLIENT_CERTIFICATE
- # is not set.
- client_cert_callback.assert_not_called()
- ssl_channel_credentials.assert_called_once()
- # Check the composite credentials call.
- composite_channel_credentials.assert_called_once_with(
- ssl_channel_credentials.return_value, metadata_call_credentials.return_value
- )
- @mock.patch("grpc.ssl_channel_credentials", autospec=True)
- @mock.patch(
- "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True
- )
- @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True)
- @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
- class TestSslCredentials(object):
- def test_no_context_aware_metadata(
- self,
- mock_check_config_path,
- mock_load_json_file,
- mock_get_client_ssl_credentials,
- mock_ssl_channel_credentials,
- ):
- # Mock that the metadata file doesn't exist.
- mock_check_config_path.return_value = None
- with mock.patch.dict(
- os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
- ):
- ssl_credentials = google.auth.transport.grpc.SslCredentials()
- # Since no context aware metadata is found, we wouldn't call
- # get_client_ssl_credentials, and the SSL channel credentials created is
- # non mTLS.
- assert ssl_credentials.ssl_credentials is not None
- assert not ssl_credentials.is_mtls
- mock_get_client_ssl_credentials.assert_not_called()
- mock_ssl_channel_credentials.assert_called_once_with()
- def test_get_client_ssl_credentials_failure(
- self,
- mock_check_config_path,
- mock_load_json_file,
- mock_get_client_ssl_credentials,
- mock_ssl_channel_credentials,
- ):
- mock_check_config_path.return_value = METADATA_PATH
- mock_load_json_file.return_value = {"cert_provider_command": ["some command"]}
- # Mock that client cert and key are not loaded and exception is raised.
- mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError()
- with pytest.raises(exceptions.MutualTLSChannelError):
- with mock.patch.dict(
- os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
- ):
- assert google.auth.transport.grpc.SslCredentials().ssl_credentials
- def test_get_client_ssl_credentials_success(
- self,
- mock_check_config_path,
- mock_load_json_file,
- mock_get_client_ssl_credentials,
- mock_ssl_channel_credentials,
- ):
- mock_check_config_path.return_value = METADATA_PATH
- mock_load_json_file.return_value = {"cert_provider_command": ["some command"]}
- mock_get_client_ssl_credentials.return_value = (
- True,
- PUBLIC_CERT_BYTES,
- PRIVATE_KEY_BYTES,
- None,
- )
- with mock.patch.dict(
- os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
- ):
- ssl_credentials = google.auth.transport.grpc.SslCredentials()
- assert ssl_credentials.ssl_credentials is not None
- assert ssl_credentials.is_mtls
- mock_get_client_ssl_credentials.assert_called_once()
- mock_ssl_channel_credentials.assert_called_once_with(
- certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
- )
- def test_get_client_ssl_credentials_without_client_cert_env(
- self,
- mock_check_config_path,
- mock_load_json_file,
- mock_get_client_ssl_credentials,
- mock_ssl_channel_credentials,
- ):
- # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set.
- ssl_credentials = google.auth.transport.grpc.SslCredentials()
- assert ssl_credentials.ssl_credentials is not None
- assert not ssl_credentials.is_mtls
- mock_check_config_path.assert_not_called()
- mock_load_json_file.assert_not_called()
- mock_get_client_ssl_credentials.assert_not_called()
- mock_ssl_channel_credentials.assert_called_once()
|