[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change plugin credentials API to support both sync and async modes #12374

Merged
merged 16 commits into from
Sep 29, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/environment_variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ some configuration as environment variables that can be set.
completion queue
- round_robin - traces the round_robin load balancing policy
- pick_first - traces the pick first load balancing policy
- plugin_credentials - traces plugin credentials
- resource_quota - trace resource quota objects internals
- glb - traces the grpclb load balancer
- queue_pluck
Expand Down
35 changes: 28 additions & 7 deletions include/grpc/grpc_security.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,19 +249,40 @@ typedef struct {
void *reserved;
} grpc_auth_metadata_context;

/** Maximum number of credentials returnable by a credentials plugin via
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maximum number of metadata as opposed to credentials.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

a synchronous return. */
#define GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX 4
Copy link
Contributor
@fengli79 fengli79 Sep 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make it configurable? Php need to get the callback always invoked on the same thread. Thus Php may use a larger threshold to make sure it's always sync.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we can't make it configurable. For performance reasons, we need to allocate space for the return parameters on the stack instead of heap-allocating it, so the number has to be set at compile time.

This limitation may go away in the future with some metadata API changes that @ctiller has in mind. But for now, if PHP needs to have a plugin return more then 4 metadata elements, then it needs to use the async API.


/** grpc_metadata_credentials plugin is an API user provided structure used to
create grpc_credentials objects that can be set on a channel (composed) or
a call. See grpc_credentials_metadata_create_from_plugin below.
The grpc client stack will call the get_metadata method of the plugin for
every call in scope for the credentials created from it. */
typedef struct {
/** The implementation of this method has to be non-blocking.
- context is the information that can be used by the plugin to create auth
metadata.
- cb is the callback that needs to be called when the metadata is ready.
- user_data needs to be passed as the first parameter of the callback. */
void (*get_metadata)(void *state, grpc_auth_metadata_context context,
grpc_credentials_plugin_metadata_cb cb, void *user_data);
/** The implementation of this method has to be non-blocking, but can
be performed synchronously or asynchronously.

If processing occurs synchronously, returns non-zero and populates
creds_md, num_creds_md, status, and error_details. In this case,
the caller takes ownership of the entries in creds_md and of
error_details. Note that if the plugin needs to return more than
GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX entries in creds_md, it must
return asynchronously.

If processing occurs asynchronously, returns zero and invokes \a cb
when processing is completed. \a user_data will be passed as the
first parameter of the callback. NOTE: \a cb MUST be invoked in a
different thread, not from the thread in which \a get_metadata() is
invoked.

\a context is the information that can be used by the plugin to create
auth metadata. */
int (*get_metadata)(
void *state, grpc_auth_metadata_context context,
grpc_credentials_plugin_metadata_cb cb, void *user_data,
grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
size_t *num_creds_md, grpc_status_code *status,
const char **error_details);

/** Destroys the plugin state. */
void (*destroy)(void *state);
Expand Down
182 changes: 132 additions & 50 deletions src/core/lib/security/credentials/plugin/plugin_credentials.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#include "src/core/lib/surface/api_trace.h"
#include "src/core/lib/surface/validate_metadata.h"

grpc_tracer_flag grpc_plugin_credentials_trace =
GRPC_TRACER_INITIALIZER(false, "plugin_credentials");

static void plugin_destruct(grpc_exec_ctx *exec_ctx,
grpc_call_credentials *creds) {
grpc_plugin_credentials *c = (grpc_plugin_credentials *)creds;
Expand All @@ -53,6 +56,62 @@ static void pending_request_remove_locked(
}
}

// Checks if the request has been cancelled.
// If not, removes it from the pending list, so that it cannot be
// cancelled out from under us.
// When this returns, r->cancelled indicates whether the request was
// cancelled before completion.
static void pending_request_complete(
grpc_exec_ctx *exec_ctx, grpc_plugin_credentials_pending_request *r) {
gpr_mu_lock(&r->creds->mu);
if (!r->cancelled) pending_request_remove_locked(r->creds, r);
gpr_mu_unlock(&r->creds->mu);
// Ref to credentials not needed anymore.
grpc_call_credentials_unref(exec_ctx, &r->creds->base);
}

static grpc_error *process_plugin_result(
grpc_exec_ctx *exec_ctx, grpc_plugin_credentials_pending_request *r,
const grpc_metadata *md, size_t num_md, grpc_status_code status,
const char *error_details) {
grpc_error *error = GRPC_ERROR_NONE;
if (status != GRPC_STATUS_OK) {
char *msg;
gpr_asprintf(&msg, "Getting metadata from plugin failed with error: %s",
error_details);
error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
gpr_free(msg);
} else {
bool seen_illegal_header = false;
for (size_t i = 0; i < num_md; ++i) {
if (!GRPC_LOG_IF_ERROR("validate_metadata_from_plugin",
grpc_validate_header_key_is_legal(md[i].key))) {
seen_illegal_header = true;
break;
} else if (!grpc_is_binary_header(md[i].key) &&
!GRPC_LOG_IF_ERROR(
"validate_metadata_from_plugin",
grpc_validate_header_nonbin_value_is_legal(md[i].value))) {
gpr_log(GPR_ERROR, "Plugin added invalid metadata value.");
seen_illegal_header = true;
break;
}
}
if (seen_illegal_header) {
error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Illegal metadata");
} else {
for (size_t i = 0; i < num_md; ++i) {
grpc_mdelem mdelem = grpc_mdelem_from_slices(
exec_ctx, grpc_slice_ref_internal(md[i].key),
grpc_slice_ref_internal(md[i].value));
grpc_credentials_mdelem_array_add(r->md_array, mdelem);
GRPC_MDELEM_UNREF(exec_ctx, mdelem);
}
}
}
return error;
}

static void plugin_md_request_metadata_ready(void *request,
const grpc_metadata *md,
size_t num_md,
Expand All @@ -64,54 +123,24 @@ static void plugin_md_request_metadata_ready(void *request,
NULL, NULL);
grpc_plugin_credentials_pending_request *r =
(grpc_plugin_credentials_pending_request *)request;
// Check if the request has been cancelled.
// If not, remove it from the pending list, so that it cannot be
// cancelled out from under us.
gpr_mu_lock(&r->creds->mu);
if (!r->cancelled) pending_request_remove_locked(r->creds, r);
gpr_mu_unlock(&r->creds->mu);
grpc_call_credentials_unref(&exec_ctx, &r->creds->base);
if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO,
"plugin_credentials[%p]: request %p: plugin returned "
"asynchronously",
r->creds, r);
}
// Remove request from pending list if not previously cancelled.
pending_request_complete(&exec_ctx, r);
// If it has not been cancelled, process it.
if (!r->cancelled) {
if (status != GRPC_STATUS_OK) {
char *msg;
gpr_asprintf(&msg, "Getting metadata from plugin failed with error: %s",
error_details);
GRPC_CLOSURE_SCHED(&exec_ctx, r->on_request_metadata,
GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
gpr_free(msg);
} else {
bool seen_illegal_header = false;
for (size_t i = 0; i < num_md; ++i) {
if (!GRPC_LOG_IF_ERROR("validate_metadata_from_plugin",
grpc_validate_header_key_is_legal(md[i].key))) {
seen_illegal_header = true;
break;
} else if (!grpc_is_binary_header(md[i].key) &&
!GRPC_LOG_IF_ERROR(
"validate_metadata_from_plugin",
grpc_validate_header_nonbin_value_is_legal(
md[i].value))) {
gpr_log(GPR_ERROR, "Plugin added invalid metadata value.");
seen_illegal_header = true;
break;
}
}
if (seen_illegal_header) {
GRPC_CLOSURE_SCHED(
&exec_ctx, r->on_request_metadata,
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Illegal metadata"));
} else {
for (size_t i = 0; i < num_md; ++i) {
grpc_mdelem mdelem = grpc_mdelem_from_slices(
&exec_ctx, grpc_slice_ref_internal(md[i].key),
grpc_slice_ref_internal(md[i].value));
grpc_credentials_mdelem_array_add(r->md_array, mdelem);
GRPC_MDELEM_UNREF(&exec_ctx, mdelem);
}
GRPC_CLOSURE_SCHED(&exec_ctx, r->on_request_metadata, GRPC_ERROR_NONE);
}
}
grpc_error *error =
process_plugin_result(&exec_ctx, r, md, num_md, status, error_details);
GRPC_CLOSURE_SCHED(&exec_ctx, r->on_request_metadata, error);
} else if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO,
"plugin_credentials[%p]: request %p: plugin was previously "
"cancelled",
r->creds, r);
}
gpr_free(r);
grpc_exec_ctx_finish(&exec_ctx);
Expand All @@ -125,6 +154,7 @@ static bool plugin_get_request_metadata(grpc_exec_ctx *exec_ctx,
grpc_closure *on_request_metadata,
grpc_error **error) {
grpc_plugin_credentials *c = (grpc_plugin_credentials *)creds;
Copy link
Contributor
@fengli79 fengli79 Sep 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markdroth Could you add traces to track the begin/end of the sync/async request? It helps to debug potential deadlock issues mentioned here: #11553 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

bool retval = true; // Synchronous return.
if (c->plugin.get_metadata != NULL) {
// Create pending_request object.
grpc_plugin_credentials_pending_request *pending_request =
Expand All @@ -142,12 +172,60 @@ static bool plugin_get_request_metadata(grpc_exec_ctx *exec_ctx,
c->pending_requests = pending_request;
gpr_mu_unlock(&c->mu);
// Invoke the plugin. The callback holds a ref to us.
if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO, "plugin_credentials[%p]: request %p: invoking plugin",
c, pending_request);
}
grpc_call_credentials_ref(creds);
c->plugin.get_metadata(c->plugin.state, context,
plugin_md_request_metadata_ready, pending_request);
return false;
grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX];
size_t num_creds_md = 0;
grpc_status_code status = GRPC_STATUS_OK;
const char *error_details = NULL;
if (!c->plugin.get_metadata(c->plugin.state, context,
plugin_md_request_metadata_ready,
pending_request, creds_md, &num_creds_md,
&status, &error_details)) {
if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO,
"plugin_credentials[%p]: request %p: plugin will return "
"asynchronously",
c, pending_request);
}
return false; // Asynchronous return.
}
// Returned synchronously.
// Remove request from pending list if not previously cancelled.
pending_request_complete(exec_ctx, pending_request);
// If the request was cancelled, the error will have been returned
// asynchronously by plugin_cancel_get_request_metadata(), so return
// false. Otherwise, process the result.
if (pending_request->cancelled) {
if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO,
"plugin_credentials[%p]: request %p was cancelled, error "
"will be returned asynchronously",
c, pending_request);
}
retval = false;
} else {
if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO,
"plugin_credentials[%p]: request %p: plugin returned "
"synchronously",
c, pending_request);
}
*error = process_plugin_result(exec_ctx, pending_request, creds_md,
num_creds_md, status, error_details);
}
// Clean up.
for (size_t i = 0; i < num_creds_md; ++i) {
grpc_slice_unref_internal(exec_ctx, creds_md[i].key);
grpc_slice_unref_internal(exec_ctx, creds_md[i].value);
}
gpr_free((void *)error_details);
gpr_free(pending_request);
}
return true;
return retval;
}

static void plugin_cancel_get_request_metadata(
Expand All @@ -159,6 +237,10 @@ static void plugin_cancel_get_request_metadata(
c->pending_requests;
pending_request != NULL; pending_request = pending_request->next) {
if (pending_request->md_array == md_array) {
if (GRPC_TRACER_ON(grpc_plugin_credentials_trace)) {
gpr_log(GPR_INFO, "plugin_credentials[%p]: cancelling request %p", c,
pending_request);
}
pending_request->cancelled = true;
GRPC_CLOSURE_SCHED(exec_ctx, pending_request->on_request_metadata,
GRPC_ERROR_REF(error));
Expand Down
2 changes: 2 additions & 0 deletions src/core/lib/security/credentials/plugin/plugin_credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include "src/core/lib/security/credentials/credentials.h"

extern grpc_tracer_flag grpc_plugin_credentials_trace;

struct grpc_plugin_credentials;

typedef struct grpc_plugin_credentials_pending_request {
Expand Down
6 changes: 5 additions & 1 deletion src/core/lib/surface/init_secure.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "src/core/lib/debug/trace.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "src/core/lib/security/credentials/plugin/plugin_credentials.h"
#include "src/core/lib/security/transport/auth_filters.h"
#include "src/core/lib/security/transport/secure_endpoint.h"
#include "src/core/lib/security/transport/security_connector.h"
Expand Down Expand Up @@ -84,4 +85,7 @@ void grpc_register_security_filters(void) {
maybe_prepend_server_auth_filter, NULL);
}

void grpc_security_init() { grpc_security_register_handshaker_factories(); }
void grpc_security_init() {
grpc_security_register_handshaker_factories();
grpc_register_tracer(&grpc_plugin_credentials_trace);
}
Loading