| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 | /* * * Copyright 2015, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * *     * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. *     * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. *     * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */#include "src/core/security/auth.h"#include <string.h>#include <grpc/support/alloc.h>#include <grpc/support/log.h>#include "src/core/support/string.h"#include "src/core/channel/channel_stack.h"#include "src/core/security/security_connector.h"#include "src/core/security/credentials.h"#include "src/core/surface/call.h"#define MAX_CREDENTIALS_METADATA_COUNT 4/* We can have a per-call credentials. */typedef struct {  grpc_credentials *creds;  grpc_mdstr *host;  grpc_mdstr *method;  grpc_call_op op;  grpc_linked_mdelem md_links[MAX_CREDENTIALS_METADATA_COUNT];} call_data;/* We can have a per-channel credentials. */typedef struct {  grpc_channel_security_connector *security_connector;  grpc_mdctx *md_ctx;  grpc_mdstr *authority_string;  grpc_mdstr *path_string;  grpc_mdstr *error_msg_key;  grpc_mdstr *status_key;} channel_data;static void bubbleup_error(grpc_call_element *elem, const char *error_msg) {  grpc_call_element_recv_status(elem, GRPC_STATUS_UNAUTHENTICATED, error_msg);  grpc_call_element_send_cancel(elem);}static void on_credentials_metadata(void *user_data, grpc_mdelem **md_elems,                                    size_t num_md,                                    grpc_credentials_status status) {  grpc_call_element *elem = (grpc_call_element *)user_data;  call_data *calld = elem->call_data;  grpc_call_op op = calld->op;  size_t i;  GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT);  for (i = 0; i < num_md; i++) {    grpc_metadata_batch_add_tail(&op.data.metadata, &calld->md_links[i],                                 grpc_mdelem_ref(md_elems[i]));  }  grpc_call_next_op(elem, &op);}static char *build_service_url(const char *url_scheme, call_data *calld) {  char *service_url;  char *service = gpr_strdup(grpc_mdstr_as_c_string(calld->method));  char *last_slash = strrchr(service, '/');  if (last_slash == NULL) {    gpr_log(GPR_ERROR, "No '/' found in fully qualified method name");    service[0] = '\0';  } else if (last_slash == service) {    /* No service part in fully qualified method name: will just be "/". */    service[1] = '\0';  } else {    *last_slash = '\0';  }  if (url_scheme == NULL) url_scheme = "";  gpr_asprintf(&service_url, "%s://%s%s", url_scheme,               grpc_mdstr_as_c_string(calld->host), service);  gpr_free(service);  return service_url;}static void send_security_metadata(grpc_call_element *elem, grpc_call_op *op) {  /* grab pointers to our data from the call element */  call_data *calld = elem->call_data;  channel_data *channeld = elem->channel_data;  grpc_credentials *channel_creds =      channeld->security_connector->request_metadata_creds;  /* TODO(jboeuf):     Decide on the policy in this case:     - populate both channel and call?     - the call takes precedence over the channel?     - leave this decision up to the channel credentials?  */  if (calld->creds != NULL) {    gpr_log(GPR_ERROR, "Ignoring per call credentials for now.");  }  if (channel_creds != NULL &&      grpc_credentials_has_request_metadata(channel_creds)) {    char *service_url =        build_service_url(channeld->security_connector->base.url_scheme, calld);    calld->op = *op; /* Copy op (originates from the caller's stack). */    grpc_credentials_get_request_metadata(channel_creds, service_url,                                          on_credentials_metadata, elem);    gpr_free(service_url);  } else {    grpc_call_next_op(elem, op);  }}static void on_host_checked(void *user_data, grpc_security_status status) {  grpc_call_element *elem = (grpc_call_element *)user_data;  call_data *calld = elem->call_data;  if (status == GRPC_SECURITY_OK) {    send_security_metadata(elem, &calld->op);  } else {    char *error_msg;    gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.",                 grpc_mdstr_as_c_string(calld->host));    bubbleup_error(elem, error_msg);    grpc_metadata_batch_destroy(&calld->op.data.metadata);    gpr_free(error_msg);    calld->op.done_cb(calld->op.user_data, GRPC_OP_ERROR);  }}/* Called either:     - in response to an API call (or similar) from above, to send something     - a network event (or similar) from below, to receive something   op contains type and call direction information, in addition to the data   that is being sent or received. */static void call_op(grpc_call_element *elem, grpc_call_element *from_elem,                    grpc_call_op *op) {  /* grab pointers to our data from the call element */  call_data *calld = elem->call_data;  channel_data *channeld = elem->channel_data;  grpc_linked_mdelem *l;  switch (op->type) {    case GRPC_SEND_METADATA:      for (l = op->data.metadata.list.head; l != NULL; l = l->next) {        grpc_mdelem *md = l->md;        /* Pointer comparison is OK for md_elems created from the same context.         */        if (md->key == channeld->authority_string) {          if (calld->host != NULL) grpc_mdstr_unref(calld->host);          calld->host = grpc_mdstr_ref(md->value);        } else if (md->key == channeld->path_string) {          if (calld->method != NULL) grpc_mdstr_unref(calld->method);          calld->method = grpc_mdstr_ref(md->value);        }      }      if (calld->host != NULL) {        grpc_security_status status;        const char *call_host = grpc_mdstr_as_c_string(calld->host);        calld->op = *op; /* Copy op (originates from the caller's stack). */        status = grpc_channel_security_connector_check_call_host(            channeld->security_connector, call_host, on_host_checked, elem);        if (status != GRPC_SECURITY_OK) {          if (status == GRPC_SECURITY_ERROR) {            char *error_msg;            gpr_asprintf(&error_msg,                         "Invalid host %s set in :authority metadata.",                         call_host);            bubbleup_error(elem, error_msg);            grpc_metadata_batch_destroy(&calld->op.data.metadata);            gpr_free(error_msg);            op->done_cb(op->user_data, GRPC_OP_ERROR);          }          break;        }      }      send_security_metadata(elem, op);      break;    default:      /* pass control up or down the stack depending on op->dir */      grpc_call_next_op(elem, op);      break;  }}/* Called on special channel events, such as disconnection or new incoming   calls on the server */static void channel_op(grpc_channel_element *elem,                       grpc_channel_element *from_elem, grpc_channel_op *op) {  grpc_channel_next_op(elem, op);}/* Constructor for call_data */static void init_call_elem(grpc_call_element *elem,                           const void *server_transport_data) {  /* TODO(jboeuf):     Find a way to pass-in the credentials from the caller here.  */  call_data *calld = elem->call_data;  calld->creds = NULL;  calld->host = NULL;  calld->method = NULL;}/* Destructor for call_data */static void destroy_call_elem(grpc_call_element *elem) {  call_data *calld = elem->call_data;  if (calld->creds != NULL) {    grpc_credentials_unref(calld->creds);  }  if (calld->host != NULL) {    grpc_mdstr_unref(calld->host);  }  if (calld->method != NULL) {    grpc_mdstr_unref(calld->method);  }}/* Constructor for channel_data */static void init_channel_elem(grpc_channel_element *elem,                              const grpc_channel_args *args,                              grpc_mdctx *metadata_context, int is_first,                              int is_last) {  grpc_security_connector *ctx = grpc_find_security_connector_in_args(args);  /* grab pointers to our data from the channel element */  channel_data *channeld = elem->channel_data;  /* The first and the last filters tend to be implemented differently to     handle the case that there's no 'next' filter to call on the up or down     path */  GPR_ASSERT(!is_first);  GPR_ASSERT(!is_last);  GPR_ASSERT(ctx != NULL);  /* initialize members */  GPR_ASSERT(ctx->is_client_side);  channeld->security_connector =      (grpc_channel_security_connector *)grpc_security_connector_ref(ctx);  channeld->md_ctx = metadata_context;  channeld->authority_string =      grpc_mdstr_from_string(channeld->md_ctx, ":authority");  channeld->path_string = grpc_mdstr_from_string(channeld->md_ctx, ":path");  channeld->error_msg_key =      grpc_mdstr_from_string(channeld->md_ctx, "grpc-message");  channeld->status_key =      grpc_mdstr_from_string(channeld->md_ctx, "grpc-status");}/* Destructor for channel data */static void destroy_channel_elem(grpc_channel_element *elem) {  /* grab pointers to our data from the channel element */  channel_data *channeld = elem->channel_data;  grpc_channel_security_connector *ctx = channeld->security_connector;  if (ctx != NULL) grpc_security_connector_unref(&ctx->base);  if (channeld->authority_string != NULL) {    grpc_mdstr_unref(channeld->authority_string);  }  if (channeld->error_msg_key != NULL) {    grpc_mdstr_unref(channeld->error_msg_key);  }  if (channeld->status_key != NULL) {    grpc_mdstr_unref(channeld->status_key);  }  if (channeld->path_string != NULL) {    grpc_mdstr_unref(channeld->path_string);  }}const grpc_channel_filter grpc_client_auth_filter = {    call_op, channel_op, sizeof(call_data), init_call_elem, destroy_call_elem,    sizeof(channel_data), init_channel_elem, destroy_channel_elem, "auth"};
 |