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
15"""Authorization support for gRPC."""
16
17from __future__ import absolute_import
18
19import logging
20import warnings
21
22from google.auth import exceptions
23from google.auth.transport import _mtls_helper
24from google.auth.transport import mtls
25from google.oauth2 import service_account
26
27try:
28 import grpc # type: ignore
29except ImportError as caught_exc: # pragma: NO COVER
30 raise ImportError(
31 "gRPC is not installed from please install the grpcio package to use the gRPC transport."
32 ) from caught_exc
33
34
35_grpc_ver_str = getattr(grpc, "__version__", None)
36if isinstance(_grpc_ver_str, str):
37 _parts = []
38 for _part in _grpc_ver_str.split("."):
39 try:
40 _parts.append(int(_part))
41 except ValueError:
42 break
43 if _parts and tuple(_parts) < (1, 83, 0):
44 warnings.warn(
45 "grpcio < 1.83.0 does not support Post-Quantum Cryptography (PQC). "
46 "Support for non-PQC environments is deprecated. In October 2026, "
47 "google-auth will raise its minimum requirements "
48 "to enforce grpcio >= 1.83.0. "
49 "For more details on Google Cloud's post-quantum security migration, visit: "
50 "https://cloud.google.com/security/resources/post-quantum-cryptography",
51 FutureWarning,
52 )
53
54_LOGGER = logging.getLogger(__name__)
55
56
57class AuthMetadataPlugin(grpc.AuthMetadataPlugin):
58 """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each
59 request.
60
61 .. _gRPC AuthMetadataPlugin:
62 http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin
63
64 Args:
65 credentials (google.auth.credentials.Credentials): The credentials to
66 add to requests.
67 request (google.auth.transport.Request): A HTTP transport request
68 object used to refresh credentials as needed.
69 default_host (Optional[str]): A host like "pubsub.googleapis.com".
70 This is used when a self-signed JWT is created from service
71 account credentials.
72 suppress_metrics_header (bool): When enabled, ``x-goog-api-client``
73 will be stripped from authorization headers.
74 """
75
76 def __init__(
77 self, credentials, request, default_host=None, *, suppress_metrics_header=False
78 ):
79 # pylint: disable=no-value-for-parameter
80 # pylint doesn't realize that the super method takes no arguments
81 # because this class is the same name as the superclass.
82 super(AuthMetadataPlugin, self).__init__()
83 self._credentials = credentials
84 self._request = request
85 self._default_host = default_host
86 self._suppress_metrics_header = suppress_metrics_header
87
88 def _get_authorization_headers(self, context):
89 """Gets the authorization headers for a request.
90
91 Returns:
92 Sequence[Tuple[str, str]]: A list of request headers (key, value)
93 to add to the request.
94 """
95 headers = {}
96
97 # https://google.aip.dev/auth/4111
98 # Attempt to use self-signed JWTs when a service account is used.
99 # A default host must be explicitly provided since it cannot always
100 # be determined from the context.service_url.
101 if isinstance(self._credentials, service_account.Credentials):
102 self._credentials._create_self_signed_jwt(
103 "https://{}/".format(self._default_host) if self._default_host else None
104 )
105
106 self._credentials.before_request(
107 self._request, context.method_name, context.service_url, headers
108 )
109
110 if self._suppress_metrics_header and "x-goog-api-client" in headers:
111 del headers["x-goog-api-client"]
112
113 return list(headers.items())
114
115 def __call__(self, context, callback):
116 """Passes authorization metadata into the given callback.
117
118 Args:
119 context (grpc.AuthMetadataContext): The RPC context.
120 callback (grpc.AuthMetadataPluginCallback): The callback that will
121 be invoked to pass in the authorization metadata.
122 """
123 callback(self._get_authorization_headers(context), None)
124
125
126def secure_authorized_channel(
127 credentials,
128 request,
129 target,
130 ssl_credentials=None,
131 client_cert_callback=None,
132 **kwargs
133):
134 """Creates a secure authorized gRPC channel.
135
136 This creates a channel with SSL and :class:`AuthMetadataPlugin`. This
137 channel can be used to create a stub that can make authorized requests.
138 Users can configure client certificate or rely on device certificates to
139 establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE`
140 variable is explicitly set to `true`.
141
142 Example::
143
144 import google.auth
145 import google.auth.transport.grpc
146 import google.auth.transport.requests
147 from google.cloud.speech.v1 import cloud_speech_pb2
148
149 # Get credentials.
150 credentials, _ = google.auth.default()
151
152 # Get an HTTP request function to refresh credentials.
153 request = google.auth.transport.requests.Request()
154
155 # Create a channel.
156 channel = google.auth.transport.grpc.secure_authorized_channel(
157 credentials, regular_endpoint, request,
158 ssl_credentials=grpc.ssl_channel_credentials())
159
160 # Use the channel to create a stub.
161 cloud_speech.create_Speech_stub(channel)
162
163 Usage:
164
165 There are actually a couple of options to create a channel, depending on if
166 you want to create a regular or mutual TLS channel.
167
168 First let's list the endpoints (regular vs mutual TLS) to choose from::
169
170 regular_endpoint = 'speech.googleapis.com:443'
171 mtls_endpoint = 'speech.mtls.googleapis.com:443'
172
173 Option 1: create a regular (non-mutual) TLS channel by explicitly setting
174 the ssl_credentials::
175
176 regular_ssl_credentials = grpc.ssl_channel_credentials()
177
178 channel = google.auth.transport.grpc.secure_authorized_channel(
179 credentials, request, regular_endpoint,
180 ssl_credentials=regular_ssl_credentials)
181
182 Option 2: create a mutual TLS channel by calling a callback which returns
183 the client side certificate and the key (Note that
184 `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
185 set to `true`)::
186
187 def my_client_cert_callback():
188 code_to_load_client_cert_and_key()
189 if loaded:
190 return (pem_cert_bytes, pem_key_bytes)
191 raise MyClientCertFailureException()
192
193 try:
194 channel = google.auth.transport.grpc.secure_authorized_channel(
195 credentials, request, mtls_endpoint,
196 client_cert_callback=my_client_cert_callback)
197 except MyClientCertFailureException:
198 # handle the exception
199
200 Option 3: use application default SSL credentials. It searches and uses
201 the command in a context aware metadata file, which is available on devices
202 with endpoint verification support (Note that
203 `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly
204 set to `true`).
205 See https://cloud.google.com/endpoint-verification/docs/overview::
206
207 try:
208 default_ssl_credentials = SslCredentials()
209 except:
210 # Exception can be raised if the context aware metadata is malformed.
211 # See :class:`SslCredentials` for the possible exceptions.
212
213 # Choose the endpoint based on the SSL credentials type.
214 if default_ssl_credentials.is_mtls:
215 endpoint_to_use = mtls_endpoint
216 else:
217 endpoint_to_use = regular_endpoint
218 channel = google.auth.transport.grpc.secure_authorized_channel(
219 credentials, request, endpoint_to_use,
220 ssl_credentials=default_ssl_credentials)
221
222 Option 4: not setting ssl_credentials and client_cert_callback. For devices
223 without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE`
224 environment variable is not `true`, a regular TLS channel is created;
225 otherwise, a mutual TLS channel is created, however, the call should be
226 wrapped in a try/except block in case of malformed context aware metadata.
227
228 The following code uses regular_endpoint, it works the same no matter the
229 created channle is regular or mutual TLS. Regular endpoint ignores client
230 certificate and key::
231
232 channel = google.auth.transport.grpc.secure_authorized_channel(
233 credentials, request, regular_endpoint)
234
235 The following code uses mtls_endpoint, if the created channle is regular,
236 and API mtls_endpoint is confgured to require client SSL credentials, API
237 calls using this channel will be rejected::
238
239 channel = google.auth.transport.grpc.secure_authorized_channel(
240 credentials, request, mtls_endpoint)
241
242 Args:
243 credentials (google.auth.credentials.Credentials): The credentials to
244 add to requests.
245 request (google.auth.transport.Request): A HTTP transport request
246 object used to refresh credentials as needed. Even though gRPC
247 is a separate transport, there's no way to refresh the credentials
248 without using a standard http transport.
249 target (str): The host and port of the service.
250 ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
251 credentials. This can be used to specify different certificates.
252 This argument is mutually exclusive with client_cert_callback;
253 providing both will raise an exception.
254 If ssl_credentials and client_cert_callback are None, application
255 default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE`
256 environment variable is explicitly set to `true`, otherwise one way TLS
257 SSL credentials are used.
258 client_cert_callback (Callable[[], (bytes, bytes)]): Optional
259 callback function to obtain client certicate and key for mutual TLS
260 connection. This argument is mutually exclusive with
261 ssl_credentials; providing both will raise an exception.
262 This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE`
263 environment variable is explicitly set to `true`.
264 kwargs: Additional arguments to pass to :func:`grpc.secure_channel`.
265
266 Returns:
267 grpc.Channel: The created gRPC channel.
268
269 Raises:
270 google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
271 creation failed for any reason.
272 """
273 # Create the metadata plugin for inserting the authorization header.
274 metadata_plugin = AuthMetadataPlugin(credentials, request)
275
276 # Create a set of grpc.CallCredentials using the metadata plugin.
277 google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
278
279 if ssl_credentials and client_cert_callback:
280 raise exceptions.MalformedError(
281 "Received both ssl_credentials and client_cert_callback; "
282 "these are mutually exclusive."
283 )
284
285 # If SSL credentials are not explicitly set, try client_cert_callback and ADC.
286 if not ssl_credentials:
287 use_client_cert = _mtls_helper.check_use_client_cert()
288 if use_client_cert and client_cert_callback:
289 # Use the callback if provided.
290 cert, key = client_cert_callback()
291 ssl_credentials = grpc.ssl_channel_credentials(
292 certificate_chain=cert, private_key=key
293 )
294 elif use_client_cert:
295 # Use application default SSL credentials.
296 adc_ssl_credentils = SslCredentials()
297 ssl_credentials = adc_ssl_credentils.ssl_credentials
298 else:
299 ssl_credentials = grpc.ssl_channel_credentials()
300
301 # Combine the ssl credentials and the authorization credentials.
302 composite_credentials = grpc.composite_channel_credentials(
303 ssl_credentials, google_auth_credentials
304 )
305
306 return grpc.secure_channel(target, composite_credentials, **kwargs)
307
308
309class SslCredentials:
310 """Class for application default SSL credentials.
311
312 Mutual TLS (mTLS) is enabled if either:
313
314 1. The `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is explicitly
315 set to `"true"`.
316 2. The `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is unset or empty,
317 but a valid workload certificate configuration is found (e.g., via the
318 `GOOGLE_API_CERTIFICATE_CONFIG` environment variable or the default gcloud config path).
319
320 See https://google.aip.dev/auth/4114 for client certificate discovery details.
321
322 If client certificate usage is enabled, then for devices with endpoint
323 verification support, a device certificate will be automatically loaded and
324 mutual TLS will be established.
325 See https://cloud.google.com/endpoint-verification/docs/overview.
326 """
327
328 def __init__(self):
329 use_client_cert = _mtls_helper.check_use_client_cert()
330 if not use_client_cert:
331 self._is_mtls = False
332 else:
333 self._is_mtls = mtls.has_default_client_cert_source()
334
335 @property
336 def ssl_credentials(self):
337 """Get the created SSL channel credentials.
338
339 For devices with endpoint verification support, if the device certificate
340 loading has any problems, corresponding exceptions will be raised. For
341 a device without endpoint verification support, no exceptions will be
342 raised.
343
344 Returns:
345 grpc.ChannelCredentials: The created grpc channel credentials.
346
347 Raises:
348 google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel
349 creation failed for any reason.
350 """
351 if self._is_mtls:
352 try:
353 has_cert, cert, key, _ = _mtls_helper.get_client_ssl_credentials()
354 if has_cert:
355 self._ssl_credentials = grpc.ssl_channel_credentials(
356 certificate_chain=cert, private_key=key
357 )
358 else:
359 self._ssl_credentials = grpc.ssl_channel_credentials()
360 self._is_mtls = False
361 except (exceptions.ClientCertError, OSError) as caught_exc:
362 new_exc = exceptions.MutualTLSChannelError(caught_exc)
363 raise new_exc from caught_exc
364 else:
365 self._ssl_credentials = grpc.ssl_channel_credentials()
366
367 return self._ssl_credentials
368
369 @property
370 def is_mtls(self):
371 """Indicates if the created SSL channel credentials is mutual TLS."""
372 return self._is_mtls