1717from __future__ import absolute_import
1818
1919from concurrent import futures
20+ import logging
2021
2122import six
2223
24+ from google .auth .transport import _mtls_helper
25+
2326try :
2427 import grpc
2528except ImportError as caught_exc : # pragma: NO COVER
3134 caught_exc ,
3235 )
3336
37+ _LOGGER = logging .getLogger (__name__ )
38+
3439
3540class AuthMetadataPlugin (grpc .AuthMetadataPlugin ):
3641 """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each
@@ -92,7 +97,12 @@ def __del__(self):
9297
9398
9499def secure_authorized_channel (
95- credentials , request , target , ssl_credentials = None , ** kwargs
100+ credentials ,
101+ request ,
102+ target ,
103+ ssl_credentials = None ,
104+ client_cert_callback = None ,
105+ ** kwargs
96106):
97107 """Creates a secure authorized gRPC channel.
98108
@@ -114,11 +124,86 @@ def secure_authorized_channel(
114124
115125 # Create a channel.
116126 channel = google.auth.transport.grpc.secure_authorized_channel(
117- credentials, 'speech.googleapis.com:443', request)
127+ credentials, regular_endpoint, request,
128+ ssl_credentials=grpc.ssl_channel_credentials())
118129
119130 # Use the channel to create a stub.
120131 cloud_speech.create_Speech_stub(channel)
121132
133+ Usage:
134+
135+ There are actually a couple of options to create a channel, depending on if
136+ you want to create a regular or mutual TLS channel.
137+
138+ First let's list the endpoints (regular vs mutual TLS) to choose from::
139+
140+ regular_endpoint = 'speech.googleapis.com:443'
141+ mtls_endpoint = 'speech.mtls.googleapis.com:443'
142+
143+ Option 1: create a regular (non-mutual) TLS channel by explicitly setting
144+ the ssl_credentials::
145+
146+ regular_ssl_credentials = grpc.ssl_channel_credentials()
147+
148+ channel = google.auth.transport.grpc.secure_authorized_channel(
149+ credentials, regular_endpoint, request,
150+ ssl_credentials=regular_ssl_credentials)
151+
152+ Option 2: create a mutual TLS channel by calling a callback which returns
153+ the client side certificate and the key::
154+
155+ def my_client_cert_callback():
156+ code_to_load_client_cert_and_key()
157+ if loaded:
158+ return (pem_cert_bytes, pem_key_bytes)
159+ raise MyClientCertFailureException()
160+
161+ try:
162+ channel = google.auth.transport.grpc.secure_authorized_channel(
163+ credentials, mtls_endpoint, request,
164+ client_cert_callback=my_client_cert_callback)
165+ except MyClientCertFailureException:
166+ # handle the exception
167+
168+ Option 3: use application default SSL credentials. It searches and uses
169+ the command in a context aware metadata file, which is available on devices
170+ with endpoint verification support.
171+ See https://cloud.google.com/endpoint-verification/docs/overview::
172+
173+ try:
174+ default_ssl_credentials = SslCredentials()
175+ except:
176+ # Exception can be raised if the context aware metadata is malformed.
177+ # See :class:`SslCredentials` for the possible exceptions.
178+
179+ # Choose the endpoint based on the SSL credentials type.
180+ if default_ssl_credentials.is_mtls:
181+ endpoint_to_use = mtls_endpoint
182+ else:
183+ endpoint_to_use = regular_endpoint
184+ channel = google.auth.transport.grpc.secure_authorized_channel(
185+ credentials, endpoint_to_use, request,
186+ ssl_credentials=default_ssl_credentials)
187+
188+ Option 4: not setting ssl_credentials and client_cert_callback. For devices
189+ without endpoint verification support, a regular TLS channel is created;
190+ otherwise, a mutual TLS channel is created, however, the call should be
191+ wrapped in a try/except block in case of malformed context aware metadata.
192+
193+ The following code uses regular_endpoint, it works the same no matter the
194+ created channle is regular or mutual TLS. Regular endpoint ignores client
195+ certificate and key::
196+
197+ channel = google.auth.transport.grpc.secure_authorized_channel(
198+ credentials, regular_endpoint, request)
199+
200+ The following code uses mtls_endpoint, if the created channle is regular,
201+ and API mtls_endpoint is confgured to require client SSL credentials, API
202+ calls using this channel will be rejected::
203+
204+ channel = google.auth.transport.grpc.secure_authorized_channel(
205+ credentials, mtls_endpoint, request)
206+
122207 Args:
123208 credentials (google.auth.credentials.Credentials): The credentials to
124209 add to requests.
@@ -129,23 +214,118 @@ def secure_authorized_channel(
129214 target (str): The host and port of the service.
130215 ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
131216 credentials. This can be used to specify different certificates.
217+ This argument is mutually exclusive with client_cert_callback;
218+ providing both will raise an exception.
219+ If ssl_credentials and client_cert_callback are None, application
220+ default SSL credentials will be used.
221+ client_cert_callback (Callable[[], (bytes, bytes)]): Optional
222+ callback function to obtain client certicate and key for mutual TLS
223+ connection. This argument is mutually exclusive with
224+ ssl_credentials; providing both will raise an exception.
225+ If ssl_credentials and client_cert_callback are None, application
226+ default SSL credentials will be used.
132227 kwargs: Additional arguments to pass to :func:`grpc.secure_channel`.
133228
134229 Returns:
135230 grpc.Channel: The created gRPC channel.
231+
232+ Raises:
233+ OSError: If the cert provider command launch fails during the application
234+ default SSL credentials loading process on devices with endpoint
235+ verification support.
236+ RuntimeError: If the cert provider command has a runtime error during the
237+ application default SSL credentials loading process on devices with
238+ endpoint verification support.
239+ ValueError:
240+ If the context aware metadata file is malformed or if the cert provider
241+ command doesn't produce both client certificate and key during the
242+ application default SSL credentials loading process on devices with
243+ endpoint verification support.
136244 """
137245 # Create the metadata plugin for inserting the authorization header.
138246 metadata_plugin = AuthMetadataPlugin (credentials , request )
139247
140248 # Create a set of grpc.CallCredentials using the metadata plugin.
141249 google_auth_credentials = grpc .metadata_call_credentials (metadata_plugin )
142250
143- if ssl_credentials is None :
144- ssl_credentials = grpc .ssl_channel_credentials ()
251+ if ssl_credentials and client_cert_callback :
252+ raise ValueError (
253+ "Received both ssl_credentials and client_cert_callback; "
254+ "these are mutually exclusive."
255+ )
256+
257+ # If SSL credentials are not explicitly set, try client_cert_callback and ADC.
258+ if not ssl_credentials :
259+ if client_cert_callback :
260+ # Use the callback if provided.
261+ cert , key = client_cert_callback ()
262+ ssl_credentials = grpc .ssl_channel_credentials (
263+ certificate_chain = cert , private_key = key
264+ )
265+ else :
266+ # Use application default SSL credentials.
267+ adc_ssl_credentils = SslCredentials ()
268+ ssl_credentials = adc_ssl_credentils .ssl_credentials
145269
146270 # Combine the ssl credentials and the authorization credentials.
147271 composite_credentials = grpc .composite_channel_credentials (
148272 ssl_credentials , google_auth_credentials
149273 )
150274
151275 return grpc .secure_channel (target , composite_credentials , ** kwargs )
276+
277+
278+ class SslCredentials :
279+ """Class for application default SSL credentials.
280+
281+ For devices with endpoint verification support, a device certificate will be
282+ automatically loaded and mutual TLS will be established.
283+ See https://cloud.google.com/endpoint-verification/docs/overview.
284+ """
285+
286+ def __init__ (self ):
287+ # Load client SSL credentials.
288+ self ._context_aware_metadata_path = _mtls_helper ._check_dca_metadata_path (
289+ _mtls_helper .CONTEXT_AWARE_METADATA_PATH
290+ )
291+ if self ._context_aware_metadata_path :
292+ self ._is_mtls = True
293+ else :
294+ self ._is_mtls = False
295+
296+ @property
297+ def ssl_credentials (self ):
298+ """Get the created SSL channel credentials.
299+
300+ For devices with endpoint verification support, if the device certificate
301+ loading has any problems, corresponding exceptions will be raised. For
302+ a device without endpoint verification support, no exceptions will be
303+ raised.
304+
305+ Returns:
306+ grpc.ChannelCredentials: The created grpc channel credentials.
307+
308+ Raises:
309+ OSError: If the cert provider command launch fails.
310+ RuntimeError: If the cert provider command has a runtime error.
311+ ValueError:
312+ If the context aware metadata file is malformed or if the cert provider
313+ command doesn't produce both the client certificate and key.
314+ """
315+ if self ._context_aware_metadata_path :
316+ metadata = _mtls_helper ._read_dca_metadata_file (
317+ self ._context_aware_metadata_path
318+ )
319+ cert , key = _mtls_helper .get_client_ssl_credentials (metadata )
320+ self ._ssl_credentials = grpc .ssl_channel_credentials (
321+ certificate_chain = cert , private_key = key
322+ )
323+ else :
324+ self ._ssl_credentials = grpc .ssl_channel_credentials ()
325+
326+ return self ._ssl_credentials
327+
328+ @property
329+ def is_mtls (self ):
330+ """Indicates if the created SSL channel credentials is mutual TLS."""
331+ return self ._is_mtls
0 commit comments