• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *
3  * Copyright 2015 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 #include <grpc/support/port_platform.h>
20 
21 #include "src/core/lib/security/transport/auth_filters.h"
22 
23 #include <string.h>
24 
25 #include <grpc/support/alloc.h>
26 #include <grpc/support/log.h>
27 #include <grpc/support/string_util.h>
28 
29 #include "src/core/lib/channel/channel_stack.h"
30 #include "src/core/lib/gpr/string.h"
31 #include "src/core/lib/profiling/timers.h"
32 #include "src/core/lib/security/context/security_context.h"
33 #include "src/core/lib/security/credentials/credentials.h"
34 #include "src/core/lib/security/security_connector/security_connector.h"
35 #include "src/core/lib/slice/slice_internal.h"
36 #include "src/core/lib/slice/slice_string_helpers.h"
37 #include "src/core/lib/surface/call.h"
38 #include "src/core/lib/transport/static_metadata.h"
39 
40 #define MAX_CREDENTIALS_METADATA_COUNT 4
41 
42 namespace {
43 /* We can have a per-call credentials. */
44 struct call_data {
45   gpr_arena* arena;
46   grpc_call_stack* owning_call;
47   grpc_call_combiner* call_combiner;
48   grpc_call_credentials* creds;
49   grpc_slice host;
50   grpc_slice method;
51   /* pollset{_set} bound to this call; if we need to make external
52      network requests, they should be done under a pollset added to this
53      pollset_set so that work can progress when this call wants work to progress
54   */
55   grpc_polling_entity* pollent;
56   grpc_credentials_mdelem_array md_array;
57   grpc_linked_mdelem md_links[MAX_CREDENTIALS_METADATA_COUNT];
58   grpc_auth_metadata_context auth_md_context;
59   grpc_closure async_result_closure;
60   grpc_closure check_call_host_cancel_closure;
61   grpc_closure get_request_metadata_cancel_closure;
62 };
63 
64 /* We can have a per-channel credentials. */
65 struct channel_data {
66   grpc_channel_security_connector* security_connector;
67   grpc_auth_context* auth_context;
68 };
69 }  // namespace
70 
grpc_auth_metadata_context_reset(grpc_auth_metadata_context * auth_md_context)71 void grpc_auth_metadata_context_reset(
72     grpc_auth_metadata_context* auth_md_context) {
73   if (auth_md_context->service_url != nullptr) {
74     gpr_free(const_cast<char*>(auth_md_context->service_url));
75     auth_md_context->service_url = nullptr;
76   }
77   if (auth_md_context->method_name != nullptr) {
78     gpr_free(const_cast<char*>(auth_md_context->method_name));
79     auth_md_context->method_name = nullptr;
80   }
81   GRPC_AUTH_CONTEXT_UNREF(
82       (grpc_auth_context*)auth_md_context->channel_auth_context,
83       "grpc_auth_metadata_context");
84   auth_md_context->channel_auth_context = nullptr;
85 }
86 
add_error(grpc_error ** combined,grpc_error * error)87 static void add_error(grpc_error** combined, grpc_error* error) {
88   if (error == GRPC_ERROR_NONE) return;
89   if (*combined == GRPC_ERROR_NONE) {
90     *combined = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
91         "Client auth metadata plugin error");
92   }
93   *combined = grpc_error_add_child(*combined, error);
94 }
95 
on_credentials_metadata(void * arg,grpc_error * input_error)96 static void on_credentials_metadata(void* arg, grpc_error* input_error) {
97   grpc_transport_stream_op_batch* batch =
98       static_cast<grpc_transport_stream_op_batch*>(arg);
99   grpc_call_element* elem =
100       static_cast<grpc_call_element*>(batch->handler_private.extra_arg);
101   call_data* calld = static_cast<call_data*>(elem->call_data);
102   grpc_auth_metadata_context_reset(&calld->auth_md_context);
103   grpc_error* error = GRPC_ERROR_REF(input_error);
104   if (error == GRPC_ERROR_NONE) {
105     GPR_ASSERT(calld->md_array.size <= MAX_CREDENTIALS_METADATA_COUNT);
106     GPR_ASSERT(batch->send_initial_metadata);
107     grpc_metadata_batch* mdb =
108         batch->payload->send_initial_metadata.send_initial_metadata;
109     for (size_t i = 0; i < calld->md_array.size; ++i) {
110       add_error(&error, grpc_metadata_batch_add_tail(
111                             mdb, &calld->md_links[i],
112                             GRPC_MDELEM_REF(calld->md_array.md[i])));
113     }
114   }
115   if (error == GRPC_ERROR_NONE) {
116     grpc_call_next_op(elem, batch);
117   } else {
118     error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS,
119                                GRPC_STATUS_UNAVAILABLE);
120     grpc_transport_stream_op_batch_finish_with_failure(batch, error,
121                                                        calld->call_combiner);
122   }
123   GRPC_CALL_STACK_UNREF(calld->owning_call, "get_request_metadata");
124 }
125 
grpc_auth_metadata_context_build(const char * url_scheme,grpc_slice call_host,grpc_slice call_method,grpc_auth_context * auth_context,grpc_auth_metadata_context * auth_md_context)126 void grpc_auth_metadata_context_build(
127     const char* url_scheme, grpc_slice call_host, grpc_slice call_method,
128     grpc_auth_context* auth_context,
129     grpc_auth_metadata_context* auth_md_context) {
130   char* service = grpc_slice_to_c_string(call_method);
131   char* last_slash = strrchr(service, '/');
132   char* method_name = nullptr;
133   char* service_url = nullptr;
134   grpc_auth_metadata_context_reset(auth_md_context);
135   if (last_slash == nullptr) {
136     gpr_log(GPR_ERROR, "No '/' found in fully qualified method name");
137     service[0] = '\0';
138     method_name = gpr_strdup("");
139   } else if (last_slash == service) {
140     method_name = gpr_strdup("");
141   } else {
142     *last_slash = '\0';
143     method_name = gpr_strdup(last_slash + 1);
144   }
145   char* host_and_port = grpc_slice_to_c_string(call_host);
146   if (url_scheme != nullptr && strcmp(url_scheme, GRPC_SSL_URL_SCHEME) == 0) {
147     /* Remove the port if it is 443. */
148     char* port_delimiter = strrchr(host_and_port, ':');
149     if (port_delimiter != nullptr && strcmp(port_delimiter + 1, "443") == 0) {
150       *port_delimiter = '\0';
151     }
152   }
153   gpr_asprintf(&service_url, "%s://%s%s",
154                url_scheme == nullptr ? "" : url_scheme, host_and_port, service);
155   auth_md_context->service_url = service_url;
156   auth_md_context->method_name = method_name;
157   auth_md_context->channel_auth_context =
158       GRPC_AUTH_CONTEXT_REF(auth_context, "grpc_auth_metadata_context");
159   gpr_free(service);
160   gpr_free(host_and_port);
161 }
162 
cancel_get_request_metadata(void * arg,grpc_error * error)163 static void cancel_get_request_metadata(void* arg, grpc_error* error) {
164   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
165   call_data* calld = static_cast<call_data*>(elem->call_data);
166   if (error != GRPC_ERROR_NONE) {
167     grpc_call_credentials_cancel_get_request_metadata(
168         calld->creds, &calld->md_array, GRPC_ERROR_REF(error));
169   }
170 }
171 
send_security_metadata(grpc_call_element * elem,grpc_transport_stream_op_batch * batch)172 static void send_security_metadata(grpc_call_element* elem,
173                                    grpc_transport_stream_op_batch* batch) {
174   call_data* calld = static_cast<call_data*>(elem->call_data);
175   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
176   grpc_client_security_context* ctx =
177       static_cast<grpc_client_security_context*>(
178           batch->payload->context[GRPC_CONTEXT_SECURITY].value);
179   grpc_call_credentials* channel_call_creds =
180       chand->security_connector->request_metadata_creds;
181   int call_creds_has_md = (ctx != nullptr) && (ctx->creds != nullptr);
182 
183   if (channel_call_creds == nullptr && !call_creds_has_md) {
184     /* Skip sending metadata altogether. */
185     grpc_call_next_op(elem, batch);
186     return;
187   }
188 
189   if (channel_call_creds != nullptr && call_creds_has_md) {
190     calld->creds = grpc_composite_call_credentials_create(channel_call_creds,
191                                                           ctx->creds, nullptr);
192     if (calld->creds == nullptr) {
193       grpc_transport_stream_op_batch_finish_with_failure(
194           batch,
195           grpc_error_set_int(
196               GRPC_ERROR_CREATE_FROM_STATIC_STRING(
197                   "Incompatible credentials set on channel and call."),
198               GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAUTHENTICATED),
199           calld->call_combiner);
200       return;
201     }
202   } else {
203     calld->creds = grpc_call_credentials_ref(
204         call_creds_has_md ? ctx->creds : channel_call_creds);
205   }
206 
207   grpc_auth_metadata_context_build(
208       chand->security_connector->base.url_scheme, calld->host, calld->method,
209       chand->auth_context, &calld->auth_md_context);
210 
211   GPR_ASSERT(calld->pollent != nullptr);
212   GRPC_CALL_STACK_REF(calld->owning_call, "get_request_metadata");
213   GRPC_CLOSURE_INIT(&calld->async_result_closure, on_credentials_metadata,
214                     batch, grpc_schedule_on_exec_ctx);
215   grpc_error* error = GRPC_ERROR_NONE;
216   if (grpc_call_credentials_get_request_metadata(
217           calld->creds, calld->pollent, calld->auth_md_context,
218           &calld->md_array, &calld->async_result_closure, &error)) {
219     // Synchronous return; invoke on_credentials_metadata() directly.
220     on_credentials_metadata(batch, error);
221     GRPC_ERROR_UNREF(error);
222   } else {
223     // Async return; register cancellation closure with call combiner.
224     grpc_call_combiner_set_notify_on_cancel(
225         calld->call_combiner,
226         GRPC_CLOSURE_INIT(&calld->get_request_metadata_cancel_closure,
227                           cancel_get_request_metadata, elem,
228                           grpc_schedule_on_exec_ctx));
229   }
230 }
231 
on_host_checked(void * arg,grpc_error * error)232 static void on_host_checked(void* arg, grpc_error* error) {
233   grpc_transport_stream_op_batch* batch =
234       static_cast<grpc_transport_stream_op_batch*>(arg);
235   grpc_call_element* elem =
236       static_cast<grpc_call_element*>(batch->handler_private.extra_arg);
237   call_data* calld = static_cast<call_data*>(elem->call_data);
238   if (error == GRPC_ERROR_NONE) {
239     send_security_metadata(elem, batch);
240   } else {
241     char* error_msg;
242     char* host = grpc_slice_to_c_string(calld->host);
243     gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.",
244                  host);
245     gpr_free(host);
246     grpc_transport_stream_op_batch_finish_with_failure(
247         batch,
248         grpc_error_set_int(GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg),
249                            GRPC_ERROR_INT_GRPC_STATUS,
250                            GRPC_STATUS_UNAUTHENTICATED),
251         calld->call_combiner);
252     gpr_free(error_msg);
253   }
254   GRPC_CALL_STACK_UNREF(calld->owning_call, "check_call_host");
255 }
256 
cancel_check_call_host(void * arg,grpc_error * error)257 static void cancel_check_call_host(void* arg, grpc_error* error) {
258   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
259   call_data* calld = static_cast<call_data*>(elem->call_data);
260   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
261   if (error != GRPC_ERROR_NONE) {
262     grpc_channel_security_connector_cancel_check_call_host(
263         chand->security_connector, &calld->async_result_closure,
264         GRPC_ERROR_REF(error));
265   }
266 }
267 
auth_start_transport_stream_op_batch(grpc_call_element * elem,grpc_transport_stream_op_batch * batch)268 static void auth_start_transport_stream_op_batch(
269     grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
270   GPR_TIMER_SCOPE("auth_start_transport_stream_op_batch", 0);
271 
272   /* grab pointers to our data from the call element */
273   call_data* calld = static_cast<call_data*>(elem->call_data);
274   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
275 
276   if (!batch->cancel_stream) {
277     // TODO(hcaseyal): move this to init_call_elem once issue #15927 is
278     // resolved.
279     GPR_ASSERT(batch->payload->context != nullptr);
280     if (batch->payload->context[GRPC_CONTEXT_SECURITY].value == nullptr) {
281       batch->payload->context[GRPC_CONTEXT_SECURITY].value =
282           grpc_client_security_context_create(calld->arena);
283       batch->payload->context[GRPC_CONTEXT_SECURITY].destroy =
284           grpc_client_security_context_destroy;
285     }
286     grpc_client_security_context* sec_ctx =
287         static_cast<grpc_client_security_context*>(
288             batch->payload->context[GRPC_CONTEXT_SECURITY].value);
289     GRPC_AUTH_CONTEXT_UNREF(sec_ctx->auth_context, "client auth filter");
290     sec_ctx->auth_context =
291         GRPC_AUTH_CONTEXT_REF(chand->auth_context, "client_auth_filter");
292   }
293 
294   if (batch->send_initial_metadata) {
295     grpc_metadata_batch* metadata =
296         batch->payload->send_initial_metadata.send_initial_metadata;
297     if (metadata->idx.named.path != nullptr) {
298       calld->method =
299           grpc_slice_ref_internal(GRPC_MDVALUE(metadata->idx.named.path->md));
300     }
301     if (metadata->idx.named.authority != nullptr) {
302       calld->host = grpc_slice_ref_internal(
303           GRPC_MDVALUE(metadata->idx.named.authority->md));
304       batch->handler_private.extra_arg = elem;
305       GRPC_CALL_STACK_REF(calld->owning_call, "check_call_host");
306       GRPC_CLOSURE_INIT(&calld->async_result_closure, on_host_checked, batch,
307                         grpc_schedule_on_exec_ctx);
308       char* call_host = grpc_slice_to_c_string(calld->host);
309       grpc_error* error = GRPC_ERROR_NONE;
310       if (grpc_channel_security_connector_check_call_host(
311               chand->security_connector, call_host, chand->auth_context,
312               &calld->async_result_closure, &error)) {
313         // Synchronous return; invoke on_host_checked() directly.
314         on_host_checked(batch, error);
315         GRPC_ERROR_UNREF(error);
316       } else {
317         // Async return; register cancellation closure with call combiner.
318         grpc_call_combiner_set_notify_on_cancel(
319             calld->call_combiner,
320             GRPC_CLOSURE_INIT(&calld->check_call_host_cancel_closure,
321                               cancel_check_call_host, elem,
322                               grpc_schedule_on_exec_ctx));
323       }
324       gpr_free(call_host);
325       return; /* early exit */
326     }
327   }
328 
329   /* pass control down the stack */
330   grpc_call_next_op(elem, batch);
331 }
332 
333 /* Constructor for call_data */
init_call_elem(grpc_call_element * elem,const grpc_call_element_args * args)334 static grpc_error* init_call_elem(grpc_call_element* elem,
335                                   const grpc_call_element_args* args) {
336   call_data* calld = static_cast<call_data*>(elem->call_data);
337   calld->arena = args->arena;
338   calld->owning_call = args->call_stack;
339   calld->call_combiner = args->call_combiner;
340   calld->host = grpc_empty_slice();
341   calld->method = grpc_empty_slice();
342   return GRPC_ERROR_NONE;
343 }
344 
set_pollset_or_pollset_set(grpc_call_element * elem,grpc_polling_entity * pollent)345 static void set_pollset_or_pollset_set(grpc_call_element* elem,
346                                        grpc_polling_entity* pollent) {
347   call_data* calld = static_cast<call_data*>(elem->call_data);
348   calld->pollent = pollent;
349 }
350 
351 /* Destructor for call_data */
destroy_call_elem(grpc_call_element * elem,const grpc_call_final_info * final_info,grpc_closure * ignored)352 static void destroy_call_elem(grpc_call_element* elem,
353                               const grpc_call_final_info* final_info,
354                               grpc_closure* ignored) {
355   call_data* calld = static_cast<call_data*>(elem->call_data);
356   grpc_credentials_mdelem_array_destroy(&calld->md_array);
357   grpc_call_credentials_unref(calld->creds);
358   grpc_slice_unref_internal(calld->host);
359   grpc_slice_unref_internal(calld->method);
360   grpc_auth_metadata_context_reset(&calld->auth_md_context);
361 }
362 
363 /* Constructor for channel_data */
init_channel_elem(grpc_channel_element * elem,grpc_channel_element_args * args)364 static grpc_error* init_channel_elem(grpc_channel_element* elem,
365                                      grpc_channel_element_args* args) {
366   grpc_security_connector* sc =
367       grpc_security_connector_find_in_args(args->channel_args);
368   if (sc == nullptr) {
369     return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
370         "Security connector missing from client auth filter args");
371   }
372   grpc_auth_context* auth_context =
373       grpc_find_auth_context_in_args(args->channel_args);
374   if (auth_context == nullptr) {
375     return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
376         "Auth context missing from client auth filter args");
377   }
378 
379   /* grab pointers to our data from the channel element */
380   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
381 
382   /* The first and the last filters tend to be implemented differently to
383      handle the case that there's no 'next' filter to call on the up or down
384      path */
385   GPR_ASSERT(!args->is_last);
386 
387   /* initialize members */
388   chand->security_connector =
389       reinterpret_cast<grpc_channel_security_connector*>(
390           GRPC_SECURITY_CONNECTOR_REF(sc, "client_auth_filter"));
391   chand->auth_context =
392       GRPC_AUTH_CONTEXT_REF(auth_context, "client_auth_filter");
393   return GRPC_ERROR_NONE;
394 }
395 
396 /* Destructor for channel data */
destroy_channel_elem(grpc_channel_element * elem)397 static void destroy_channel_elem(grpc_channel_element* elem) {
398   /* grab pointers to our data from the channel element */
399   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
400   grpc_channel_security_connector* sc = chand->security_connector;
401   if (sc != nullptr) {
402     GRPC_SECURITY_CONNECTOR_UNREF(&sc->base, "client_auth_filter");
403   }
404   GRPC_AUTH_CONTEXT_UNREF(chand->auth_context, "client_auth_filter");
405 }
406 
407 const grpc_channel_filter grpc_client_auth_filter = {
408     auth_start_transport_stream_op_batch,
409     grpc_channel_next_op,
410     sizeof(call_data),
411     init_call_elem,
412     set_pollset_or_pollset_set,
413     destroy_call_elem,
414     sizeof(channel_data),
415     init_channel_elem,
416     destroy_channel_elem,
417     grpc_channel_next_get_info,
418     "client-auth"};
419