aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core
diff options
context:
space:
mode:
authorGravatar jboeuf <jboeuf@google.com>2014-12-19 15:44:30 -0800
committerGravatar Jan Tattermusch <jtattermusch@google.com>2014-12-29 17:03:04 -0800
commit1a809c0ebbf77aedf7f6322ef7d6373962c80264 (patch)
tree40ba18186cf5078fc36d8a5cb99b37e75015b3a1 /src/core
parent3f1af6ee2d5eb9898e9b21270f7fc1f5ee39b37a (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.c224
-rw-r--r--src/core/security/credentials.h3
-rw-r--r--src/core/security/json_token.c33
-rw-r--r--src/core/security/json_token.h13
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_ */