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
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index bfc2e33..442d2fa 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 @@
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 @@
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 @@
gpr_free(c);
}
-static int compute_engine_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(
const grpc_credentials *creds) {
return 1;
}
-grpc_credentials_status grpc_compute_engine_credentials_parse_server_response(
+static int oauth2_token_fetcher_has_request_metadata_only(
+ const grpc_credentials *creds) {
+ return 1;
+}
+
+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 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 @@
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 @@
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 @@
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));
- } else {
- cb(user_data, &c->access_token_md, 1, GRPC_CREDENTIALS_OK);
+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);
}
- 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 {
+ 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));
+ }
}
-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 {