LCOV - code coverage report
Current view: top level - src/core/security - client_auth_filter.c (source / functions) Hit Total Coverage
Test: tmp.zDYK9MVh93 Lines: 157 167 94.0 %
Date: 2015-10-10 Functions: 11 11 100.0 %

          Line data    Source code
       1             : /*
       2             :  *
       3             :  * Copyright 2015, Google Inc.
       4             :  * All rights reserved.
       5             :  *
       6             :  * Redistribution and use in source and binary forms, with or without
       7             :  * modification, are permitted provided that the following conditions are
       8             :  * met:
       9             :  *
      10             :  *     * Redistributions of source code must retain the above copyright
      11             :  * notice, this list of conditions and the following disclaimer.
      12             :  *     * Redistributions in binary form must reproduce the above
      13             :  * copyright notice, this list of conditions and the following disclaimer
      14             :  * in the documentation and/or other materials provided with the
      15             :  * distribution.
      16             :  *     * Neither the name of Google Inc. nor the names of its
      17             :  * contributors may be used to endorse or promote products derived from
      18             :  * this software without specific prior written permission.
      19             :  *
      20             :  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      21             :  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      22             :  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      23             :  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      24             :  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      25             :  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      26             :  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      27             :  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      28             :  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      29             :  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      30             :  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      31             :  *
      32             :  */
      33             : 
      34             : #include "src/core/security/auth_filters.h"
      35             : 
      36             : #include <string.h>
      37             : 
      38             : #include <grpc/support/alloc.h>
      39             : #include <grpc/support/log.h>
      40             : #include <grpc/support/string_util.h>
      41             : 
      42             : #include "src/core/support/string.h"
      43             : #include "src/core/channel/channel_stack.h"
      44             : #include "src/core/security/security_context.h"
      45             : #include "src/core/security/security_connector.h"
      46             : #include "src/core/security/credentials.h"
      47             : #include "src/core/surface/call.h"
      48             : 
      49             : #define MAX_CREDENTIALS_METADATA_COUNT 4
      50             : 
      51             : /* We can have a per-call credentials. */
      52             : typedef struct {
      53             :   grpc_credentials *creds;
      54             :   grpc_mdstr *host;
      55             :   grpc_mdstr *method;
      56             :   /* pollset bound to this call; if we need to make external
      57             :      network requests, they should be done under this pollset
      58             :      so that work can progress when this call wants work to
      59             :      progress */
      60             :   grpc_pollset *pollset;
      61             :   grpc_transport_stream_op op;
      62             :   size_t op_md_idx;
      63             :   int sent_initial_metadata;
      64             :   gpr_uint8 security_context_set;
      65             :   grpc_linked_mdelem md_links[MAX_CREDENTIALS_METADATA_COUNT];
      66             :   char *service_url;
      67             : } call_data;
      68             : 
      69             : /* We can have a per-channel credentials. */
      70             : typedef struct {
      71             :   grpc_channel_security_connector *security_connector;
      72             :   grpc_mdctx *md_ctx;
      73             :   grpc_mdstr *authority_string;
      74             :   grpc_mdstr *path_string;
      75             :   grpc_mdstr *error_msg_key;
      76             :   grpc_mdstr *status_key;
      77             : } channel_data;
      78             : 
      79        1127 : static void reset_service_url(call_data *calld) {
      80        1127 :   if (calld->service_url != NULL) {
      81         128 :     gpr_free(calld->service_url);
      82         128 :     calld->service_url = NULL;
      83             :   }
      84        1127 : }
      85             : 
      86           2 : static void bubble_up_error(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
      87             :                             grpc_status_code status, const char *error_msg) {
      88           2 :   call_data *calld = elem->call_data;
      89           2 :   gpr_log(GPR_ERROR, "Client side authentication failure: %s", error_msg);
      90           2 :   grpc_transport_stream_op_add_cancellation(&calld->op, status);
      91           2 :   grpc_call_next_op(exec_ctx, elem, &calld->op);
      92           2 : }
      93             : 
      94         128 : static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data,
      95             :                                     grpc_credentials_md *md_elems,
      96             :                                     size_t num_md,
      97             :                                     grpc_credentials_status status) {
      98         128 :   grpc_call_element *elem = (grpc_call_element *)user_data;
      99         128 :   call_data *calld = elem->call_data;
     100         128 :   channel_data *chand = elem->channel_data;
     101         128 :   grpc_transport_stream_op *op = &calld->op;
     102             :   grpc_metadata_batch *mdb;
     103             :   size_t i;
     104         128 :   reset_service_url(calld);
     105         128 :   if (status != GRPC_CREDENTIALS_OK) {
     106           2 :     bubble_up_error(exec_ctx, elem, GRPC_STATUS_UNAUTHENTICATED,
     107             :                     "Credentials failed to get metadata.");
     108         130 :     return;
     109             :   }
     110         126 :   GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT);
     111         126 :   GPR_ASSERT(op->send_ops && op->send_ops->nops > calld->op_md_idx &&
     112             :              op->send_ops->ops[calld->op_md_idx].type == GRPC_OP_METADATA);
     113         126 :   mdb = &op->send_ops->ops[calld->op_md_idx].data.metadata;
     114         272 :   for (i = 0; i < num_md; i++) {
     115         292 :     grpc_metadata_batch_add_tail(
     116             :         mdb, &calld->md_links[i],
     117         146 :         grpc_mdelem_from_slices(chand->md_ctx, gpr_slice_ref(md_elems[i].key),
     118         146 :                                 gpr_slice_ref(md_elems[i].value)));
     119             :   }
     120         126 :   grpc_call_next_op(exec_ctx, elem, op);
     121             : }
     122             : 
     123         128 : void build_service_url(const char *url_scheme, call_data *calld) {
     124         128 :   char *service = gpr_strdup(grpc_mdstr_as_c_string(calld->method));
     125         128 :   char *last_slash = strrchr(service, '/');
     126         128 :   if (last_slash == NULL) {
     127           0 :     gpr_log(GPR_ERROR, "No '/' found in fully qualified method name");
     128           0 :     service[0] = '\0';
     129         128 :   } else if (last_slash == service) {
     130             :     /* No service part in fully qualified method name: will just be "/". */
     131         120 :     service[1] = '\0';
     132             :   } else {
     133           8 :     *last_slash = '\0';
     134             :   }
     135         128 :   if (url_scheme == NULL) url_scheme = "";
     136         128 :   reset_service_url(calld);
     137         128 :   gpr_asprintf(&calld->service_url, "%s://%s%s", url_scheme,
     138             :                grpc_mdstr_as_c_string(calld->host), service);
     139         128 :   gpr_free(service);
     140         128 : }
     141             : 
     142         871 : static void send_security_metadata(grpc_exec_ctx *exec_ctx,
     143             :                                    grpc_call_element *elem,
     144             :                                    grpc_transport_stream_op *op) {
     145         871 :   call_data *calld = elem->call_data;
     146         871 :   channel_data *chand = elem->channel_data;
     147         871 :   grpc_client_security_context *ctx =
     148         871 :       (grpc_client_security_context *)op->context[GRPC_CONTEXT_SECURITY].value;
     149         871 :   grpc_credentials *channel_creds =
     150         871 :       chand->security_connector->request_metadata_creds;
     151         871 :   int channel_creds_has_md =
     152         979 :       (channel_creds != NULL) &&
     153         108 :       grpc_credentials_has_request_metadata(channel_creds);
     154         894 :   int call_creds_has_md = (ctx != NULL) && (ctx->creds != NULL) &&
     155          23 :                           grpc_credentials_has_request_metadata(ctx->creds);
     156             : 
     157         871 :   if (!channel_creds_has_md && !call_creds_has_md) {
     158             :     /* Skip sending metadata altogether. */
     159         743 :     grpc_call_next_op(exec_ctx, elem, op);
     160         743 :     return;
     161             :   }
     162             : 
     163         128 :   if (channel_creds_has_md && call_creds_has_md) {
     164           3 :     calld->creds =
     165           3 :         grpc_composite_credentials_create(channel_creds, ctx->creds, NULL);
     166           6 :     if (calld->creds == NULL) {
     167           0 :       bubble_up_error(exec_ctx, elem, GRPC_STATUS_INVALID_ARGUMENT,
     168             :                       "Incompatible credentials set on channel and call.");
     169           0 :       return;
     170             :     }
     171             :   } else {
     172         125 :     calld->creds =
     173         125 :         grpc_credentials_ref(call_creds_has_md ? ctx->creds : channel_creds);
     174             :   }
     175             : 
     176         128 :   build_service_url(chand->security_connector->base.url_scheme, calld);
     177         128 :   calld->op = *op; /* Copy op (originates from the caller's stack). */
     178         128 :   GPR_ASSERT(calld->pollset);
     179         128 :   grpc_credentials_get_request_metadata(exec_ctx, calld->creds, calld->pollset,
     180         128 :                                         calld->service_url,
     181             :                                         on_credentials_metadata, elem);
     182             : }
     183             : 
     184         111 : static void on_host_checked(grpc_exec_ctx *exec_ctx, void *user_data,
     185             :                             grpc_security_status status) {
     186         111 :   grpc_call_element *elem = (grpc_call_element *)user_data;
     187         111 :   call_data *calld = elem->call_data;
     188             : 
     189         111 :   if (status == GRPC_SECURITY_OK) {
     190         111 :     send_security_metadata(exec_ctx, elem, &calld->op);
     191             :   } else {
     192             :     char *error_msg;
     193           0 :     gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.",
     194             :                  grpc_mdstr_as_c_string(calld->host));
     195           0 :     bubble_up_error(exec_ctx, elem, GRPC_STATUS_INVALID_ARGUMENT, error_msg);
     196           0 :     gpr_free(error_msg);
     197             :   }
     198         111 : }
     199             : 
     200             : /* Called either:
     201             :      - in response to an API call (or similar) from above, to send something
     202             :      - a network event (or similar) from below, to receive something
     203             :    op contains type and call direction information, in addition to the data
     204             :    that is being sent or received. */
     205        2118 : static void auth_start_transport_op(grpc_exec_ctx *exec_ctx,
     206             :                                     grpc_call_element *elem,
     207             :                                     grpc_transport_stream_op *op) {
     208             :   /* grab pointers to our data from the call element */
     209        2118 :   call_data *calld = elem->call_data;
     210        2118 :   channel_data *chand = elem->channel_data;
     211             :   grpc_linked_mdelem *l;
     212             :   size_t i;
     213        2118 :   grpc_client_security_context *sec_ctx = NULL;
     214             : 
     215        2989 :   if (calld->security_context_set == 0 &&
     216         871 :       op->cancel_with_status == GRPC_STATUS_OK) {
     217         871 :     calld->security_context_set = 1;
     218         871 :     GPR_ASSERT(op->context);
     219         871 :     if (op->context[GRPC_CONTEXT_SECURITY].value == NULL) {
     220        1686 :       op->context[GRPC_CONTEXT_SECURITY].value =
     221         843 :           grpc_client_security_context_create();
     222         843 :       op->context[GRPC_CONTEXT_SECURITY].destroy =
     223             :           grpc_client_security_context_destroy;
     224             :     }
     225         871 :     sec_ctx = op->context[GRPC_CONTEXT_SECURITY].value;
     226         871 :     GRPC_AUTH_CONTEXT_UNREF(sec_ctx->auth_context, "client auth filter");
     227         871 :     sec_ctx->auth_context = GRPC_AUTH_CONTEXT_REF(
     228             :         chand->security_connector->base.auth_context, "client_auth_filter");
     229             :   }
     230             : 
     231        2118 :   if (op->bind_pollset != NULL) {
     232         871 :     calld->pollset = op->bind_pollset;
     233             :   }
     234             : 
     235        2118 :   if (op->send_ops != NULL && !calld->sent_initial_metadata) {
     236         871 :     size_t nops = op->send_ops->nops;
     237         871 :     grpc_stream_op *ops = op->send_ops->ops;
     238        1742 :     for (i = 0; i < nops; i++) {
     239         871 :       grpc_stream_op *sop = &ops[i];
     240         871 :       if (sop->type != GRPC_OP_METADATA) continue;
     241         871 :       calld->op_md_idx = i;
     242         871 :       calld->sent_initial_metadata = 1;
     243        8759 :       for (l = sop->data.metadata.list.head; l != NULL; l = l->next) {
     244        7888 :         grpc_mdelem *md = l->md;
     245             :         /* Pointer comparison is OK for md_elems created from the same context.
     246             :          */
     247        7888 :         if (md->key == chand->authority_string) {
     248         871 :           if (calld->host != NULL) GRPC_MDSTR_UNREF(calld->host);
     249         871 :           calld->host = GRPC_MDSTR_REF(md->value);
     250        7017 :         } else if (md->key == chand->path_string) {
     251         871 :           if (calld->method != NULL) GRPC_MDSTR_UNREF(calld->method);
     252         871 :           calld->method = GRPC_MDSTR_REF(md->value);
     253             :         }
     254             :       }
     255         871 :       if (calld->host != NULL) {
     256             :         grpc_security_status status;
     257         871 :         const char *call_host = grpc_mdstr_as_c_string(calld->host);
     258         871 :         calld->op = *op; /* Copy op (originates from the caller's stack). */
     259         871 :         status = grpc_channel_security_connector_check_call_host(
     260             :             exec_ctx, chand->security_connector, call_host, on_host_checked,
     261             :             elem);
     262         871 :         if (status != GRPC_SECURITY_OK) {
     263         111 :           if (status == GRPC_SECURITY_ERROR) {
     264             :             char *error_msg;
     265           0 :             gpr_asprintf(&error_msg,
     266             :                          "Invalid host %s set in :authority metadata.",
     267             :                          call_host);
     268           0 :             bubble_up_error(exec_ctx, elem, GRPC_STATUS_INVALID_ARGUMENT,
     269             :                             error_msg);
     270           0 :             gpr_free(error_msg);
     271             :           }
     272         111 :           return; /* early exit */
     273             :         }
     274             :       }
     275         760 :       send_security_metadata(exec_ctx, elem, op);
     276         760 :       return; /* early exit */
     277             :     }
     278             :   }
     279             : 
     280             :   /* pass control down the stack */
     281        1247 :   grpc_call_next_op(exec_ctx, elem, op);
     282             : }
     283             : 
     284             : /* Constructor for call_data */
     285         871 : static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
     286             :                            const void *server_transport_data,
     287             :                            grpc_transport_stream_op *initial_op) {
     288         871 :   call_data *calld = elem->call_data;
     289         871 :   memset(calld, 0, sizeof(*calld));
     290         871 :   GPR_ASSERT(!initial_op || !initial_op->send_ops);
     291         871 : }
     292             : 
     293             : /* Destructor for call_data */
     294         871 : static void destroy_call_elem(grpc_exec_ctx *exec_ctx,
     295             :                               grpc_call_element *elem) {
     296         871 :   call_data *calld = elem->call_data;
     297         871 :   grpc_credentials_unref(calld->creds);
     298         871 :   if (calld->host != NULL) {
     299         871 :     GRPC_MDSTR_UNREF(calld->host);
     300             :   }
     301         871 :   if (calld->method != NULL) {
     302         871 :     GRPC_MDSTR_UNREF(calld->method);
     303             :   }
     304         871 :   reset_service_url(calld);
     305         871 : }
     306             : 
     307             : /* Constructor for channel_data */
     308         438 : static void init_channel_elem(grpc_exec_ctx *exec_ctx,
     309             :                               grpc_channel_element *elem, grpc_channel *master,
     310             :                               const grpc_channel_args *args,
     311             :                               grpc_mdctx *metadata_context, int is_first,
     312             :                               int is_last) {
     313         438 :   grpc_security_connector *sc = grpc_find_security_connector_in_args(args);
     314             :   /* grab pointers to our data from the channel element */
     315         438 :   channel_data *chand = elem->channel_data;
     316             : 
     317             :   /* The first and the last filters tend to be implemented differently to
     318             :      handle the case that there's no 'next' filter to call on the up or down
     319             :      path */
     320         438 :   GPR_ASSERT(!is_last);
     321         438 :   GPR_ASSERT(sc != NULL);
     322             : 
     323             :   /* initialize members */
     324         438 :   GPR_ASSERT(sc->is_client_side);
     325         438 :   chand->security_connector =
     326         438 :       (grpc_channel_security_connector *)GRPC_SECURITY_CONNECTOR_REF(
     327             :           sc, "client_auth_filter");
     328         438 :   chand->md_ctx = metadata_context;
     329         438 :   chand->authority_string = grpc_mdstr_from_string(chand->md_ctx, ":authority");
     330         438 :   chand->path_string = grpc_mdstr_from_string(chand->md_ctx, ":path");
     331         438 :   chand->error_msg_key = grpc_mdstr_from_string(chand->md_ctx, "grpc-message");
     332         438 :   chand->status_key = grpc_mdstr_from_string(chand->md_ctx, "grpc-status");
     333         438 : }
     334             : 
     335             : /* Destructor for channel data */
     336         438 : static void destroy_channel_elem(grpc_exec_ctx *exec_ctx,
     337             :                                  grpc_channel_element *elem) {
     338             :   /* grab pointers to our data from the channel element */
     339         438 :   channel_data *chand = elem->channel_data;
     340         438 :   grpc_channel_security_connector *ctx = chand->security_connector;
     341         438 :   if (ctx != NULL)
     342         438 :     GRPC_SECURITY_CONNECTOR_UNREF(&ctx->base, "client_auth_filter");
     343         438 :   if (chand->authority_string != NULL) {
     344         438 :     GRPC_MDSTR_UNREF(chand->authority_string);
     345             :   }
     346         438 :   if (chand->error_msg_key != NULL) {
     347         438 :     GRPC_MDSTR_UNREF(chand->error_msg_key);
     348             :   }
     349         438 :   if (chand->status_key != NULL) {
     350         438 :     GRPC_MDSTR_UNREF(chand->status_key);
     351             :   }
     352         438 :   if (chand->path_string != NULL) {
     353         438 :     GRPC_MDSTR_UNREF(chand->path_string);
     354             :   }
     355         438 : }
     356             : 
     357             : const grpc_channel_filter grpc_client_auth_filter = {
     358             :     auth_start_transport_op, grpc_channel_next_op, sizeof(call_data),
     359             :     init_call_elem,          destroy_call_elem,    sizeof(channel_data),
     360             :     init_channel_elem,       destroy_channel_elem, grpc_call_next_get_peer,
     361             :     "client-auth"};

Generated by: LCOV version 1.10