diff options
author | jboeuf <jboeuf@google.com> | 2014-12-19 15:44:30 -0800 |
---|---|---|
committer | Jan Tattermusch <jtattermusch@google.com> | 2014-12-29 17:03:04 -0800 |
commit | 1a809c0ebbf77aedf7f6322ef7d6373962c80264 (patch) | |
tree | 40ba18186cf5078fc36d8a5cb99b37e75015b3a1 /src/core | |
parent | 3f1af6ee2d5eb9898e9b21270f7fc1f5ee39b37a (diff) |
Adding support for service account credentials.
- Tested end to end with a JSON key I generated for my account using the
fetch_oauth2 binary.
- The same fetch_oauth2 binary can get a token from the GCE metadata service on a VM in cloud.
Change on 2014/12/19 by jboeuf <jboeuf@google.com>
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=82548689
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/security/credentials.c | 224 | ||||
-rw-r--r-- | src/core/security/credentials.h | 3 | ||||
-rw-r--r-- | src/core/security/json_token.c | 33 | ||||
-rw-r--r-- | src/core/security/json_token.h | 13 |
4 files changed, 209 insertions, 64 deletions
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c index bfc2e3361a..442d2fa624 100644 --- a/src/core/security/credentials.c +++ b/src/core/security/credentials.c @@ -35,6 +35,7 @@ #include "src/core/httpcli/httpcli.h" #include "src/core/iomgr/iomgr.h" +#include "src/core/security/json_token.h" #include <grpc/support/alloc.h> #include <grpc/support/log.h> #include <grpc/support/string.h> @@ -47,10 +48,18 @@ #include <stdio.h> /* -- Constants. -- */ -#define GRPC_COMPUTE_ENGINE_TOKEN_REFRESH_THRESHOLD_SECS 60 + +#define GRPC_OAUTH2_TOKEN_REFRESH_THRESHOLD_SECS 60 + #define GRPC_COMPUTE_ENGINE_METADATA_HOST "metadata" #define GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH \ - "computeMetadata/v1/instance/service-accounts/default/token" + "/computeMetadata/v1/instance/service-accounts/default/token" + +#define GRPC_SERVICE_ACCOUNT_HOST "www.googleapis.com" +#define GRPC_SERVICE_ACCOUNT_TOKEN_PATH "/oauth2/v3/token" +#define GRPC_SERVICE_ACCOUNT_POST_BODY_PREFIX \ + "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&" \ + "assertion=" /* -- Common. -- */ @@ -234,7 +243,14 @@ grpc_server_credentials *grpc_ssl_server_credentials_create( return &c->base; } -/* -- ComputeEngine credentials. -- */ +/* -- Oauth2TokenFetcher credentials -- */ + +/* This object is a base for credentials that need to acquire an oauth2 token + from an http service. */ + +typedef void (*grpc_fetch_oauth2_func)(grpc_credentials_metadata_request *req, + grpc_httpcli_response_cb response_cb, + gpr_timespec deadline); typedef struct { grpc_credentials base; @@ -242,10 +258,12 @@ typedef struct { grpc_mdctx *md_ctx; grpc_mdelem *access_token_md; gpr_timespec token_expiration; -} grpc_compute_engine_credentials; + grpc_fetch_oauth2_func fetch_func; +} grpc_oauth2_token_fetcher_credentials; -static void compute_engine_destroy(grpc_credentials *creds) { - grpc_compute_engine_credentials *c = (grpc_compute_engine_credentials *)creds; +static void oauth2_token_fetcher_destroy(grpc_credentials *creds) { + grpc_oauth2_token_fetcher_credentials *c = + (grpc_oauth2_token_fetcher_credentials *)creds; if (c->access_token_md != NULL) { grpc_mdelem_unref(c->access_token_md); } @@ -254,16 +272,18 @@ static void compute_engine_destroy(grpc_credentials *creds) { gpr_free(c); } -static int compute_engine_has_request_metadata(const grpc_credentials *creds) { +static int oauth2_token_fetcher_has_request_metadata( + const grpc_credentials *creds) { return 1; } -static int compute_engine_has_request_metadata_only( +static int oauth2_token_fetcher_has_request_metadata_only( const grpc_credentials *creds) { return 1; } -grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( +grpc_credentials_status +grpc_oauth2_token_fetcher_credentials_parse_server_response( const grpc_httpcli_response *response, grpc_mdctx *ctx, grpc_mdelem **token_elem, gpr_timespec *token_lifetime) { char *null_terminated_body = NULL; @@ -271,9 +291,16 @@ grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( grpc_credentials_status status = GRPC_CREDENTIALS_OK; cJSON *json = NULL; + if (response->body_length > 0) { + null_terminated_body = gpr_malloc(response->body_length + 1); + null_terminated_body[response->body_length] = '\0'; + memcpy(null_terminated_body, response->body, response->body_length); + } + if (response->status != 200) { - gpr_log(GPR_ERROR, "Call to metadata server ended with error %d", - response->status); + gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].", + response->status, + null_terminated_body != NULL ? null_terminated_body : ""); status = GRPC_CREDENTIALS_ERROR; goto end; } else { @@ -281,9 +308,6 @@ grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( cJSON *token_type = NULL; cJSON *expires_in = NULL; size_t new_access_token_size = 0; - null_terminated_body = gpr_malloc(response->body_length + 1); - null_terminated_body[response->body_length] = '\0'; - memcpy(null_terminated_body, response->body, response->body_length); json = cJSON_Parse(null_terminated_body); if (json == NULL) { gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body); @@ -338,17 +362,17 @@ end: return status; } -static void on_compute_engine_token_response( +static void on_oauth2_token_fetcher_http_response( void *user_data, const grpc_httpcli_response *response) { grpc_credentials_metadata_request *r = (grpc_credentials_metadata_request *)user_data; - grpc_compute_engine_credentials *c = - (grpc_compute_engine_credentials *)r->creds; + grpc_oauth2_token_fetcher_credentials *c = + (grpc_oauth2_token_fetcher_credentials *)r->creds; gpr_timespec token_lifetime; grpc_credentials_status status; gpr_mu_lock(&c->mu); - status = grpc_compute_engine_credentials_parse_server_response( + status = grpc_oauth2_token_fetcher_credentials_parse_server_response( response, c->md_ctx, &c->access_token_md, &token_lifetime); if (status == GRPC_CREDENTIALS_OK) { c->token_expiration = gpr_time_add(gpr_now(), token_lifetime); @@ -361,51 +385,149 @@ static void on_compute_engine_token_response( grpc_credentials_metadata_request_destroy(r); } -static void compute_engine_get_request_metadata(grpc_credentials *creds, - grpc_credentials_metadata_cb cb, - void *user_data) { - grpc_compute_engine_credentials *c = (grpc_compute_engine_credentials *)creds; - gpr_timespec refresh_threshold = { - GRPC_COMPUTE_ENGINE_TOKEN_REFRESH_THRESHOLD_SECS, 0}; - - gpr_mu_lock(&c->mu); - if (c->access_token_md == NULL || - (gpr_time_cmp(gpr_time_sub(gpr_now(), c->token_expiration), - refresh_threshold) < 0)) { - grpc_httpcli_header header = {"Metadata-Flavor", "Google"}; - grpc_httpcli_request request; - request.host = GRPC_COMPUTE_ENGINE_METADATA_HOST; - request.path = GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH; - request.hdr_count = 1; - request.hdrs = &header; - grpc_httpcli_get( - &request, gpr_time_add(gpr_now(), refresh_threshold), - on_compute_engine_token_response, - grpc_credentials_metadata_request_create(creds, cb, user_data)); +static void oauth2_token_fetcher_get_request_metadata( + grpc_credentials *creds, grpc_credentials_metadata_cb cb, void *user_data) { + grpc_oauth2_token_fetcher_credentials *c = + (grpc_oauth2_token_fetcher_credentials *)creds; + gpr_timespec refresh_threshold = {GRPC_OAUTH2_TOKEN_REFRESH_THRESHOLD_SECS, + 0}; + grpc_mdelem *cached_access_token_md = NULL; + { + gpr_mu_lock(&c->mu); + if (c->access_token_md != NULL && + (gpr_time_cmp(gpr_time_sub(c->token_expiration, gpr_now()), + refresh_threshold) > 0)) { + cached_access_token_md = grpc_mdelem_ref(c->access_token_md); + } + gpr_mu_unlock(&c->mu); + } + if (cached_access_token_md != NULL) { + cb(user_data, &cached_access_token_md, 1, GRPC_CREDENTIALS_OK); + grpc_mdelem_unref(cached_access_token_md); } else { - cb(user_data, &c->access_token_md, 1, GRPC_CREDENTIALS_OK); + c->fetch_func( + grpc_credentials_metadata_request_create(creds, cb, user_data), + on_oauth2_token_fetcher_http_response, + gpr_time_add(gpr_now(), refresh_threshold)); } - gpr_mu_unlock(&c->mu); } -static grpc_credentials_vtable compute_engine_vtable = { - compute_engine_destroy, compute_engine_has_request_metadata, - compute_engine_has_request_metadata_only, - compute_engine_get_request_metadata}; - -grpc_credentials *grpc_compute_engine_credentials_create(void) { - grpc_compute_engine_credentials *c = - gpr_malloc(sizeof(grpc_compute_engine_credentials)); - memset(c, 0, sizeof(grpc_compute_engine_credentials)); +static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials *c, + grpc_fetch_oauth2_func fetch_func) { + memset(c, 0, sizeof(grpc_oauth2_token_fetcher_credentials)); c->base.type = GRPC_CREDENTIALS_TYPE_OAUTH2; - c->base.vtable = &compute_engine_vtable; gpr_ref_init(&c->base.refcount, 1); gpr_mu_init(&c->mu); c->md_ctx = grpc_mdctx_create(); c->token_expiration = gpr_inf_past; + c->fetch_func = fetch_func; +} + +/* -- ComputeEngine credentials. -- */ + +static grpc_credentials_vtable compute_engine_vtable = { + oauth2_token_fetcher_destroy, oauth2_token_fetcher_has_request_metadata, + oauth2_token_fetcher_has_request_metadata_only, + oauth2_token_fetcher_get_request_metadata}; + +static void compute_engine_fetch_oauth2( + grpc_credentials_metadata_request *metadata_req, + grpc_httpcli_response_cb response_cb, gpr_timespec deadline) { + grpc_httpcli_header header = {"Metadata-Flavor", "Google"}; + grpc_httpcli_request request; + memset(&request, 0, sizeof(grpc_httpcli_request)); + request.host = GRPC_COMPUTE_ENGINE_METADATA_HOST; + request.path = GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH; + request.hdr_count = 1; + request.hdrs = &header; + grpc_httpcli_get(&request, deadline, response_cb, metadata_req); +} + +grpc_credentials *grpc_compute_engine_credentials_create(void) { + grpc_oauth2_token_fetcher_credentials *c = + gpr_malloc(sizeof(grpc_oauth2_token_fetcher_credentials)); + init_oauth2_token_fetcher(c, compute_engine_fetch_oauth2); + c->base.vtable = &compute_engine_vtable; return &c->base; } +/* -- ServiceAccount credentials. -- */ + +typedef struct { + grpc_oauth2_token_fetcher_credentials base; + grpc_auth_json_key key; + char *scope; + gpr_timespec token_lifetime; +} grpc_service_account_credentials; + +static void service_account_destroy(grpc_credentials *creds) { + grpc_service_account_credentials *c = + (grpc_service_account_credentials *)creds; + if (c->scope != NULL) gpr_free(c->scope); + grpc_auth_json_key_destruct(&c->key); + oauth2_token_fetcher_destroy(&c->base.base); +} + +static grpc_credentials_vtable service_account_vtable = { + service_account_destroy, oauth2_token_fetcher_has_request_metadata, + oauth2_token_fetcher_has_request_metadata_only, + oauth2_token_fetcher_get_request_metadata}; + +static void service_account_fetch_oauth2( + grpc_credentials_metadata_request *metadata_req, + grpc_httpcli_response_cb response_cb, gpr_timespec deadline) { + grpc_service_account_credentials *c = + (grpc_service_account_credentials *)metadata_req->creds; + grpc_httpcli_header header = {"Content-Type", + "application/x-www-form-urlencoded"}; + grpc_httpcli_request request; + char *body = NULL; + char *jwt = grpc_jwt_encode_and_sign(&c->key, c->scope, c->token_lifetime); + if (jwt == NULL) { + grpc_httpcli_response response; + memset(&response, 0, sizeof(grpc_httpcli_response)); + response.status = 400; /* Invalid request. */ + gpr_log(GPR_ERROR, "Could not create signed jwt."); + /* Do not even send the request, just call the response callback. */ + response_cb(metadata_req, &response); + return; + } + body = gpr_malloc(strlen(GRPC_SERVICE_ACCOUNT_POST_BODY_PREFIX) + + strlen(jwt) + 1); + sprintf(body, "%s%s", GRPC_SERVICE_ACCOUNT_POST_BODY_PREFIX, jwt); + memset(&request, 0, sizeof(grpc_httpcli_request)); + request.host = GRPC_SERVICE_ACCOUNT_HOST; + request.path = GRPC_SERVICE_ACCOUNT_TOKEN_PATH; + request.hdr_count = 1; + request.hdrs = &header; + request.use_ssl = 1; + grpc_httpcli_post(&request, body, strlen(body), deadline, response_cb, + metadata_req); + gpr_free(body); + gpr_free(jwt); +} + +grpc_credentials *grpc_service_account_credentials_create( + const char *json_key, const char *scope, gpr_timespec token_lifetime) { + grpc_service_account_credentials *c; + grpc_auth_json_key key = grpc_auth_json_key_create_from_string(json_key); + + if (scope == NULL || (strlen(scope) == 0) || + !grpc_auth_json_key_is_valid(&key)) { + gpr_log(GPR_ERROR, + "Invalid input for service account credentials creation"); + return NULL; + } + c = gpr_malloc(sizeof(grpc_service_account_credentials)); + memset(c, 0, sizeof(grpc_service_account_credentials)); + init_oauth2_token_fetcher(&c->base, service_account_fetch_oauth2); + c->base.base.vtable = &service_account_vtable; + c->scope = gpr_strdup(scope); + c->key = key; + c->token_lifetime = token_lifetime; + return &c->base.base; +} + /* -- Fake Oauth2 credentials. -- */ typedef struct { diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h index 9fb82e1ecd..036a44493e 100644 --- a/src/core/security/credentials.h +++ b/src/core/security/credentials.h @@ -109,7 +109,8 @@ const grpc_credentials_array *grpc_composite_credentials_get_credentials( grpc_credentials *composite_creds); /* Exposed for testing only. */ -grpc_credentials_status grpc_compute_engine_credentials_parse_server_response( +grpc_credentials_status +grpc_oauth2_token_fetcher_credentials_parse_server_response( const struct grpc_httpcli_response *response, grpc_mdctx *ctx, grpc_mdelem **token_elem, gpr_timespec *token_lifetime); diff --git a/src/core/security/json_token.c b/src/core/security/json_token.c index 440dd8a872..14ee758e8b 100644 --- a/src/core/security/json_token.c +++ b/src/core/security/json_token.c @@ -58,6 +58,10 @@ const gpr_timespec grpc_max_auth_token_lifetime = {3600, 0}; #define GRPC_JWT_RSA_SHA256_ALGORITHM "RS256" #define GRPC_JWT_TYPE "JWT" +/* --- Override for testing. --- */ + +static grpc_jwt_encode_and_sign_override g_jwt_encode_and_sign_override = NULL; + /* --- grpc_auth_json_key. --- */ static const char *json_get_string_property(cJSON *json, @@ -79,7 +83,7 @@ static int set_json_key_string_property(cJSON *json, const char *prop_name, return 1; } -int grpc_auth_json_key_is_valid(grpc_auth_json_key *json_key) { +int grpc_auth_json_key_is_valid(const grpc_auth_json_key *json_key) { return (json_key != NULL) && strcmp(json_key->type, GRPC_AUTH_JSON_KEY_TYPE_INVALID); } @@ -277,14 +281,23 @@ end: char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key, const char *scope, gpr_timespec token_lifetime) { - const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM; - char *to_sign = dot_concat_and_free_strings( - encoded_jwt_header(sig_algo), - encoded_jwt_claim(json_key, scope, token_lifetime)); - char *sig = compute_and_encode_signature(json_key, sig_algo, to_sign); - if (sig == NULL) { - gpr_free(to_sign); - return NULL; + if (g_jwt_encode_and_sign_override != NULL) { + return g_jwt_encode_and_sign_override(json_key, scope, token_lifetime); + } else { + const char *sig_algo = GRPC_JWT_RSA_SHA256_ALGORITHM; + char *to_sign = dot_concat_and_free_strings( + encoded_jwt_header(sig_algo), + encoded_jwt_claim(json_key, scope, token_lifetime)); + char *sig = compute_and_encode_signature(json_key, sig_algo, to_sign); + if (sig == NULL) { + gpr_free(to_sign); + return NULL; + } + return dot_concat_and_free_strings(to_sign, sig); } - return dot_concat_and_free_strings(to_sign, sig); +} + +void grpc_jwt_encode_and_sign_set_override( + grpc_jwt_encode_and_sign_override func) { + g_jwt_encode_and_sign_override = func; } diff --git a/src/core/security/json_token.h b/src/core/security/json_token.h index 2a1f8256cd..3ef9f1bfc0 100644 --- a/src/core/security/json_token.h +++ b/src/core/security/json_token.h @@ -37,7 +37,7 @@ #include <grpc/support/slice.h> #include <openssl/rsa.h> -/* --- auth_json_key parsing. Exposed for testing only. --- */ +/* --- auth_json_key parsing. --- */ typedef struct { char *type; @@ -48,7 +48,7 @@ typedef struct { } grpc_auth_json_key; /* Returns 1 if the object is valid, 0 otherwise. */ -int grpc_auth_json_key_is_valid(grpc_auth_json_key *json_key); +int grpc_auth_json_key_is_valid(const grpc_auth_json_key *json_key); /* Creates a json_key object from string. Returns an invalid object if a parsing error has been encountered. */ @@ -65,4 +65,13 @@ void grpc_auth_json_key_destruct(grpc_auth_json_key *json_key); char *grpc_jwt_encode_and_sign(const grpc_auth_json_key *json_key, const char *scope, gpr_timespec token_lifetime); +/* Override encode_and_sign function for testing. */ +typedef char *(*grpc_jwt_encode_and_sign_override)( + const grpc_auth_json_key *json_key, const char *scope, + gpr_timespec token_lifetime); + +/* Set a custom encode_and_sign override for testing. */ +void grpc_jwt_encode_and_sign_set_override( + grpc_jwt_encode_and_sign_override func); + #endif /* __GRPC_INTERNAL_SECURITY_JSON_TOKEN_H_ */ |