test_grpc.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. # Copyright 2016 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import datetime
  15. import os
  16. import time
  17. import mock
  18. import pytest # type: ignore
  19. from google.auth import _helpers
  20. from google.auth import credentials
  21. from google.auth import environment_vars
  22. from google.auth import exceptions
  23. from google.auth import transport
  24. from google.oauth2 import service_account
  25. try:
  26. # pylint: disable=ungrouped-imports
  27. import grpc # type: ignore
  28. import google.auth.transport.grpc
  29. HAS_GRPC = True
  30. except ImportError: # pragma: NO COVER
  31. HAS_GRPC = False
  32. import yatest.common as yc
  33. DATA_DIR = os.path.join(os.path.dirname(yc.source_path(__file__)), "..", "data")
  34. METADATA_PATH = os.path.join(DATA_DIR, "context_aware_metadata.json")
  35. with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh:
  36. PRIVATE_KEY_BYTES = fh.read()
  37. with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh:
  38. PUBLIC_CERT_BYTES = fh.read()
  39. pytestmark = pytest.mark.skipif(not HAS_GRPC, reason="gRPC is unavailable.")
  40. class CredentialsStub(credentials.Credentials):
  41. def __init__(self, token="token"):
  42. super(CredentialsStub, self).__init__()
  43. self.token = token
  44. self.expiry = None
  45. def refresh(self, request):
  46. self.token += "1"
  47. def with_quota_project(self, quota_project_id):
  48. raise NotImplementedError()
  49. class TestAuthMetadataPlugin(object):
  50. def test_call_no_refresh(self):
  51. credentials = CredentialsStub()
  52. request = mock.create_autospec(transport.Request)
  53. plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
  54. context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
  55. context.method_name = mock.sentinel.method_name
  56. context.service_url = mock.sentinel.service_url
  57. callback = mock.create_autospec(grpc.AuthMetadataPluginCallback)
  58. plugin(context, callback)
  59. time.sleep(2)
  60. callback.assert_called_once_with(
  61. [("authorization", "Bearer {}".format(credentials.token))], None
  62. )
  63. def test_call_refresh(self):
  64. credentials = CredentialsStub()
  65. credentials.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
  66. request = mock.create_autospec(transport.Request)
  67. plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
  68. context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
  69. context.method_name = mock.sentinel.method_name
  70. context.service_url = mock.sentinel.service_url
  71. callback = mock.create_autospec(grpc.AuthMetadataPluginCallback)
  72. plugin(context, callback)
  73. time.sleep(2)
  74. assert credentials.token == "token1"
  75. callback.assert_called_once_with(
  76. [("authorization", "Bearer {}".format(credentials.token))], None
  77. )
  78. def test__get_authorization_headers_with_service_account(self):
  79. credentials = mock.create_autospec(service_account.Credentials)
  80. request = mock.create_autospec(transport.Request)
  81. plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request)
  82. context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
  83. context.method_name = "methodName"
  84. context.service_url = "https://pubsub.googleapis.com/methodName"
  85. plugin._get_authorization_headers(context)
  86. credentials._create_self_signed_jwt.assert_called_once_with(None)
  87. def test__get_authorization_headers_with_service_account_and_default_host(self):
  88. credentials = mock.create_autospec(service_account.Credentials)
  89. request = mock.create_autospec(transport.Request)
  90. default_host = "pubsub.googleapis.com"
  91. plugin = google.auth.transport.grpc.AuthMetadataPlugin(
  92. credentials, request, default_host=default_host
  93. )
  94. context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
  95. context.method_name = "methodName"
  96. context.service_url = "https://pubsub.googleapis.com/methodName"
  97. plugin._get_authorization_headers(context)
  98. credentials._create_self_signed_jwt.assert_called_once_with(
  99. "https://{}/".format(default_host)
  100. )
  101. @mock.patch(
  102. "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True
  103. )
  104. @mock.patch("grpc.composite_channel_credentials", autospec=True)
  105. @mock.patch("grpc.metadata_call_credentials", autospec=True)
  106. @mock.patch("grpc.ssl_channel_credentials", autospec=True)
  107. @mock.patch("grpc.secure_channel", autospec=True)
  108. class TestSecureAuthorizedChannel(object):
  109. @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True)
  110. @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
  111. def test_secure_authorized_channel_adc(
  112. self,
  113. check_config_path,
  114. load_json_file,
  115. secure_channel,
  116. ssl_channel_credentials,
  117. metadata_call_credentials,
  118. composite_channel_credentials,
  119. get_client_ssl_credentials,
  120. ):
  121. credentials = CredentialsStub()
  122. request = mock.create_autospec(transport.Request)
  123. target = "example.com:80"
  124. # Mock the context aware metadata and client cert/key so mTLS SSL channel
  125. # will be used.
  126. check_config_path.return_value = METADATA_PATH
  127. load_json_file.return_value = {"cert_provider_command": ["some command"]}
  128. get_client_ssl_credentials.return_value = (
  129. True,
  130. PUBLIC_CERT_BYTES,
  131. PRIVATE_KEY_BYTES,
  132. None,
  133. )
  134. channel = None
  135. with mock.patch.dict(
  136. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  137. ):
  138. channel = google.auth.transport.grpc.secure_authorized_channel(
  139. credentials, request, target, options=mock.sentinel.options
  140. )
  141. # Check the auth plugin construction.
  142. auth_plugin = metadata_call_credentials.call_args[0][0]
  143. assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin)
  144. assert auth_plugin._credentials == credentials
  145. assert auth_plugin._request == request
  146. # Check the ssl channel call.
  147. ssl_channel_credentials.assert_called_once_with(
  148. certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
  149. )
  150. # Check the composite credentials call.
  151. composite_channel_credentials.assert_called_once_with(
  152. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  153. )
  154. # Check the channel call.
  155. secure_channel.assert_called_once_with(
  156. target,
  157. composite_channel_credentials.return_value,
  158. options=mock.sentinel.options,
  159. )
  160. assert channel == secure_channel.return_value
  161. @mock.patch("google.auth.transport.grpc.SslCredentials", autospec=True)
  162. def test_secure_authorized_channel_adc_without_client_cert_env(
  163. self,
  164. ssl_credentials_adc_method,
  165. secure_channel,
  166. ssl_channel_credentials,
  167. metadata_call_credentials,
  168. composite_channel_credentials,
  169. get_client_ssl_credentials,
  170. ):
  171. # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE
  172. # environment variable is not set.
  173. credentials = CredentialsStub()
  174. request = mock.create_autospec(transport.Request)
  175. target = "example.com:80"
  176. channel = google.auth.transport.grpc.secure_authorized_channel(
  177. credentials, request, target, options=mock.sentinel.options
  178. )
  179. # Check the auth plugin construction.
  180. auth_plugin = metadata_call_credentials.call_args[0][0]
  181. assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin)
  182. assert auth_plugin._credentials == credentials
  183. assert auth_plugin._request == request
  184. # Check the ssl channel call.
  185. ssl_channel_credentials.assert_called_once()
  186. ssl_credentials_adc_method.assert_not_called()
  187. # Check the composite credentials call.
  188. composite_channel_credentials.assert_called_once_with(
  189. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  190. )
  191. # Check the channel call.
  192. secure_channel.assert_called_once_with(
  193. target,
  194. composite_channel_credentials.return_value,
  195. options=mock.sentinel.options,
  196. )
  197. assert channel == secure_channel.return_value
  198. def test_secure_authorized_channel_explicit_ssl(
  199. self,
  200. secure_channel,
  201. ssl_channel_credentials,
  202. metadata_call_credentials,
  203. composite_channel_credentials,
  204. get_client_ssl_credentials,
  205. ):
  206. credentials = mock.Mock()
  207. request = mock.Mock()
  208. target = "example.com:80"
  209. ssl_credentials = mock.Mock()
  210. google.auth.transport.grpc.secure_authorized_channel(
  211. credentials, request, target, ssl_credentials=ssl_credentials
  212. )
  213. # Since explicit SSL credentials are provided, get_client_ssl_credentials
  214. # shouldn't be called.
  215. assert not get_client_ssl_credentials.called
  216. # Check the ssl channel call.
  217. assert not ssl_channel_credentials.called
  218. # Check the composite credentials call.
  219. composite_channel_credentials.assert_called_once_with(
  220. ssl_credentials, metadata_call_credentials.return_value
  221. )
  222. def test_secure_authorized_channel_mutual_exclusive(
  223. self,
  224. secure_channel,
  225. ssl_channel_credentials,
  226. metadata_call_credentials,
  227. composite_channel_credentials,
  228. get_client_ssl_credentials,
  229. ):
  230. credentials = mock.Mock()
  231. request = mock.Mock()
  232. target = "example.com:80"
  233. ssl_credentials = mock.Mock()
  234. client_cert_callback = mock.Mock()
  235. with pytest.raises(ValueError):
  236. google.auth.transport.grpc.secure_authorized_channel(
  237. credentials,
  238. request,
  239. target,
  240. ssl_credentials=ssl_credentials,
  241. client_cert_callback=client_cert_callback,
  242. )
  243. def test_secure_authorized_channel_with_client_cert_callback_success(
  244. self,
  245. secure_channel,
  246. ssl_channel_credentials,
  247. metadata_call_credentials,
  248. composite_channel_credentials,
  249. get_client_ssl_credentials,
  250. ):
  251. credentials = mock.Mock()
  252. request = mock.Mock()
  253. target = "example.com:80"
  254. client_cert_callback = mock.Mock()
  255. client_cert_callback.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES)
  256. with mock.patch.dict(
  257. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  258. ):
  259. google.auth.transport.grpc.secure_authorized_channel(
  260. credentials, request, target, client_cert_callback=client_cert_callback
  261. )
  262. client_cert_callback.assert_called_once()
  263. # Check we are using the cert and key provided by client_cert_callback.
  264. ssl_channel_credentials.assert_called_once_with(
  265. certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
  266. )
  267. # Check the composite credentials call.
  268. composite_channel_credentials.assert_called_once_with(
  269. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  270. )
  271. @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True)
  272. @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
  273. def test_secure_authorized_channel_with_client_cert_callback_failure(
  274. self,
  275. check_config_path,
  276. load_json_file,
  277. secure_channel,
  278. ssl_channel_credentials,
  279. metadata_call_credentials,
  280. composite_channel_credentials,
  281. get_client_ssl_credentials,
  282. ):
  283. credentials = mock.Mock()
  284. request = mock.Mock()
  285. target = "example.com:80"
  286. client_cert_callback = mock.Mock()
  287. client_cert_callback.side_effect = Exception("callback exception")
  288. with pytest.raises(Exception) as excinfo:
  289. with mock.patch.dict(
  290. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  291. ):
  292. google.auth.transport.grpc.secure_authorized_channel(
  293. credentials,
  294. request,
  295. target,
  296. client_cert_callback=client_cert_callback,
  297. )
  298. assert str(excinfo.value) == "callback exception"
  299. def test_secure_authorized_channel_cert_callback_without_client_cert_env(
  300. self,
  301. secure_channel,
  302. ssl_channel_credentials,
  303. metadata_call_credentials,
  304. composite_channel_credentials,
  305. get_client_ssl_credentials,
  306. ):
  307. # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE
  308. # environment variable is not set.
  309. credentials = mock.Mock()
  310. request = mock.Mock()
  311. target = "example.com:80"
  312. client_cert_callback = mock.Mock()
  313. google.auth.transport.grpc.secure_authorized_channel(
  314. credentials, request, target, client_cert_callback=client_cert_callback
  315. )
  316. # Check client_cert_callback is not called because GOOGLE_API_USE_CLIENT_CERTIFICATE
  317. # is not set.
  318. client_cert_callback.assert_not_called()
  319. ssl_channel_credentials.assert_called_once()
  320. # Check the composite credentials call.
  321. composite_channel_credentials.assert_called_once_with(
  322. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  323. )
  324. @mock.patch("grpc.ssl_channel_credentials", autospec=True)
  325. @mock.patch(
  326. "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True
  327. )
  328. @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True)
  329. @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True)
  330. class TestSslCredentials(object):
  331. def test_no_context_aware_metadata(
  332. self,
  333. mock_check_config_path,
  334. mock_load_json_file,
  335. mock_get_client_ssl_credentials,
  336. mock_ssl_channel_credentials,
  337. ):
  338. # Mock that the metadata file doesn't exist.
  339. mock_check_config_path.return_value = None
  340. with mock.patch.dict(
  341. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  342. ):
  343. ssl_credentials = google.auth.transport.grpc.SslCredentials()
  344. # Since no context aware metadata is found, we wouldn't call
  345. # get_client_ssl_credentials, and the SSL channel credentials created is
  346. # non mTLS.
  347. assert ssl_credentials.ssl_credentials is not None
  348. assert not ssl_credentials.is_mtls
  349. mock_get_client_ssl_credentials.assert_not_called()
  350. mock_ssl_channel_credentials.assert_called_once_with()
  351. def test_get_client_ssl_credentials_failure(
  352. self,
  353. mock_check_config_path,
  354. mock_load_json_file,
  355. mock_get_client_ssl_credentials,
  356. mock_ssl_channel_credentials,
  357. ):
  358. mock_check_config_path.return_value = METADATA_PATH
  359. mock_load_json_file.return_value = {"cert_provider_command": ["some command"]}
  360. # Mock that client cert and key are not loaded and exception is raised.
  361. mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError()
  362. with pytest.raises(exceptions.MutualTLSChannelError):
  363. with mock.patch.dict(
  364. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  365. ):
  366. assert google.auth.transport.grpc.SslCredentials().ssl_credentials
  367. def test_get_client_ssl_credentials_success(
  368. self,
  369. mock_check_config_path,
  370. mock_load_json_file,
  371. mock_get_client_ssl_credentials,
  372. mock_ssl_channel_credentials,
  373. ):
  374. mock_check_config_path.return_value = METADATA_PATH
  375. mock_load_json_file.return_value = {"cert_provider_command": ["some command"]}
  376. mock_get_client_ssl_credentials.return_value = (
  377. True,
  378. PUBLIC_CERT_BYTES,
  379. PRIVATE_KEY_BYTES,
  380. None,
  381. )
  382. with mock.patch.dict(
  383. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  384. ):
  385. ssl_credentials = google.auth.transport.grpc.SslCredentials()
  386. assert ssl_credentials.ssl_credentials is not None
  387. assert ssl_credentials.is_mtls
  388. mock_get_client_ssl_credentials.assert_called_once()
  389. mock_ssl_channel_credentials.assert_called_once_with(
  390. certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
  391. )
  392. def test_get_client_ssl_credentials_without_client_cert_env(
  393. self,
  394. mock_check_config_path,
  395. mock_load_json_file,
  396. mock_get_client_ssl_credentials,
  397. mock_ssl_channel_credentials,
  398. ):
  399. # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set.
  400. ssl_credentials = google.auth.transport.grpc.SslCredentials()
  401. assert ssl_credentials.ssl_credentials is not None
  402. assert not ssl_credentials.is_mtls
  403. mock_check_config_path.assert_not_called()
  404. mock_load_json_file.assert_not_called()
  405. mock_get_client_ssl_credentials.assert_not_called()
  406. mock_ssl_channel_credentials.assert_called_once()