test_grpc.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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
  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
  28. import google.auth.transport.grpc
  29. HAS_GRPC = True
  30. except ImportError: # pragma: NO COVER
  31. HAS_GRPC = False
  32. import yatest.common
  33. DATA_DIR = os.path.join(yatest.common.test_source_path(), "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.CLOCK_SKEW
  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. # self-signed JWT should not be created when default_host is not set
  87. #credentials._create_self_signed_jwt.assert_not_called()
  88. def test__get_authorization_headers_with_service_account_and_default_host(self):
  89. credentials = mock.create_autospec(service_account.Credentials)
  90. request = mock.create_autospec(transport.Request)
  91. default_host = "pubsub.googleapis.com"
  92. plugin = google.auth.transport.grpc.AuthMetadataPlugin(
  93. credentials, request, default_host=default_host
  94. )
  95. context = mock.create_autospec(grpc.AuthMetadataContext, instance=True)
  96. context.method_name = "methodName"
  97. context.service_url = "https://pubsub.googleapis.com/methodName"
  98. plugin._get_authorization_headers(context)
  99. credentials._create_self_signed_jwt.assert_called_once_with(
  100. "https://{}/".format(default_host)
  101. )
  102. @mock.patch(
  103. "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True
  104. )
  105. @mock.patch("grpc.composite_channel_credentials", autospec=True)
  106. @mock.patch("grpc.metadata_call_credentials", autospec=True)
  107. @mock.patch("grpc.ssl_channel_credentials", autospec=True)
  108. @mock.patch("grpc.secure_channel", autospec=True)
  109. class TestSecureAuthorizedChannel(object):
  110. @mock.patch(
  111. "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True
  112. )
  113. @mock.patch(
  114. "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True
  115. )
  116. def test_secure_authorized_channel_adc(
  117. self,
  118. check_dca_metadata_path,
  119. read_dca_metadata_file,
  120. secure_channel,
  121. ssl_channel_credentials,
  122. metadata_call_credentials,
  123. composite_channel_credentials,
  124. get_client_ssl_credentials,
  125. ):
  126. credentials = CredentialsStub()
  127. request = mock.create_autospec(transport.Request)
  128. target = "example.com:80"
  129. # Mock the context aware metadata and client cert/key so mTLS SSL channel
  130. # will be used.
  131. check_dca_metadata_path.return_value = METADATA_PATH
  132. read_dca_metadata_file.return_value = {
  133. "cert_provider_command": ["some command"]
  134. }
  135. get_client_ssl_credentials.return_value = (
  136. True,
  137. PUBLIC_CERT_BYTES,
  138. PRIVATE_KEY_BYTES,
  139. None,
  140. )
  141. channel = None
  142. with mock.patch.dict(
  143. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  144. ):
  145. channel = google.auth.transport.grpc.secure_authorized_channel(
  146. credentials, request, target, options=mock.sentinel.options
  147. )
  148. # Check the auth plugin construction.
  149. auth_plugin = metadata_call_credentials.call_args[0][0]
  150. assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin)
  151. assert auth_plugin._credentials == credentials
  152. assert auth_plugin._request == request
  153. # Check the ssl channel call.
  154. ssl_channel_credentials.assert_called_once_with(
  155. certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
  156. )
  157. # Check the composite credentials call.
  158. composite_channel_credentials.assert_called_once_with(
  159. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  160. )
  161. # Check the channel call.
  162. secure_channel.assert_called_once_with(
  163. target,
  164. composite_channel_credentials.return_value,
  165. options=mock.sentinel.options,
  166. )
  167. assert channel == secure_channel.return_value
  168. @mock.patch("google.auth.transport.grpc.SslCredentials", autospec=True)
  169. def test_secure_authorized_channel_adc_without_client_cert_env(
  170. self,
  171. ssl_credentials_adc_method,
  172. secure_channel,
  173. ssl_channel_credentials,
  174. metadata_call_credentials,
  175. composite_channel_credentials,
  176. get_client_ssl_credentials,
  177. ):
  178. # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE
  179. # environment variable is not set.
  180. credentials = CredentialsStub()
  181. request = mock.create_autospec(transport.Request)
  182. target = "example.com:80"
  183. channel = google.auth.transport.grpc.secure_authorized_channel(
  184. credentials, request, target, options=mock.sentinel.options
  185. )
  186. # Check the auth plugin construction.
  187. auth_plugin = metadata_call_credentials.call_args[0][0]
  188. assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin)
  189. assert auth_plugin._credentials == credentials
  190. assert auth_plugin._request == request
  191. # Check the ssl channel call.
  192. ssl_channel_credentials.assert_called_once()
  193. ssl_credentials_adc_method.assert_not_called()
  194. # Check the composite credentials call.
  195. composite_channel_credentials.assert_called_once_with(
  196. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  197. )
  198. # Check the channel call.
  199. secure_channel.assert_called_once_with(
  200. target,
  201. composite_channel_credentials.return_value,
  202. options=mock.sentinel.options,
  203. )
  204. assert channel == secure_channel.return_value
  205. def test_secure_authorized_channel_explicit_ssl(
  206. self,
  207. secure_channel,
  208. ssl_channel_credentials,
  209. metadata_call_credentials,
  210. composite_channel_credentials,
  211. get_client_ssl_credentials,
  212. ):
  213. credentials = mock.Mock()
  214. request = mock.Mock()
  215. target = "example.com:80"
  216. ssl_credentials = mock.Mock()
  217. google.auth.transport.grpc.secure_authorized_channel(
  218. credentials, request, target, ssl_credentials=ssl_credentials
  219. )
  220. # Since explicit SSL credentials are provided, get_client_ssl_credentials
  221. # shouldn't be called.
  222. assert not get_client_ssl_credentials.called
  223. # Check the ssl channel call.
  224. assert not ssl_channel_credentials.called
  225. # Check the composite credentials call.
  226. composite_channel_credentials.assert_called_once_with(
  227. ssl_credentials, metadata_call_credentials.return_value
  228. )
  229. def test_secure_authorized_channel_mutual_exclusive(
  230. self,
  231. secure_channel,
  232. ssl_channel_credentials,
  233. metadata_call_credentials,
  234. composite_channel_credentials,
  235. get_client_ssl_credentials,
  236. ):
  237. credentials = mock.Mock()
  238. request = mock.Mock()
  239. target = "example.com:80"
  240. ssl_credentials = mock.Mock()
  241. client_cert_callback = mock.Mock()
  242. with pytest.raises(ValueError):
  243. google.auth.transport.grpc.secure_authorized_channel(
  244. credentials,
  245. request,
  246. target,
  247. ssl_credentials=ssl_credentials,
  248. client_cert_callback=client_cert_callback,
  249. )
  250. def test_secure_authorized_channel_with_client_cert_callback_success(
  251. self,
  252. secure_channel,
  253. ssl_channel_credentials,
  254. metadata_call_credentials,
  255. composite_channel_credentials,
  256. get_client_ssl_credentials,
  257. ):
  258. credentials = mock.Mock()
  259. request = mock.Mock()
  260. target = "example.com:80"
  261. client_cert_callback = mock.Mock()
  262. client_cert_callback.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES)
  263. with mock.patch.dict(
  264. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  265. ):
  266. google.auth.transport.grpc.secure_authorized_channel(
  267. credentials, request, target, client_cert_callback=client_cert_callback
  268. )
  269. client_cert_callback.assert_called_once()
  270. # Check we are using the cert and key provided by client_cert_callback.
  271. ssl_channel_credentials.assert_called_once_with(
  272. certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
  273. )
  274. # Check the composite credentials call.
  275. composite_channel_credentials.assert_called_once_with(
  276. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  277. )
  278. @mock.patch(
  279. "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True
  280. )
  281. @mock.patch(
  282. "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True
  283. )
  284. def test_secure_authorized_channel_with_client_cert_callback_failure(
  285. self,
  286. check_dca_metadata_path,
  287. read_dca_metadata_file,
  288. secure_channel,
  289. ssl_channel_credentials,
  290. metadata_call_credentials,
  291. composite_channel_credentials,
  292. get_client_ssl_credentials,
  293. ):
  294. credentials = mock.Mock()
  295. request = mock.Mock()
  296. target = "example.com:80"
  297. client_cert_callback = mock.Mock()
  298. client_cert_callback.side_effect = Exception("callback exception")
  299. with pytest.raises(Exception) as excinfo:
  300. with mock.patch.dict(
  301. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  302. ):
  303. google.auth.transport.grpc.secure_authorized_channel(
  304. credentials,
  305. request,
  306. target,
  307. client_cert_callback=client_cert_callback,
  308. )
  309. assert str(excinfo.value) == "callback exception"
  310. def test_secure_authorized_channel_cert_callback_without_client_cert_env(
  311. self,
  312. secure_channel,
  313. ssl_channel_credentials,
  314. metadata_call_credentials,
  315. composite_channel_credentials,
  316. get_client_ssl_credentials,
  317. ):
  318. # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE
  319. # environment variable is not set.
  320. credentials = mock.Mock()
  321. request = mock.Mock()
  322. target = "example.com:80"
  323. client_cert_callback = mock.Mock()
  324. google.auth.transport.grpc.secure_authorized_channel(
  325. credentials, request, target, client_cert_callback=client_cert_callback
  326. )
  327. # Check client_cert_callback is not called because GOOGLE_API_USE_CLIENT_CERTIFICATE
  328. # is not set.
  329. client_cert_callback.assert_not_called()
  330. ssl_channel_credentials.assert_called_once()
  331. # Check the composite credentials call.
  332. composite_channel_credentials.assert_called_once_with(
  333. ssl_channel_credentials.return_value, metadata_call_credentials.return_value
  334. )
  335. @mock.patch("grpc.ssl_channel_credentials", autospec=True)
  336. @mock.patch(
  337. "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True
  338. )
  339. @mock.patch("google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True)
  340. @mock.patch(
  341. "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True
  342. )
  343. class TestSslCredentials(object):
  344. def test_no_context_aware_metadata(
  345. self,
  346. mock_check_dca_metadata_path,
  347. mock_read_dca_metadata_file,
  348. mock_get_client_ssl_credentials,
  349. mock_ssl_channel_credentials,
  350. ):
  351. # Mock that the metadata file doesn't exist.
  352. mock_check_dca_metadata_path.return_value = None
  353. with mock.patch.dict(
  354. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  355. ):
  356. ssl_credentials = google.auth.transport.grpc.SslCredentials()
  357. # Since no context aware metadata is found, we wouldn't call
  358. # get_client_ssl_credentials, and the SSL channel credentials created is
  359. # non mTLS.
  360. assert ssl_credentials.ssl_credentials is not None
  361. assert not ssl_credentials.is_mtls
  362. mock_get_client_ssl_credentials.assert_not_called()
  363. mock_ssl_channel_credentials.assert_called_once_with()
  364. def test_get_client_ssl_credentials_failure(
  365. self,
  366. mock_check_dca_metadata_path,
  367. mock_read_dca_metadata_file,
  368. mock_get_client_ssl_credentials,
  369. mock_ssl_channel_credentials,
  370. ):
  371. mock_check_dca_metadata_path.return_value = METADATA_PATH
  372. mock_read_dca_metadata_file.return_value = {
  373. "cert_provider_command": ["some command"]
  374. }
  375. # Mock that client cert and key are not loaded and exception is raised.
  376. mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError()
  377. with pytest.raises(exceptions.MutualTLSChannelError):
  378. with mock.patch.dict(
  379. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  380. ):
  381. assert google.auth.transport.grpc.SslCredentials().ssl_credentials
  382. def test_get_client_ssl_credentials_success(
  383. self,
  384. mock_check_dca_metadata_path,
  385. mock_read_dca_metadata_file,
  386. mock_get_client_ssl_credentials,
  387. mock_ssl_channel_credentials,
  388. ):
  389. mock_check_dca_metadata_path.return_value = METADATA_PATH
  390. mock_read_dca_metadata_file.return_value = {
  391. "cert_provider_command": ["some command"]
  392. }
  393. mock_get_client_ssl_credentials.return_value = (
  394. True,
  395. PUBLIC_CERT_BYTES,
  396. PRIVATE_KEY_BYTES,
  397. None,
  398. )
  399. with mock.patch.dict(
  400. os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}
  401. ):
  402. ssl_credentials = google.auth.transport.grpc.SslCredentials()
  403. assert ssl_credentials.ssl_credentials is not None
  404. assert ssl_credentials.is_mtls
  405. mock_get_client_ssl_credentials.assert_called_once()
  406. mock_ssl_channel_credentials.assert_called_once_with(
  407. certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES
  408. )
  409. def test_get_client_ssl_credentials_without_client_cert_env(
  410. self,
  411. mock_check_dca_metadata_path,
  412. mock_read_dca_metadata_file,
  413. mock_get_client_ssl_credentials,
  414. mock_ssl_channel_credentials,
  415. ):
  416. # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set.
  417. ssl_credentials = google.auth.transport.grpc.SslCredentials()
  418. assert ssl_credentials.ssl_credentials is not None
  419. assert not ssl_credentials.is_mtls
  420. mock_check_dca_metadata_path.assert_not_called()
  421. mock_read_dca_metadata_file.assert_not_called()
  422. mock_get_client_ssl_credentials.assert_not_called()
  423. mock_ssl_channel_credentials.assert_called_once()