Merge pull request #19032 from jboeuf/sts_core_creds_pr

Adding support for STS Token Exchange Creds in core:
pull/19492/head
jboeuf 6 years ago committed by GitHub
commit bd8698e8fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      grpc.def
  2. 25
      include/grpc/grpc_security.h
  3. 8
      src/core/lib/security/credentials/jwt/json_token.cc
  4. 258
      src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
  5. 16
      src/core/lib/security/credentials/oauth2/oauth2_credentials.h
  6. 24
      src/core/lib/security/util/json_util.cc
  7. 4
      src/core/lib/security/util/json_util.h
  8. 2
      src/ruby/ext/grpc/rb_grpc_imports.generated.c
  9. 3
      src/ruby/ext/grpc/rb_grpc_imports.generated.h
  10. 305
      test/core/security/credentials_test.cc
  11. 82
      test/core/security/fetch_oauth2.cc
  12. 15
      test/core/security/oauth2_utils.cc
  13. 1
      test/core/surface/public_headers_must_be_c89.c

@ -111,6 +111,7 @@ EXPORTS
grpc_google_refresh_token_credentials_create
grpc_access_token_credentials_create
grpc_google_iam_credentials_create
grpc_sts_credentials_create
grpc_metadata_credentials_create_from_plugin
grpc_secure_channel_create
grpc_server_credentials_release

@ -328,6 +328,31 @@ GRPCAPI grpc_call_credentials* grpc_google_iam_credentials_create(
const char* authorization_token, const char* authority_selector,
void* reserved);
/** Options for creating STS Oauth Token Exchange credentials following the IETF
draft https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16.
Optional fields may be set to NULL. It is the responsibility of the caller to
ensure that the subject and actor tokens are refreshed on disk at the
specified paths. This API is used for experimental purposes for now and may
change in the future. */
typedef struct {
const char* sts_endpoint_url; /* Required. */
const char* resource; /* Optional. */
const char* audience; /* Optional. */
const char* scope; /* Optional. */
const char* requested_token_type; /* Optional. */
const char* subject_token_path; /* Required. */
const char* subject_token_type; /* Required. */
const char* actor_token_path; /* Optional. */
const char* actor_token_type; /* Optional. */
} grpc_sts_credentials_options;
/** Creates an STS credentials following the STS Token Exchanged specifed in the
IETF draft https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16.
This API is used for experimental purposes for now and may change in the
future. */
GRPCAPI grpc_call_credentials* grpc_sts_credentials_create(
const grpc_sts_credentials_options* options, void* reserved);
/** Callback function to be called by the metadata credentials plugin
implementation when the metadata is ready.
- user_data is the opaque pointer that was passed in the get_metadata method

@ -18,6 +18,7 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/security/credentials/jwt/json_token.h"
#include <string.h>
@ -69,6 +70,7 @@ grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json* json) {
BIO* bio = nullptr;
const char* prop_value;
int success = 0;
grpc_error* error = GRPC_ERROR_NONE;
memset(&result, 0, sizeof(grpc_auth_json_key));
result.type = GRPC_AUTH_JSON_TYPE_INVALID;
@ -77,7 +79,8 @@ grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json* json) {
goto end;
}
prop_value = grpc_json_get_string_property(json, "type");
prop_value = grpc_json_get_string_property(json, "type", &error);
GRPC_LOG_IF_ERROR("JSON key parsing", error);
if (prop_value == nullptr ||
strcmp(prop_value, GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT)) {
goto end;
@ -92,7 +95,8 @@ grpc_auth_json_key grpc_auth_json_key_create_from_json(const grpc_json* json) {
goto end;
}
prop_value = grpc_json_get_string_property(json, "private_key");
prop_value = grpc_json_get_string_property(json, "private_key", &error);
GRPC_LOG_IF_ERROR("JSON key parsing", error);
if (prop_value == nullptr) {
goto end;
}

@ -22,14 +22,23 @@
#include <string.h>
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/security/util/json_util.h"
#include "src/core/lib/surface/api_trace.h"
#include <grpc/grpc_security.h>
#include <grpc/impl/codegen/slice.h>
#include <grpc/slice.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gprpp/inlined_vector.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/security/util/json_util.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/lib/surface/api_trace.h"
#include "src/core/lib/uri/uri_parser.h"
//
// Auth Refresh Token.
//
@ -45,6 +54,7 @@ grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
grpc_auth_refresh_token result;
const char* prop_value;
int success = 0;
grpc_error* error = GRPC_ERROR_NONE;
memset(&result, 0, sizeof(grpc_auth_refresh_token));
result.type = GRPC_AUTH_JSON_TYPE_INVALID;
@ -53,7 +63,8 @@ grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
goto end;
}
prop_value = grpc_json_get_string_property(json, "type");
prop_value = grpc_json_get_string_property(json, "type", &error);
GRPC_LOG_IF_ERROR("Parsing refresh token", error);
if (prop_value == nullptr ||
strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) {
goto end;
@ -218,8 +229,10 @@ void grpc_oauth2_token_fetcher_credentials::on_http_response(
grpc_mdelem access_token_md = GRPC_MDNULL;
grpc_millis token_lifetime;
grpc_credentials_status status =
grpc_oauth2_token_fetcher_credentials_parse_server_response(
&r->response, &access_token_md, &token_lifetime);
error == GRPC_ERROR_NONE
? grpc_oauth2_token_fetcher_credentials_parse_server_response(
&r->response, &access_token_md, &token_lifetime)
: GRPC_CREDENTIALS_ERROR;
// Update cache and grab list of pending requests.
gpr_mu_lock(&mu_);
token_fetch_pending_ = false;
@ -234,14 +247,15 @@ void grpc_oauth2_token_fetcher_credentials::on_http_response(
gpr_mu_unlock(&mu_);
// Invoke callbacks for all pending requests.
while (pending_request != nullptr) {
grpc_error* new_error = GRPC_ERROR_NONE;
if (status == GRPC_CREDENTIALS_OK) {
grpc_credentials_mdelem_array_add(pending_request->md_array,
access_token_md);
} else {
error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
new_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"Error occurred when fetching oauth2 token.", &error, 1);
}
GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, error);
GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, new_error);
grpc_polling_entity_del_from_pollset_set(
pending_request->pollent, grpc_polling_entity_pollset_set(&pollent_));
grpc_oauth2_pending_get_request_metadata* prev = pending_request;
@ -356,7 +370,8 @@ class grpc_compute_engine_token_fetcher_credentials
grpc_polling_entity* pollent,
grpc_iomgr_cb_func response_cb,
grpc_millis deadline) override {
grpc_http_header header = {(char*)"Metadata-Flavor", (char*)"Google"};
grpc_http_header header = {const_cast<char*>("Metadata-Flavor"),
const_cast<char*>("Google")};
grpc_httpcli_request request;
memset(&request, 0, sizeof(grpc_httpcli_request));
request.host = (char*)GRPC_COMPUTE_ENGINE_METADATA_HOST;
@ -369,11 +384,14 @@ class grpc_compute_engine_token_fetcher_credentials
grpc_resource_quota* resource_quota =
grpc_resource_quota_create("oauth2_credentials");
grpc_httpcli_get(http_context, pollent, resource_quota, &request, deadline,
GRPC_CLOSURE_CREATE(response_cb, metadata_req,
grpc_schedule_on_exec_ctx),
GRPC_CLOSURE_INIT(&http_get_cb_closure_, response_cb,
metadata_req, grpc_schedule_on_exec_ctx),
&metadata_req->response);
grpc_resource_quota_unref_internal(resource_quota);
}
private:
grpc_closure http_get_cb_closure_;
};
} // namespace
@ -401,8 +419,9 @@ void grpc_google_refresh_token_credentials::fetch_oauth2(
grpc_credentials_metadata_request* metadata_req,
grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent,
grpc_iomgr_cb_func response_cb, grpc_millis deadline) {
grpc_http_header header = {(char*)"Content-Type",
(char*)"application/x-www-form-urlencoded"};
grpc_http_header header = {
const_cast<char*>("Content-Type"),
const_cast<char*>("application/x-www-form-urlencoded")};
grpc_httpcli_request request;
char* body = nullptr;
gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING,
@ -419,11 +438,11 @@ void grpc_google_refresh_token_credentials::fetch_oauth2(
extreme memory pressure. */
grpc_resource_quota* resource_quota =
grpc_resource_quota_create("oauth2_credentials_refresh");
grpc_httpcli_post(
httpcli_context, pollent, resource_quota, &request, body, strlen(body),
deadline,
GRPC_CLOSURE_CREATE(response_cb, metadata_req, grpc_schedule_on_exec_ctx),
&metadata_req->response);
grpc_httpcli_post(httpcli_context, pollent, resource_quota, &request, body,
strlen(body), deadline,
GRPC_CLOSURE_INIT(&http_post_cb_closure_, response_cb,
metadata_req, grpc_schedule_on_exec_ctx),
&metadata_req->response);
grpc_resource_quota_unref_internal(resource_quota);
gpr_free(body);
}
@ -472,6 +491,207 @@ grpc_call_credentials* grpc_google_refresh_token_credentials_create(
.release();
}
//
// STS credentials.
//
namespace grpc_core {
namespace {
void MaybeAddToBody(gpr_strvec* body_strvec, const char* field_name,
const char* field) {
if (field == nullptr || strlen(field) == 0) return;
char* new_query;
gpr_asprintf(&new_query, "&%s=%s", field_name, field);
gpr_strvec_add(body_strvec, new_query);
}
grpc_error* LoadTokenFile(const char* path, gpr_slice* token) {
grpc_error* err = grpc_load_file(path, 1, token);
if (err != GRPC_ERROR_NONE) return err;
if (GRPC_SLICE_LENGTH(*token) == 0) {
gpr_log(GPR_ERROR, "Token file %s is empty", path);
err = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Token file is empty.");
}
return err;
}
class StsTokenFetcherCredentials
: public grpc_oauth2_token_fetcher_credentials {
public:
StsTokenFetcherCredentials(grpc_uri* sts_url, // Ownership transfered.
const grpc_sts_credentials_options* options)
: sts_url_(sts_url),
resource_(gpr_strdup(options->resource)),
audience_(gpr_strdup(options->audience)),
scope_(gpr_strdup(options->scope)),
requested_token_type_(gpr_strdup(options->requested_token_type)),
subject_token_path_(gpr_strdup(options->subject_token_path)),
subject_token_type_(gpr_strdup(options->subject_token_type)),
actor_token_path_(gpr_strdup(options->actor_token_path)),
actor_token_type_(gpr_strdup(options->actor_token_type)) {}
~StsTokenFetcherCredentials() override { grpc_uri_destroy(sts_url_); }
private:
void fetch_oauth2(grpc_credentials_metadata_request* metadata_req,
grpc_httpcli_context* http_context,
grpc_polling_entity* pollent,
grpc_iomgr_cb_func response_cb,
grpc_millis deadline) override {
char* body = nullptr;
size_t body_length = 0;
grpc_error* err = FillBody(&body, &body_length);
if (err != GRPC_ERROR_NONE) {
response_cb(metadata_req, err);
GRPC_ERROR_UNREF(err);
return;
}
grpc_http_header header = {
const_cast<char*>("Content-Type"),
const_cast<char*>("application/x-www-form-urlencoded")};
grpc_httpcli_request request;
memset(&request, 0, sizeof(grpc_httpcli_request));
request.host = (char*)sts_url_->authority;
request.http.path = (char*)sts_url_->path;
request.http.hdr_count = 1;
request.http.hdrs = &header;
request.handshaker = (strcmp(sts_url_->scheme, "https") == 0)
? &grpc_httpcli_ssl
: &grpc_httpcli_plaintext;
/* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
channel. This would allow us to cancel an authentication query when under
extreme memory pressure. */
grpc_resource_quota* resource_quota =
grpc_resource_quota_create("oauth2_credentials_refresh");
grpc_httpcli_post(
http_context, pollent, resource_quota, &request, body, body_length,
deadline,
GRPC_CLOSURE_INIT(&http_post_cb_closure_, response_cb, metadata_req,
grpc_schedule_on_exec_ctx),
&metadata_req->response);
grpc_resource_quota_unref_internal(resource_quota);
gpr_free(body);
}
grpc_error* FillBody(char** body, size_t* body_length) {
*body = nullptr;
gpr_strvec body_strvec;
gpr_strvec_init(&body_strvec);
grpc_slice subject_token = grpc_empty_slice();
grpc_slice actor_token = grpc_empty_slice();
grpc_error* err = GRPC_ERROR_NONE;
auto cleanup = [&body, &body_length, &body_strvec, &subject_token,
&actor_token, &err]() {
if (err == GRPC_ERROR_NONE) {
*body = gpr_strvec_flatten(&body_strvec, body_length);
} else {
gpr_free(*body);
}
gpr_strvec_destroy(&body_strvec);
grpc_slice_unref_internal(subject_token);
grpc_slice_unref_internal(actor_token);
return err;
};
err = LoadTokenFile(subject_token_path_.get(), &subject_token);
if (err != GRPC_ERROR_NONE) return cleanup();
gpr_asprintf(
body, GRPC_STS_POST_MINIMAL_BODY_FORMAT_STRING,
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(subject_token)),
subject_token_type_.get());
gpr_strvec_add(&body_strvec, *body);
MaybeAddToBody(&body_strvec, "resource", resource_.get());
MaybeAddToBody(&body_strvec, "audience", audience_.get());
MaybeAddToBody(&body_strvec, "scope", scope_.get());
MaybeAddToBody(&body_strvec, "requested_token_type",
requested_token_type_.get());
if (actor_token_path_ != nullptr) {
err = LoadTokenFile(actor_token_path_.get(), &actor_token);
if (err != GRPC_ERROR_NONE) return cleanup();
MaybeAddToBody(
&body_strvec, "actor_token",
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(subject_token)));
MaybeAddToBody(&body_strvec, "actor_token_type", actor_token_type_.get());
}
return cleanup();
}
grpc_uri* sts_url_;
grpc_closure http_post_cb_closure_;
grpc_core::UniquePtr<char> resource_;
grpc_core::UniquePtr<char> audience_;
grpc_core::UniquePtr<char> scope_;
grpc_core::UniquePtr<char> requested_token_type_;
grpc_core::UniquePtr<char> subject_token_path_;
grpc_core::UniquePtr<char> subject_token_type_;
grpc_core::UniquePtr<char> actor_token_path_;
grpc_core::UniquePtr<char> actor_token_type_;
};
} // namespace
grpc_error* ValidateStsCredentialsOptions(
const grpc_sts_credentials_options* options, grpc_uri** sts_url_out) {
struct GrpcUriDeleter {
void operator()(grpc_uri* uri) { grpc_uri_destroy(uri); }
};
*sts_url_out = nullptr;
InlinedVector<grpc_error*, 3> error_list;
UniquePtr<grpc_uri, GrpcUriDeleter> sts_url(
options->sts_endpoint_url != nullptr
? grpc_uri_parse(options->sts_endpoint_url, false)
: nullptr);
if (sts_url == nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid or missing STS endpoint URL"));
} else {
if (strcmp(sts_url->scheme, "https") != 0 &&
strcmp(sts_url->scheme, "http") != 0) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid URI scheme, must be https to http."));
}
}
if (options->subject_token_path == nullptr ||
strlen(options->subject_token_path) == 0) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"subject_token needs to be specified"));
}
if (options->subject_token_type == nullptr ||
strlen(options->subject_token_type) == 0) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"subject_token_type needs to be specified"));
}
if (error_list.empty()) {
*sts_url_out = sts_url.release();
return GRPC_ERROR_NONE;
} else {
return GRPC_ERROR_CREATE_FROM_VECTOR("Invalid STS Credentials Options",
&error_list);
}
}
} // namespace grpc_core
grpc_call_credentials* grpc_sts_credentials_create(
const grpc_sts_credentials_options* options, void* reserved) {
GPR_ASSERT(reserved == nullptr);
grpc_uri* sts_url;
grpc_error* error =
grpc_core::ValidateStsCredentialsOptions(options, &sts_url);
if (error != GRPC_ERROR_NONE) {
gpr_log(GPR_ERROR, "STS Credentials creation failed. Error: %s.",
grpc_error_string(error));
GRPC_ERROR_UNREF(error);
return nullptr;
}
return grpc_core::MakeRefCounted<grpc_core::StsTokenFetcherCredentials>(
sts_url, options)
.release();
}
//
// Oauth2 Access Token credentials.
//

@ -21,8 +21,15 @@
#include <grpc/support/port_platform.h>
#include <grpc/grpc_security.h>
#include "src/core/lib/json/json.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "src/core/lib/uri/uri_parser.h"
// Constants.
#define GRPC_STS_POST_MINIMAL_BODY_FORMAT_STRING \
"grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=%" \
"s&subject_token_type=%s"
// auth_refresh_token parsing.
typedef struct {
@ -115,6 +122,7 @@ class grpc_google_refresh_token_credentials final
private:
grpc_auth_refresh_token refresh_token_;
grpc_closure http_post_cb_closure_;
};
// Access token credentials.
@ -148,4 +156,12 @@ grpc_oauth2_token_fetcher_credentials_parse_server_response(
const struct grpc_http_response* response, grpc_mdelem* token_md,
grpc_millis* token_lifetime);
namespace grpc_core {
// Exposed for testing only. This function validates the options, ensuring that
// the required fields are set, and outputs the parsed URL of the STS token
// exchanged service.
grpc_error* ValidateStsCredentialsOptions(
const grpc_sts_credentials_options* options, grpc_uri** sts_url);
} // namespace grpc_core
#endif /* GRPC_CORE_LIB_SECURITY_CREDENTIALS_OAUTH2_OAUTH2_CREDENTIALS_H */

@ -18,6 +18,7 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/security/util/json_util.h"
#include <string.h>
@ -26,17 +27,27 @@
#include <grpc/support/string_util.h>
const char* grpc_json_get_string_property(const grpc_json* json,
const char* prop_name) {
grpc_json* child;
const char* prop_name,
grpc_error** error) {
grpc_json* child = nullptr;
if (error != nullptr) *error = GRPC_ERROR_NONE;
for (child = json->child; child != nullptr; child = child->next) {
if (child->key == nullptr) {
gpr_log(GPR_ERROR, "Invalid (null) JSON key encountered");
if (error != nullptr) {
*error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid (null) JSON key encountered");
}
return nullptr;
}
if (strcmp(child->key, prop_name) == 0) break;
}
if (child == nullptr || child->type != GRPC_JSON_STRING) {
gpr_log(GPR_ERROR, "Invalid or missing %s property.", prop_name);
if (error != nullptr) {
char* error_msg;
gpr_asprintf(&error_msg, "Invalid or missing %s property.", prop_name);
*error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(error_msg);
gpr_free(error_msg);
}
return nullptr;
}
return child->value;
@ -45,7 +56,10 @@ const char* grpc_json_get_string_property(const grpc_json* json,
bool grpc_copy_json_string_property(const grpc_json* json,
const char* prop_name,
char** copied_value) {
const char* prop_value = grpc_json_get_string_property(json, prop_name);
grpc_error* error = GRPC_ERROR_NONE;
const char* prop_value =
grpc_json_get_string_property(json, prop_name, &error);
GRPC_LOG_IF_ERROR("Could not copy JSON property", error);
if (prop_value == nullptr) return false;
*copied_value = gpr_strdup(prop_value);
return true;

@ -23,6 +23,7 @@
#include <stdbool.h>
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/json/json.h"
// Constants.
@ -32,7 +33,8 @@
// Gets a child property from a json node.
const char* grpc_json_get_string_property(const grpc_json* json,
const char* prop_name);
const char* prop_name,
grpc_error** error);
// Copies the value of the json child property specified by prop_name.
// Returns false if the property was not found.

@ -134,6 +134,7 @@ grpc_service_account_jwt_access_credentials_create_type grpc_service_account_jwt
grpc_google_refresh_token_credentials_create_type grpc_google_refresh_token_credentials_create_import;
grpc_access_token_credentials_create_type grpc_access_token_credentials_create_import;
grpc_google_iam_credentials_create_type grpc_google_iam_credentials_create_import;
grpc_sts_credentials_create_type grpc_sts_credentials_create_import;
grpc_metadata_credentials_create_from_plugin_type grpc_metadata_credentials_create_from_plugin_import;
grpc_secure_channel_create_type grpc_secure_channel_create_import;
grpc_server_credentials_release_type grpc_server_credentials_release_import;
@ -404,6 +405,7 @@ void grpc_rb_load_imports(HMODULE library) {
grpc_google_refresh_token_credentials_create_import = (grpc_google_refresh_token_credentials_create_type) GetProcAddress(library, "grpc_google_refresh_token_credentials_create");
grpc_access_token_credentials_create_import = (grpc_access_token_credentials_create_type) GetProcAddress(library, "grpc_access_token_credentials_create");
grpc_google_iam_credentials_create_import = (grpc_google_iam_credentials_create_type) GetProcAddress(library, "grpc_google_iam_credentials_create");
grpc_sts_credentials_create_import = (grpc_sts_credentials_create_type) GetProcAddress(library, "grpc_sts_credentials_create");
grpc_metadata_credentials_create_from_plugin_import = (grpc_metadata_credentials_create_from_plugin_type) GetProcAddress(library, "grpc_metadata_credentials_create_from_plugin");
grpc_secure_channel_create_import = (grpc_secure_channel_create_type) GetProcAddress(library, "grpc_secure_channel_create");
grpc_server_credentials_release_import = (grpc_server_credentials_release_type) GetProcAddress(library, "grpc_server_credentials_release");

@ -377,6 +377,9 @@ extern grpc_access_token_credentials_create_type grpc_access_token_credentials_c
typedef grpc_call_credentials*(*grpc_google_iam_credentials_create_type)(const char* authorization_token, const char* authority_selector, void* reserved);
extern grpc_google_iam_credentials_create_type grpc_google_iam_credentials_create_import;
#define grpc_google_iam_credentials_create grpc_google_iam_credentials_create_import
typedef grpc_call_credentials*(*grpc_sts_credentials_create_type)(const grpc_sts_credentials_options* options, void* reserved);
extern grpc_sts_credentials_create_type grpc_sts_credentials_create_import;
#define grpc_sts_credentials_create grpc_sts_credentials_create_import
typedef grpc_call_credentials*(*grpc_metadata_credentials_create_from_plugin_type)(grpc_metadata_credentials_plugin plugin, void* reserved);
extern grpc_metadata_credentials_create_from_plugin_type grpc_metadata_credentials_create_from_plugin_import;
#define grpc_metadata_credentials_create_from_plugin grpc_metadata_credentials_create_from_plugin_import

@ -24,8 +24,8 @@
#include <stdlib.h>
#include <string.h>
#include <grpc/grpc_security.h>
#include <grpc/slice.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
@ -34,13 +34,16 @@
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gpr/tmpfile.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/http/httpcli.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/security/credentials/composite/composite_credentials.h"
#include "src/core/lib/security/credentials/fake/fake_credentials.h"
#include "src/core/lib/security/credentials/google_default/google_default_credentials.h"
#include "src/core/lib/security/credentials/jwt/jwt_credentials.h"
#include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
#include "src/core/lib/security/transport/auth_filters.h"
#include "src/core/lib/uri/uri_parser.h"
#include "test/core/util/test_config.h"
using grpc_core::internal::grpc_flush_cached_google_default_credentials;
@ -99,15 +102,27 @@ static const char valid_oauth2_json_response[] =
" \"expires_in\":3599, "
" \"token_type\":\"Bearer\"}";
static const char valid_sts_json_response[] =
"{\"access_token\":\"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_\","
" \"expires_in\":3599, "
" \"issued_token_type\":\"urn:ietf:params:oauth:token-type:access_token\", "
" \"token_type\":\"Bearer\"}";
static const char test_scope[] = "perm1 perm2";
static const char test_signed_jwt[] =
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY0OTRkN2M1YWU2MGRmOTcyNmM4YW"
"U0MDcyZTViYTdmZDkwODg2YzcifQ";
static const char test_signed_jwt_token_type[] =
"urn:ietf:params:oauth:token-type:id_token";
static const char test_signed_jwt_path_prefix[] = "test_sign_jwt";
static const char test_service_url[] = "https://foo.com/foo.v1";
static const char other_test_service_url[] = "https://bar.com/bar.v1";
static const char test_sts_endpoint_url[] =
"https://foo.com:5555/v1/token-exchange";
static const char test_method[] = "ThisIsNotAMethod";
/* -- Global state flags. -- */
@ -657,11 +672,11 @@ static int refresh_token_httpcli_post_success(
return 1;
}
static int refresh_token_httpcli_post_failure(
const grpc_httpcli_request* request, const char* body, size_t body_size,
grpc_millis deadline, grpc_closure* on_done,
grpc_httpcli_response* response) {
validate_refresh_token_http_request(request, body, body_size);
static int token_httpcli_post_failure(const grpc_httpcli_request* request,
const char* body, size_t body_size,
grpc_millis deadline,
grpc_closure* on_done,
grpc_httpcli_response* response) {
*response = http_response(403, "Not Authorized.");
GRPC_CLOSURE_SCHED(on_done, GRPC_ERROR_NONE);
return 1;
@ -676,7 +691,7 @@ static void test_refresh_token_creds_success(void) {
grpc_call_credentials* creds = grpc_google_refresh_token_credentials_create(
test_refresh_token_str, nullptr);
/* First request: http get should be called. */
/* First request: http put should be called. */
request_metadata_state* state =
make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
@ -707,10 +722,279 @@ static void test_refresh_token_creds_failure(void) {
grpc_call_credentials* creds = grpc_google_refresh_token_credentials_create(
test_refresh_token_str, nullptr);
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
refresh_token_httpcli_post_failure);
token_httpcli_post_failure);
run_request_metadata_test(creds, auth_md_ctx, state);
creds->Unref();
grpc_httpcli_set_override(nullptr, nullptr);
}
static void test_valid_sts_creds_options(void) {
grpc_sts_credentials_options valid_options = {
test_sts_endpoint_url, // sts_endpoint_url
nullptr, // resource
nullptr, // audience
nullptr, // scope
nullptr, // requested_token_type
test_signed_jwt_path_prefix, // subject_token_path
test_signed_jwt_token_type, // subject_token_type
nullptr, // actor_token_path
nullptr // actor_token_type
};
grpc_uri* sts_url;
grpc_error* error =
grpc_core::ValidateStsCredentialsOptions(&valid_options, &sts_url);
GPR_ASSERT(error == GRPC_ERROR_NONE);
GPR_ASSERT(sts_url != nullptr);
grpc_core::StringView host;
grpc_core::StringView port;
GPR_ASSERT(grpc_core::SplitHostPort(sts_url->authority, &host, &port));
GPR_ASSERT(host.cmp("foo.com") == 0);
GPR_ASSERT(port.cmp("5555") == 0);
grpc_uri_destroy(sts_url);
}
static void test_invalid_sts_creds_options(void) {
grpc_sts_credentials_options invalid_options = {
test_sts_endpoint_url, // sts_endpoint_url
nullptr, // resource
nullptr, // audience
nullptr, // scope
nullptr, // requested_token_type
nullptr, // subject_token_path (Required)
test_signed_jwt_token_type, // subject_token_type
nullptr, // actor_token_path
nullptr // actor_token_type
};
grpc_uri* url_should_be_null;
grpc_error* error = grpc_core::ValidateStsCredentialsOptions(
&invalid_options, &url_should_be_null);
GPR_ASSERT(error != GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
GPR_ASSERT(url_should_be_null == nullptr);
invalid_options = {
test_sts_endpoint_url, // sts_endpoint_url
nullptr, // resource
nullptr, // audience
nullptr, // scope
nullptr, // requested_token_type
test_signed_jwt_path_prefix, // subject_token_path
nullptr, // subject_token_type (Required)
nullptr, // actor_token_path
nullptr // actor_token_type
};
error = grpc_core::ValidateStsCredentialsOptions(&invalid_options,
&url_should_be_null);
GPR_ASSERT(error != GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
GPR_ASSERT(url_should_be_null == nullptr);
invalid_options = {
nullptr, // sts_endpoint_url (Required)
nullptr, // resource
nullptr, // audience
nullptr, // scope
nullptr, // requested_token_type
test_signed_jwt_path_prefix, // subject_token_path
test_signed_jwt_token_type, // subject_token_type (Required)
nullptr, // actor_token_path
nullptr // actor_token_type
};
error = grpc_core::ValidateStsCredentialsOptions(&invalid_options,
&url_should_be_null);
GPR_ASSERT(error != GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
GPR_ASSERT(url_should_be_null == nullptr);
invalid_options = {
"not_a_valid_uri", // sts_endpoint_url
nullptr, // resource
nullptr, // audience
nullptr, // scope
nullptr, // requested_token_type
test_signed_jwt_path_prefix, // subject_token_path
test_signed_jwt_token_type, // subject_token_type (Required)
nullptr, // actor_token_path
nullptr // actor_token_type
};
error = grpc_core::ValidateStsCredentialsOptions(&invalid_options,
&url_should_be_null);
GPR_ASSERT(error != GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
GPR_ASSERT(url_should_be_null == nullptr);
invalid_options = {
"ftp://ftp.is.not.a.valid.scheme/bar", // sts_endpoint_url
nullptr, // resource
nullptr, // audience
nullptr, // scope
nullptr, // requested_token_type
test_signed_jwt_path_prefix, // subject_token_path
test_signed_jwt_token_type, // subject_token_type (Required)
nullptr, // actor_token_path
nullptr // actor_token_type
};
error = grpc_core::ValidateStsCredentialsOptions(&invalid_options,
&url_should_be_null);
GPR_ASSERT(error != GRPC_ERROR_NONE);
GRPC_ERROR_UNREF(error);
GPR_ASSERT(url_should_be_null == nullptr);
}
static void validate_sts_token_http_request(const grpc_httpcli_request* request,
const char* body,
size_t body_size) {
// Check that the body is constructed properly.
GPR_ASSERT(body != nullptr);
GPR_ASSERT(body_size != 0);
GPR_ASSERT(request->handshaker == &grpc_httpcli_ssl);
char* get_url_equivalent;
gpr_asprintf(&get_url_equivalent, "%s?%s", test_sts_endpoint_url, body);
grpc_uri* url = grpc_uri_parse(get_url_equivalent, false);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(url, "resource"), "resource") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(url, "audience"), "audience") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(url, "scope"), "scope") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(url, "requested_token_type"),
"requested_token_type") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(url, "subject_token"),
test_signed_jwt) == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(url, "subject_token_type"),
test_signed_jwt_token_type) == 0);
GPR_ASSERT(grpc_uri_get_query_arg(url, "actor_token") == nullptr);
GPR_ASSERT(grpc_uri_get_query_arg(url, "actor_token_type") == nullptr);
grpc_uri_destroy(url);
gpr_free(get_url_equivalent);
// Check the rest of the request.
GPR_ASSERT(strcmp(request->host, "foo.com:5555") == 0);
GPR_ASSERT(strcmp(request->http.path, "/v1/token-exchange") == 0);
GPR_ASSERT(request->http.hdr_count == 1);
GPR_ASSERT(strcmp(request->http.hdrs[0].key, "Content-Type") == 0);
GPR_ASSERT(strcmp(request->http.hdrs[0].value,
"application/x-www-form-urlencoded") == 0);
}
static int sts_token_httpcli_post_success(const grpc_httpcli_request* request,
const char* body, size_t body_size,
grpc_millis deadline,
grpc_closure* on_done,
grpc_httpcli_response* response) {
validate_sts_token_http_request(request, body, body_size);
*response = http_response(200, valid_sts_json_response);
GRPC_CLOSURE_SCHED(on_done, GRPC_ERROR_NONE);
return 1;
}
static char* write_tmp_jwt_file(void) {
char* path;
FILE* tmp = gpr_tmpfile(test_signed_jwt_path_prefix, &path);
GPR_ASSERT(path != nullptr);
GPR_ASSERT(tmp != nullptr);
size_t jwt_length = strlen(test_signed_jwt);
GPR_ASSERT(fwrite(test_signed_jwt, 1, jwt_length, tmp) == jwt_length);
fclose(tmp);
return path;
}
static void test_sts_creds_success(void) {
grpc_core::ExecCtx exec_ctx;
expected_md emd[] = {
{"authorization", "Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_"}};
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
char* test_signed_jwt_path = write_tmp_jwt_file();
grpc_sts_credentials_options valid_options = {
test_sts_endpoint_url, // sts_endpoint_url
"resource", // resource
"audience", // audience
"scope", // scope
"requested_token_type", // requested_token_type
test_signed_jwt_path, // subject_token_path
test_signed_jwt_token_type, // subject_token_type
nullptr, // actor_token_path
nullptr // actor_token_type
};
grpc_call_credentials* creds =
grpc_sts_credentials_create(&valid_options, nullptr);
/* First request: http put should be called. */
request_metadata_state* state =
make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
sts_token_httpcli_post_success);
run_request_metadata_test(creds, auth_md_ctx, state);
grpc_core::ExecCtx::Get()->Flush();
/* Second request: the cached token should be served directly. */
state =
make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
httpcli_post_should_not_be_called);
run_request_metadata_test(creds, auth_md_ctx, state);
grpc_core::ExecCtx::Get()->Flush();
creds->Unref();
grpc_httpcli_set_override(nullptr, nullptr);
gpr_free(test_signed_jwt_path);
}
static void test_sts_creds_load_token_failure(void) {
grpc_core::ExecCtx exec_ctx;
request_metadata_state* state = make_request_metadata_state(
GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Error occurred when fetching oauth2 token."),
nullptr, 0);
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
char* test_signed_jwt_path = write_tmp_jwt_file();
grpc_sts_credentials_options options = {
test_sts_endpoint_url, // sts_endpoint_url
"resource", // resource
"audience", // audience
"scope", // scope
"requested_token_type", // requested_token_type
"invalid_path", // subject_token_path
test_signed_jwt_token_type, // subject_token_type
nullptr, // actor_token_path
nullptr // actor_token_type
};
grpc_call_credentials* creds = grpc_sts_credentials_create(&options, nullptr);
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
httpcli_post_should_not_be_called);
run_request_metadata_test(creds, auth_md_ctx, state);
creds->Unref();
grpc_httpcli_set_override(nullptr, nullptr);
gpr_free(test_signed_jwt_path);
}
static void test_sts_creds_http_failure(void) {
grpc_core::ExecCtx exec_ctx;
request_metadata_state* state = make_request_metadata_state(
GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Error occurred when fetching oauth2 token."),
nullptr, 0);
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
char* test_signed_jwt_path = write_tmp_jwt_file();
grpc_sts_credentials_options valid_options = {
test_sts_endpoint_url, // sts_endpoint_url
"resource", // resource
"audience", // audience
"scope", // scope
"requested_token_type", // requested_token_type
test_signed_jwt_path, // subject_token_path
test_signed_jwt_token_type, // subject_token_type
nullptr, // actor_token_path
nullptr // actor_token_type
};
grpc_call_credentials* creds =
grpc_sts_credentials_create(&valid_options, nullptr);
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
token_httpcli_post_failure);
run_request_metadata_test(creds, auth_md_ctx, state);
creds->Unref();
grpc_httpcli_set_override(nullptr, nullptr);
gpr_free(test_signed_jwt_path);
}
static void validate_jwt_encode_and_sign_params(
@ -1288,6 +1572,11 @@ int main(int argc, char** argv) {
test_compute_engine_creds_failure();
test_refresh_token_creds_success();
test_refresh_token_creds_failure();
test_valid_sts_creds_options();
test_invalid_sts_creds_options();
test_sts_creds_success();
test_sts_creds_load_token_failure();
test_sts_creds_http_failure();
test_jwt_creds_lifetime();
test_jwt_creds_success();
test_jwt_creds_signing_failure();

@ -26,33 +26,82 @@
#include <grpc/support/log.h>
#include <grpc/support/sync.h>
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "src/core/lib/security/util/json_util.h"
#include "test/core/security/oauth2_utils.h"
#include "test/core/util/cmdline.h"
static grpc_sts_credentials_options sts_options_from_json(grpc_json* json) {
grpc_sts_credentials_options options;
memset(&options, 0, sizeof(options));
grpc_error* error = GRPC_ERROR_NONE;
options.sts_endpoint_url =
grpc_json_get_string_property(json, "sts_endpoint_url", &error);
GRPC_LOG_IF_ERROR("STS credentials parsing", error);
options.resource = grpc_json_get_string_property(json, "resource", nullptr);
options.audience = grpc_json_get_string_property(json, "audience", nullptr);
options.scope = grpc_json_get_string_property(json, "scope", nullptr);
options.requested_token_type =
grpc_json_get_string_property(json, "requested_token_type", nullptr);
options.subject_token_path =
grpc_json_get_string_property(json, "subject_token_path", &error);
GRPC_LOG_IF_ERROR("STS credentials parsing", error);
options.subject_token_type =
grpc_json_get_string_property(json, "subject_token_type", &error);
GRPC_LOG_IF_ERROR("STS credentials parsing", error);
options.actor_token_path =
grpc_json_get_string_property(json, "actor_token_path", nullptr);
options.actor_token_type =
grpc_json_get_string_property(json, "actor_token_type", nullptr);
return options;
}
static grpc_call_credentials* create_sts_creds(const char* json_file_path) {
grpc_slice sts_options_slice;
GPR_ASSERT(GRPC_LOG_IF_ERROR(
"load_file", grpc_load_file(json_file_path, 1, &sts_options_slice)));
grpc_json* json = grpc_json_parse_string(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(sts_options_slice)));
if (json == nullptr) {
gpr_log(GPR_ERROR, "Invalid json");
return nullptr;
}
grpc_sts_credentials_options options = sts_options_from_json(json);
grpc_call_credentials* result =
grpc_sts_credentials_create(&options, nullptr);
grpc_json_destroy(json);
gpr_slice_unref(sts_options_slice);
return result;
}
static grpc_call_credentials* create_refresh_token_creds(
const char* json_refresh_token_file_path) {
grpc_slice refresh_token;
GPR_ASSERT(GRPC_LOG_IF_ERROR(
"load_file",
grpc_load_file(json_refresh_token_file_path, 1, &refresh_token)));
return grpc_google_refresh_token_credentials_create(
grpc_call_credentials* result = grpc_google_refresh_token_credentials_create(
reinterpret_cast<const char*> GRPC_SLICE_START_PTR(refresh_token),
nullptr);
gpr_slice_unref(refresh_token);
return result;
}
int main(int argc, char** argv) {
grpc_call_credentials* creds = nullptr;
char* json_key_file_path = nullptr;
const char* json_sts_options_file_path = nullptr;
const char* json_refresh_token_file_path = nullptr;
char* token = nullptr;
int use_gce = 0;
char* scope = nullptr;
gpr_cmdline* cl = gpr_cmdline_create("fetch_oauth2");
gpr_cmdline_add_string(cl, "json_refresh_token",
"File path of the json refresh token.",
&json_refresh_token_file_path);
gpr_cmdline_add_string(cl, "json_sts_options",
"File path of the json sts options.",
&json_sts_options_file_path);
gpr_cmdline_add_flag(
cl, "gce",
"Get a token from the GCE metadata server (only works in GCE).",
@ -61,18 +110,20 @@ int main(int argc, char** argv) {
grpc_init();
if (json_key_file_path != nullptr &&
if (json_sts_options_file_path != nullptr &&
json_refresh_token_file_path != nullptr) {
gpr_log(GPR_ERROR,
"--json_key and --json_refresh_token are mutually exclusive.");
gpr_log(
GPR_ERROR,
"--json_sts_options and --json_refresh_token are mutually exclusive.");
exit(1);
}
if (use_gce) {
if (json_key_file_path != nullptr || scope != nullptr) {
if (json_sts_options_file_path != nullptr ||
json_refresh_token_file_path != nullptr) {
gpr_log(GPR_INFO,
"Ignoring json key and scope to get a token from the GCE "
"metadata server.");
"Ignoring json refresh token or sts options to get a token from "
"the GCE metadata server.");
}
creds = grpc_google_compute_engine_credentials_create(nullptr);
if (creds == nullptr) {
@ -88,8 +139,19 @@ int main(int argc, char** argv) {
json_refresh_token_file_path);
exit(1);
}
} else if (json_sts_options_file_path != nullptr) {
creds = create_sts_creds(json_sts_options_file_path);
if (creds == nullptr) {
gpr_log(GPR_ERROR,
"Could not create sts creds. %s does probably not contain a "
"valid json for sts options.",
json_sts_options_file_path);
exit(1);
}
} else {
gpr_log(GPR_ERROR, "Missing --gce or --json_refresh_token option.");
gpr_log(
GPR_ERROR,
"Missing --gce, --json_sts_options, or --json_refresh_token option.");
exit(1);
}
GPR_ASSERT(creds != nullptr);

@ -63,14 +63,17 @@ static void on_oauth2_response(void* arg, grpc_error* error) {
gpr_mu_unlock(request->mu);
}
static void do_nothing(void* unused, grpc_error* error) {}
static void destroy_after_shutdown(void* pollset, grpc_error* error) {
grpc_pollset_destroy(reinterpret_cast<grpc_pollset*>(pollset));
gpr_free(pollset);
}
char* grpc_test_fetch_oauth2_token_with_credentials(
grpc_call_credentials* creds) {
oauth2_request request;
memset(&request, 0, sizeof(request));
grpc_core::ExecCtx exec_ctx;
grpc_closure do_nothing_closure;
grpc_closure destroy_after_shutdown_closure;
grpc_auth_metadata_context null_ctx = {"", "", nullptr, nullptr};
grpc_pollset* pollset =
@ -79,8 +82,8 @@ char* grpc_test_fetch_oauth2_token_with_credentials(
request.pops = grpc_polling_entity_create_from_pollset(pollset);
request.is_done = false;
GRPC_CLOSURE_INIT(&do_nothing_closure, do_nothing, nullptr,
grpc_schedule_on_exec_ctx);
GRPC_CLOSURE_INIT(&destroy_after_shutdown_closure, destroy_after_shutdown,
pollset, grpc_schedule_on_exec_ctx);
GRPC_CLOSURE_INIT(&request.closure, on_oauth2_response, &request,
grpc_schedule_on_exec_ctx);
@ -107,8 +110,6 @@ char* grpc_test_fetch_oauth2_token_with_credentials(
gpr_mu_unlock(request.mu);
grpc_pollset_shutdown(grpc_polling_entity_pollset(&request.pops),
&do_nothing_closure);
gpr_free(grpc_polling_entity_pollset(&request.pops));
&destroy_after_shutdown_closure);
return request.token;
}

@ -171,6 +171,7 @@ int main(int argc, char **argv) {
printf("%lx", (unsigned long) grpc_google_refresh_token_credentials_create);
printf("%lx", (unsigned long) grpc_access_token_credentials_create);
printf("%lx", (unsigned long) grpc_google_iam_credentials_create);
printf("%lx", (unsigned long) grpc_sts_credentials_create);
printf("%lx", (unsigned long) grpc_metadata_credentials_create_from_plugin);
printf("%lx", (unsigned long) grpc_secure_channel_create);
printf("%lx", (unsigned long) grpc_server_credentials_release);

Loading…
Cancel
Save