grpc.py 14 KB

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