grpc.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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. """Authorization support for gRPC."""
  15. from __future__ import absolute_import
  16. import logging
  17. import os
  18. from google.auth import environment_vars
  19. from google.auth import exceptions
  20. from google.auth.transport import _mtls_helper
  21. from google.oauth2 import service_account
  22. try:
  23. import grpc # type: ignore
  24. except ImportError as caught_exc: # pragma: NO COVER
  25. raise ImportError(
  26. "gRPC is not installed from please install the grpcio package to use the gRPC transport."
  27. ) from caught_exc
  28. _LOGGER = logging.getLogger(__name__)
  29. class AuthMetadataPlugin(grpc.AuthMetadataPlugin):
  30. """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each
  31. request.
  32. .. _gRPC AuthMetadataPlugin:
  33. http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin
  34. Args:
  35. credentials (google.auth.credentials.Credentials): The credentials to
  36. add to requests.
  37. request (google.auth.transport.Request): A HTTP transport request
  38. object used to refresh credentials as needed.
  39. default_host (Optional[str]): A host like "pubsub.googleapis.com".
  40. This is used when a self-signed JWT is created from service
  41. account credentials.
  42. """
  43. def __init__(self, credentials, request, default_host=None):
  44. # pylint: disable=no-value-for-parameter
  45. # pylint doesn't realize that the super method takes no arguments
  46. # because this class is the same name as the superclass.
  47. super(AuthMetadataPlugin, self).__init__()
  48. self._credentials = credentials
  49. self._request = request
  50. self._default_host = default_host
  51. def _get_authorization_headers(self, context):
  52. """Gets the authorization headers for a request.
  53. Returns:
  54. Sequence[Tuple[str, str]]: A list of request headers (key, value)
  55. to add to the request.
  56. """
  57. headers = {}
  58. # https://google.aip.dev/auth/4111
  59. # Attempt to use self-signed JWTs when a service account is used.
  60. # A default host must be explicitly provided since it cannot always
  61. # be determined from the context.service_url.
  62. if isinstance(self._credentials, service_account.Credentials):
  63. self._credentials._create_self_signed_jwt(
  64. "https://{}/".format(self._default_host) if self._default_host else None
  65. )
  66. self._credentials.before_request(
  67. self._request, context.method_name, context.service_url, headers
  68. )
  69. return list(headers.items())
  70. def __call__(self, context, callback):
  71. """Passes authorization metadata into the given callback.
  72. Args:
  73. context (grpc.AuthMetadataContext): The RPC context.
  74. callback (grpc.AuthMetadataPluginCallback): The callback that will
  75. be invoked to pass in the authorization metadata.
  76. """
  77. callback(self._get_authorization_headers(context), None)
  78. def secure_authorized_channel(
  79. credentials,
  80. request,
  81. target,
  82. ssl_credentials=None,
  83. client_cert_callback=None,
  84. **kwargs
  85. ):
  86. """Creates a secure authorized gRPC channel.
  87. This creates a channel with SSL and :class:`AuthMetadataPlugin`. This
  88. channel can be used to create a stub that can make authorized requests.
  89. Users can configure client certificate or rely on device certificates to
  90. establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
  91. variable is explicitly set to `true`.
  92. Example::
  93. import google.auth
  94. import google.auth.transport.grpc
  95. import google.auth.transport.requests
  96. from google.cloud.speech.v1 import cloud_speech_pb2
  97. # Get credentials.
  98. credentials, _ = google.auth.default()
  99. # Get an HTTP request function to refresh credentials.
  100. request = google.auth.transport.requests.Request()
  101. # Create a channel.
  102. channel = google.auth.transport.grpc.secure_authorized_channel(
  103. credentials, regular_endpoint, request,
  104. ssl_credentials=grpc.ssl_channel_credentials())
  105. # Use the channel to create a stub.
  106. cloud_speech.create_Speech_stub(channel)
  107. Usage:
  108. There are actually a couple of options to create a channel, depending on if
  109. you want to create a regular or mutual TLS channel.
  110. First let's list the endpoints (regular vs mutual TLS) to choose from::
  111. regular_endpoint = 'speech.googleapis.com:443'
  112. mtls_endpoint = 'speech.mtls.googleapis.com:443'
  113. Option 1: create a regular (non-mutual) TLS channel by explicitly setting
  114. the ssl_credentials::
  115. regular_ssl_credentials = grpc.ssl_channel_credentials()
  116. channel = google.auth.transport.grpc.secure_authorized_channel(
  117. credentials, regular_endpoint, request,
  118. ssl_credentials=regular_ssl_credentials)
  119. Option 2: create a mutual TLS channel by calling a callback which returns
  120. the client side certificate and the key (Note that
  121. `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
  122. set to `true`)::
  123. def my_client_cert_callback():
  124. code_to_load_client_cert_and_key()
  125. if loaded:
  126. return (pem_cert_bytes, pem_key_bytes)
  127. raise MyClientCertFailureException()
  128. try:
  129. channel = google.auth.transport.grpc.secure_authorized_channel(
  130. credentials, mtls_endpoint, request,
  131. client_cert_callback=my_client_cert_callback)
  132. except MyClientCertFailureException:
  133. # handle the exception
  134. Option 3: use application default SSL credentials. It searches and uses
  135. the command in a context aware metadata file, which is available on devices
  136. with endpoint verification support (Note that
  137. `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
  138. set to `true`).
  139. See https://cloud.google.com/endpoint-verification/docs/overview::
  140. try:
  141. default_ssl_credentials = SslCredentials()
  142. except:
  143. # Exception can be raised if the context aware metadata is malformed.
  144. # See :class:`SslCredentials` for the possible exceptions.
  145. # Choose the endpoint based on the SSL credentials type.
  146. if default_ssl_credentials.is_mtls:
  147. endpoint_to_use = mtls_endpoint
  148. else:
  149. endpoint_to_use = regular_endpoint
  150. channel = google.auth.transport.grpc.secure_authorized_channel(
  151. credentials, endpoint_to_use, request,
  152. ssl_credentials=default_ssl_credentials)
  153. Option 4: not setting ssl_credentials and client_cert_callback. For devices
  154. without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE`
  155. environment variable is not `true`, a regular TLS channel is created;
  156. otherwise, a mutual TLS channel is created, however, the call should be
  157. wrapped in a try/except block in case of malformed context aware metadata.
  158. The following code uses regular_endpoint, it works the same no matter the
  159. created channle is regular or mutual TLS. Regular endpoint ignores client
  160. certificate and key::
  161. channel = google.auth.transport.grpc.secure_authorized_channel(
  162. credentials, regular_endpoint, request)
  163. The following code uses mtls_endpoint, if the created channle is regular,
  164. and API mtls_endpoint is confgured to require client SSL credentials, API
  165. calls using this channel will be rejected::
  166. channel = google.auth.transport.grpc.secure_authorized_channel(
  167. credentials, mtls_endpoint, request)
  168. Args:
  169. credentials (google.auth.credentials.Credentials): The credentials to
  170. add to requests.
  171. request (google.auth.transport.Request): A HTTP transport request
  172. object used to refresh credentials as needed. Even though gRPC
  173. is a separate transport, there's no way to refresh the credentials
  174. without using a standard http transport.
  175. target (str): The host and port of the service.
  176. ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
  177. credentials. This can be used to specify different certificates.
  178. This argument is mutually exclusive with client_cert_callback;
  179. providing both will raise an exception.
  180. If ssl_credentials and client_cert_callback are None, application
  181. default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE`
  182. environment variable is explicitly set to `true`, otherwise one way TLS
  183. SSL credentials are used.
  184. client_cert_callback (Callable[[], (bytes, bytes)]): Optional
  185. callback function to obtain client certicate and key for mutual TLS
  186. connection. This argument is mutually exclusive with
  187. ssl_credentials; providing both will raise an exception.
  188. This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE`
  189. environment variable is explicitly set to `true`.
  190. kwargs: Additional arguments to pass to :func:`grpc.secure_channel`.
  191. Returns:
  192. grpc.Channel: The created gRPC channel.
  193. Raises:
  194. google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
  195. creation failed for any reason.
  196. """
  197. # Create the metadata plugin for inserting the authorization header.
  198. metadata_plugin = AuthMetadataPlugin(credentials, request)
  199. # Create a set of grpc.CallCredentials using the metadata plugin.
  200. google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
  201. if ssl_credentials and client_cert_callback:
  202. raise exceptions.MalformedError(
  203. "Received both ssl_credentials and client_cert_callback; "
  204. "these are mutually exclusive."
  205. )
  206. # If SSL credentials are not explicitly set, try client_cert_callback and ADC.
  207. if not ssl_credentials:
  208. use_client_cert = os.getenv(
  209. environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
  210. )
  211. if use_client_cert == "true" and client_cert_callback:
  212. # Use the callback if provided.
  213. cert, key = client_cert_callback()
  214. ssl_credentials = grpc.ssl_channel_credentials(
  215. certificate_chain=cert, private_key=key
  216. )
  217. elif use_client_cert == "true":
  218. # Use application default SSL credentials.
  219. adc_ssl_credentils = SslCredentials()
  220. ssl_credentials = adc_ssl_credentils.ssl_credentials
  221. else:
  222. ssl_credentials = grpc.ssl_channel_credentials()
  223. # Combine the ssl credentials and the authorization credentials.
  224. composite_credentials = grpc.composite_channel_credentials(
  225. ssl_credentials, google_auth_credentials
  226. )
  227. return grpc.secure_channel(target, composite_credentials, **kwargs)
  228. class SslCredentials:
  229. """Class for application default SSL credentials.
  230. The behavior is controlled by `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment
  231. variable whose default value is `false`. Client certificate will not be used
  232. unless the environment variable is explicitly set to `true`. See
  233. https://google.aip.dev/auth/4114
  234. If the environment variable is `true`, then for devices with endpoint verification
  235. support, a device certificate will be automatically loaded and mutual TLS will
  236. be established.
  237. See https://cloud.google.com/endpoint-verification/docs/overview.
  238. """
  239. def __init__(self):
  240. use_client_cert = os.getenv(
  241. environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false"
  242. )
  243. if use_client_cert != "true":
  244. self._is_mtls = False
  245. else:
  246. # Load client SSL credentials.
  247. metadata_path = _mtls_helper._check_config_path(
  248. _mtls_helper.CONTEXT_AWARE_METADATA_PATH
  249. )
  250. self._is_mtls = metadata_path is not None
  251. @property
  252. def ssl_credentials(self):
  253. """Get the created SSL channel credentials.
  254. For devices with endpoint verification support, if the device certificate
  255. loading has any problems, corresponding exceptions will be raised. For
  256. a device without endpoint verification support, no exceptions will be
  257. raised.
  258. Returns:
  259. grpc.ChannelCredentials: The created grpc channel credentials.
  260. Raises:
  261. google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
  262. creation failed for any reason.
  263. """
  264. if self._is_mtls:
  265. try:
  266. _, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
  267. self._ssl_credentials = grpc.ssl_channel_credentials(
  268. certificate_chain=cert, private_key=key
  269. )
  270. except exceptions.ClientCertError as caught_exc:
  271. new_exc = exceptions.MutualTLSChannelError(caught_exc)
  272. raise new_exc from caught_exc
  273. else:
  274. self._ssl_credentials = grpc.ssl_channel_credentials()
  275. return self._ssl_credentials
  276. @property
  277. def is_mtls(self):
  278. """Indicates if the created SSL channel credentials is mutual TLS."""
  279. return self._is_mtls