Imported Upstream version 1.41.0
[platform/upstream/grpc.git] / src / core / lib / security / transport / client_auth_filter.cc
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 <string.h>
22
23 #include <string>
24
25 #include "absl/strings/str_cat.h"
26
27 #include <grpc/support/alloc.h>
28 #include <grpc/support/log.h>
29 #include <grpc/support/string_util.h>
30
31 #include "src/core/lib/channel/channel_stack.h"
32 #include "src/core/lib/gpr/string.h"
33 #include "src/core/lib/profiling/timers.h"
34 #include "src/core/lib/security/context/security_context.h"
35 #include "src/core/lib/security/credentials/credentials.h"
36 #include "src/core/lib/security/security_connector/security_connector.h"
37 #include "src/core/lib/security/security_connector/ssl_utils.h"
38 #include "src/core/lib/security/transport/auth_filters.h"
39 #include "src/core/lib/slice/slice_internal.h"
40 #include "src/core/lib/slice/slice_string_helpers.h"
41 #include "src/core/lib/surface/call.h"
42 #include "src/core/lib/transport/static_metadata.h"
43
44 #define MAX_CREDENTIALS_METADATA_COUNT 4
45
46 namespace {
47
48 /* We can have a per-channel credentials. */
49 struct channel_data {
50   channel_data(grpc_channel_security_connector* security_connector,
51                grpc_auth_context* auth_context)
52       : security_connector(
53             security_connector->Ref(DEBUG_LOCATION, "client_auth_filter")),
54         auth_context(auth_context->Ref(DEBUG_LOCATION, "client_auth_filter")) {}
55   ~channel_data() {
56     security_connector.reset(DEBUG_LOCATION, "client_auth_filter");
57     auth_context.reset(DEBUG_LOCATION, "client_auth_filter");
58   }
59
60   grpc_core::RefCountedPtr<grpc_channel_security_connector> security_connector;
61   grpc_core::RefCountedPtr<grpc_auth_context> auth_context;
62 };
63
64 /* We can have a per-call credentials. */
65 struct call_data {
66   call_data(grpc_call_element* elem, const grpc_call_element_args& args)
67       : owning_call(args.call_stack), call_combiner(args.call_combiner) {
68     channel_data* chand = static_cast<channel_data*>(elem->channel_data);
69     GPR_ASSERT(args.context != nullptr);
70     if (args.context[GRPC_CONTEXT_SECURITY].value == nullptr) {
71       args.context[GRPC_CONTEXT_SECURITY].value =
72           grpc_client_security_context_create(args.arena, /*creds=*/nullptr);
73       args.context[GRPC_CONTEXT_SECURITY].destroy =
74           grpc_client_security_context_destroy;
75     }
76     grpc_client_security_context* sec_ctx =
77         static_cast<grpc_client_security_context*>(
78             args.context[GRPC_CONTEXT_SECURITY].value);
79     sec_ctx->auth_context.reset(DEBUG_LOCATION, "client_auth_filter");
80     sec_ctx->auth_context =
81         chand->auth_context->Ref(DEBUG_LOCATION, "client_auth_filter");
82   }
83
84   // This method is technically the dtor of this class. However, since
85   // `get_request_metadata_cancel_closure` can run in parallel to
86   // `destroy_call_elem`, we cannot call the dtor in them. Otherwise,
87   // fields will be accessed after calling dtor, and msan correctly complains
88   // that the memory is not initialized.
89   void destroy() {
90     grpc_credentials_mdelem_array_destroy(&md_array);
91     creds.reset();
92     grpc_slice_unref_internal(host);
93     grpc_slice_unref_internal(method);
94     grpc_auth_metadata_context_reset(&auth_md_context);
95   }
96
97   grpc_call_stack* owning_call;
98   grpc_core::CallCombiner* call_combiner;
99   grpc_core::RefCountedPtr<grpc_call_credentials> creds;
100   grpc_slice host = grpc_empty_slice();
101   grpc_slice method = grpc_empty_slice();
102   /* pollset{_set} bound to this call; if we need to make external
103      network requests, they should be done under a pollset added to this
104      pollset_set so that work can progress when this call wants work to progress
105   */
106   grpc_polling_entity* pollent = nullptr;
107   grpc_credentials_mdelem_array md_array;
108   grpc_linked_mdelem md_links[MAX_CREDENTIALS_METADATA_COUNT] = {};
109   grpc_auth_metadata_context auth_md_context =
110       grpc_auth_metadata_context();  // Zero-initialize the C struct.
111   grpc_closure async_result_closure;
112   grpc_closure check_call_host_cancel_closure;
113   grpc_closure get_request_metadata_cancel_closure;
114 };
115
116 }  // namespace
117
118 void grpc_auth_metadata_context_copy(grpc_auth_metadata_context* from,
119                                      grpc_auth_metadata_context* to) {
120   grpc_auth_metadata_context_reset(to);
121   to->channel_auth_context = from->channel_auth_context;
122   if (to->channel_auth_context != nullptr) {
123     const_cast<grpc_auth_context*>(to->channel_auth_context)
124         ->Ref(DEBUG_LOCATION, "grpc_auth_metadata_context_copy")
125         .release();
126   }
127   to->service_url = gpr_strdup(from->service_url);
128   to->method_name = gpr_strdup(from->method_name);
129 }
130
131 void grpc_auth_metadata_context_reset(
132     grpc_auth_metadata_context* auth_md_context) {
133   if (auth_md_context->service_url != nullptr) {
134     gpr_free(const_cast<char*>(auth_md_context->service_url));
135     auth_md_context->service_url = nullptr;
136   }
137   if (auth_md_context->method_name != nullptr) {
138     gpr_free(const_cast<char*>(auth_md_context->method_name));
139     auth_md_context->method_name = nullptr;
140   }
141   if (auth_md_context->channel_auth_context != nullptr) {
142     const_cast<grpc_auth_context*>(auth_md_context->channel_auth_context)
143         ->Unref(DEBUG_LOCATION, "grpc_auth_metadata_context");
144     auth_md_context->channel_auth_context = nullptr;
145   }
146 }
147
148 static void add_error(grpc_error_handle* combined, grpc_error_handle error) {
149   if (error == GRPC_ERROR_NONE) return;
150   if (*combined == GRPC_ERROR_NONE) {
151     *combined = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
152         "Client auth metadata plugin error");
153   }
154   *combined = grpc_error_add_child(*combined, error);
155 }
156
157 static void on_credentials_metadata(void* arg, grpc_error_handle input_error) {
158   grpc_transport_stream_op_batch* batch =
159       static_cast<grpc_transport_stream_op_batch*>(arg);
160   grpc_call_element* elem =
161       static_cast<grpc_call_element*>(batch->handler_private.extra_arg);
162   call_data* calld = static_cast<call_data*>(elem->call_data);
163   grpc_auth_metadata_context_reset(&calld->auth_md_context);
164   grpc_error_handle error = GRPC_ERROR_REF(input_error);
165   if (error == GRPC_ERROR_NONE) {
166     GPR_ASSERT(calld->md_array.size <= MAX_CREDENTIALS_METADATA_COUNT);
167     GPR_ASSERT(batch->send_initial_metadata);
168     grpc_metadata_batch* mdb =
169         batch->payload->send_initial_metadata.send_initial_metadata;
170     for (size_t i = 0; i < calld->md_array.size; ++i) {
171       add_error(&error, grpc_metadata_batch_add_tail(
172                             mdb, &calld->md_links[i],
173                             GRPC_MDELEM_REF(calld->md_array.md[i])));
174     }
175   }
176   if (error == GRPC_ERROR_NONE) {
177     grpc_call_next_op(elem, batch);
178   } else {
179     error = grpc_error_set_int(error, GRPC_ERROR_INT_GRPC_STATUS,
180                                GRPC_STATUS_UNAVAILABLE);
181     grpc_transport_stream_op_batch_finish_with_failure(batch, error,
182                                                        calld->call_combiner);
183   }
184   GRPC_CALL_STACK_UNREF(calld->owning_call, "get_request_metadata");
185 }
186
187 void grpc_auth_metadata_context_build(
188     const char* url_scheme, const grpc_slice& call_host,
189     const grpc_slice& call_method, grpc_auth_context* auth_context,
190     grpc_auth_metadata_context* auth_md_context) {
191   char* service = grpc_slice_to_c_string(call_method);
192   char* last_slash = strrchr(service, '/');
193   char* method_name = nullptr;
194   char* service_url = nullptr;
195   grpc_auth_metadata_context_reset(auth_md_context);
196   if (last_slash == nullptr) {
197     gpr_log(GPR_ERROR, "No '/' found in fully qualified method name");
198     service[0] = '\0';
199     method_name = gpr_strdup("");
200   } else if (last_slash == service) {
201     method_name = gpr_strdup("");
202   } else {
203     *last_slash = '\0';
204     method_name = gpr_strdup(last_slash + 1);
205   }
206   char* host_and_port = grpc_slice_to_c_string(call_host);
207   if (url_scheme != nullptr && strcmp(url_scheme, GRPC_SSL_URL_SCHEME) == 0) {
208     /* Remove the port if it is 443. */
209     char* port_delimiter = strrchr(host_and_port, ':');
210     if (port_delimiter != nullptr && strcmp(port_delimiter + 1, "443") == 0) {
211       *port_delimiter = '\0';
212     }
213   }
214   gpr_asprintf(&service_url, "%s://%s%s",
215                url_scheme == nullptr ? "" : url_scheme, host_and_port, service);
216   auth_md_context->service_url = service_url;
217   auth_md_context->method_name = method_name;
218   auth_md_context->channel_auth_context =
219       auth_context == nullptr
220           ? nullptr
221           : auth_context->Ref(DEBUG_LOCATION, "grpc_auth_metadata_context")
222                 .release();
223   gpr_free(service);
224   gpr_free(host_and_port);
225 }
226
227 static void cancel_get_request_metadata(void* arg, grpc_error_handle error) {
228   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
229   call_data* calld = static_cast<call_data*>(elem->call_data);
230   if (error != GRPC_ERROR_NONE) {
231     calld->creds->cancel_get_request_metadata(&calld->md_array,
232                                               GRPC_ERROR_REF(error));
233   }
234   GRPC_CALL_STACK_UNREF(calld->owning_call, "cancel_get_request_metadata");
235 }
236
237 static void send_security_metadata(grpc_call_element* elem,
238                                    grpc_transport_stream_op_batch* batch) {
239   call_data* calld = static_cast<call_data*>(elem->call_data);
240   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
241   grpc_client_security_context* ctx =
242       static_cast<grpc_client_security_context*>(
243           batch->payload->context[GRPC_CONTEXT_SECURITY].value);
244   grpc_call_credentials* channel_call_creds =
245       chand->security_connector->mutable_request_metadata_creds();
246   int call_creds_has_md = (ctx != nullptr) && (ctx->creds != nullptr);
247
248   if (channel_call_creds == nullptr && !call_creds_has_md) {
249     /* Skip sending metadata altogether. */
250     grpc_call_next_op(elem, batch);
251     return;
252   }
253
254   if (channel_call_creds != nullptr && call_creds_has_md) {
255     calld->creds = grpc_core::RefCountedPtr<grpc_call_credentials>(
256         grpc_composite_call_credentials_create(channel_call_creds,
257                                                ctx->creds.get(), nullptr));
258     if (calld->creds == nullptr) {
259       grpc_transport_stream_op_batch_finish_with_failure(
260           batch,
261           grpc_error_set_int(
262               GRPC_ERROR_CREATE_FROM_STATIC_STRING(
263                   "Incompatible credentials set on channel and call."),
264               GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAUTHENTICATED),
265           calld->call_combiner);
266       return;
267     }
268   } else {
269     calld->creds =
270         call_creds_has_md ? ctx->creds->Ref() : channel_call_creds->Ref();
271   }
272
273   /* Check security level of call credential and channel, and do not send
274    * metadata if the check fails. */
275   grpc_auth_property_iterator it = grpc_auth_context_find_properties_by_name(
276       chand->auth_context.get(), GRPC_TRANSPORT_SECURITY_LEVEL_PROPERTY_NAME);
277   const grpc_auth_property* prop = grpc_auth_property_iterator_next(&it);
278   if (prop == nullptr) {
279     grpc_transport_stream_op_batch_finish_with_failure(
280         batch,
281         grpc_error_set_int(
282             GRPC_ERROR_CREATE_FROM_STATIC_STRING(
283                 "Established channel does not have an auth property "
284                 "representing a security level."),
285             GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAUTHENTICATED),
286         calld->call_combiner);
287     return;
288   }
289   grpc_security_level call_cred_security_level =
290       calld->creds->min_security_level();
291   int is_security_level_ok = grpc_check_security_level(
292       grpc_tsi_security_level_string_to_enum(prop->value),
293       call_cred_security_level);
294   if (!is_security_level_ok) {
295     grpc_transport_stream_op_batch_finish_with_failure(
296         batch,
297         grpc_error_set_int(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
298                                "Established channel does not have a sufficient "
299                                "security level to transfer call credential."),
300                            GRPC_ERROR_INT_GRPC_STATUS,
301                            GRPC_STATUS_UNAUTHENTICATED),
302         calld->call_combiner);
303     return;
304   }
305
306   grpc_auth_metadata_context_build(
307       chand->security_connector->url_scheme(), calld->host, calld->method,
308       chand->auth_context.get(), &calld->auth_md_context);
309
310   GPR_ASSERT(calld->pollent != nullptr);
311   GRPC_CALL_STACK_REF(calld->owning_call, "get_request_metadata");
312   GRPC_CLOSURE_INIT(&calld->async_result_closure, on_credentials_metadata,
313                     batch, grpc_schedule_on_exec_ctx);
314   grpc_error_handle error = GRPC_ERROR_NONE;
315   if (calld->creds->get_request_metadata(
316           calld->pollent, calld->auth_md_context, &calld->md_array,
317           &calld->async_result_closure, &error)) {
318     // Synchronous return; invoke on_credentials_metadata() directly.
319     on_credentials_metadata(batch, error);
320     GRPC_ERROR_UNREF(error);
321   } else {
322     // Async return; register cancellation closure with call combiner.
323     // TODO(yashykt): We would not need this ref if call combiners used
324     // Closure::Run() instead of ExecCtx::Run()
325     GRPC_CALL_STACK_REF(calld->owning_call, "cancel_get_request_metadata");
326     calld->call_combiner->SetNotifyOnCancel(GRPC_CLOSURE_INIT(
327         &calld->get_request_metadata_cancel_closure,
328         cancel_get_request_metadata, elem, grpc_schedule_on_exec_ctx));
329   }
330 }
331
332 static void on_host_checked(void* arg, grpc_error_handle error) {
333   grpc_transport_stream_op_batch* batch =
334       static_cast<grpc_transport_stream_op_batch*>(arg);
335   grpc_call_element* elem =
336       static_cast<grpc_call_element*>(batch->handler_private.extra_arg);
337   call_data* calld = static_cast<call_data*>(elem->call_data);
338   if (error == GRPC_ERROR_NONE) {
339     send_security_metadata(elem, batch);
340   } else {
341     grpc_transport_stream_op_batch_finish_with_failure(
342         batch,
343         grpc_error_set_int(
344             GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat(
345                 "Invalid host ", grpc_core::StringViewFromSlice(calld->host),
346                 " set in :authority metadata.")),
347             GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_UNAUTHENTICATED),
348         calld->call_combiner);
349   }
350   GRPC_CALL_STACK_UNREF(calld->owning_call, "check_call_host");
351 }
352
353 static void cancel_check_call_host(void* arg, grpc_error_handle error) {
354   grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
355   call_data* calld = static_cast<call_data*>(elem->call_data);
356   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
357   if (error != GRPC_ERROR_NONE) {
358     chand->security_connector->cancel_check_call_host(
359         &calld->async_result_closure, GRPC_ERROR_REF(error));
360   }
361   GRPC_CALL_STACK_UNREF(calld->owning_call, "cancel_check_call_host");
362 }
363
364 static void client_auth_start_transport_stream_op_batch(
365     grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
366   GPR_TIMER_SCOPE("auth_start_transport_stream_op_batch", 0);
367
368   /* grab pointers to our data from the call element */
369   call_data* calld = static_cast<call_data*>(elem->call_data);
370   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
371
372   if (batch->send_initial_metadata) {
373     grpc_metadata_batch* metadata =
374         batch->payload->send_initial_metadata.send_initial_metadata;
375     if (metadata->idx.named.path != nullptr) {
376       calld->method =
377           grpc_slice_ref_internal(GRPC_MDVALUE(metadata->idx.named.path->md));
378     }
379     if (metadata->idx.named.authority != nullptr) {
380       calld->host = grpc_slice_ref_internal(
381           GRPC_MDVALUE(metadata->idx.named.authority->md));
382       batch->handler_private.extra_arg = elem;
383       GRPC_CALL_STACK_REF(calld->owning_call, "check_call_host");
384       GRPC_CLOSURE_INIT(&calld->async_result_closure, on_host_checked, batch,
385                         grpc_schedule_on_exec_ctx);
386       absl::string_view call_host(grpc_core::StringViewFromSlice(calld->host));
387       grpc_error_handle error = GRPC_ERROR_NONE;
388       if (chand->security_connector->check_call_host(
389               call_host, chand->auth_context.get(),
390               &calld->async_result_closure, &error)) {
391         // Synchronous return; invoke on_host_checked() directly.
392         on_host_checked(batch, error);
393         GRPC_ERROR_UNREF(error);
394       } else {
395         // Async return; register cancellation closure with call combiner.
396         // TODO(yashykt): We would not need this ref if call combiners used
397         // Closure::Run() instead of ExecCtx::Run()
398         GRPC_CALL_STACK_REF(calld->owning_call, "cancel_check_call_host");
399         calld->call_combiner->SetNotifyOnCancel(GRPC_CLOSURE_INIT(
400             &calld->check_call_host_cancel_closure, cancel_check_call_host,
401             elem, grpc_schedule_on_exec_ctx));
402       }
403       return; /* early exit */
404     }
405   }
406
407   /* pass control down the stack */
408   grpc_call_next_op(elem, batch);
409 }
410
411 /* Constructor for call_data */
412 static grpc_error_handle client_auth_init_call_elem(
413     grpc_call_element* elem, const grpc_call_element_args* args) {
414   new (elem->call_data) call_data(elem, *args);
415   return GRPC_ERROR_NONE;
416 }
417
418 static void client_auth_set_pollset_or_pollset_set(
419     grpc_call_element* elem, grpc_polling_entity* pollent) {
420   call_data* calld = static_cast<call_data*>(elem->call_data);
421   calld->pollent = pollent;
422 }
423
424 /* Destructor for call_data */
425 static void client_auth_destroy_call_elem(
426     grpc_call_element* elem, const grpc_call_final_info* /*final_info*/,
427     grpc_closure* /*ignored*/) {
428   call_data* calld = static_cast<call_data*>(elem->call_data);
429   calld->destroy();
430 }
431
432 /* Constructor for channel_data */
433 static grpc_error_handle client_auth_init_channel_elem(
434     grpc_channel_element* elem, grpc_channel_element_args* args) {
435   /* The first and the last filters tend to be implemented differently to
436      handle the case that there's no 'next' filter to call on the up or down
437      path */
438   GPR_ASSERT(!args->is_last);
439   grpc_security_connector* sc =
440       grpc_security_connector_find_in_args(args->channel_args);
441   if (sc == nullptr) {
442     return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
443         "Security connector missing from client auth filter args");
444   }
445   grpc_auth_context* auth_context =
446       grpc_find_auth_context_in_args(args->channel_args);
447   if (auth_context == nullptr) {
448     return GRPC_ERROR_CREATE_FROM_STATIC_STRING(
449         "Auth context missing from client auth filter args");
450   }
451   new (elem->channel_data) channel_data(
452       static_cast<grpc_channel_security_connector*>(sc), auth_context);
453   return GRPC_ERROR_NONE;
454 }
455
456 /* Destructor for channel data */
457 static void client_auth_destroy_channel_elem(grpc_channel_element* elem) {
458   channel_data* chand = static_cast<channel_data*>(elem->channel_data);
459   chand->~channel_data();
460 }
461
462 const grpc_channel_filter grpc_client_auth_filter = {
463     client_auth_start_transport_stream_op_batch,
464     grpc_channel_next_op,
465     sizeof(call_data),
466     client_auth_init_call_elem,
467     client_auth_set_pollset_or_pollset_set,
468     client_auth_destroy_call_elem,
469     sizeof(channel_data),
470     client_auth_init_channel_elem,
471     client_auth_destroy_channel_elem,
472     grpc_channel_next_get_info,
473     "client-auth"};