mirror of https://github.com/grpc/grpc.git
commit
6c982e6b61
55 changed files with 1429 additions and 535 deletions
@ -0,0 +1,311 @@ |
||||
//
|
||||
// Copyright 2020 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/security/credentials/external/external_account_credentials.h" |
||||
|
||||
#include "absl/strings/str_format.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/time/clock.h" |
||||
#include "absl/time/time.h" |
||||
|
||||
#include "src/core/lib/http/parser.h" |
||||
#include "src/core/lib/security/util/json_util.h" |
||||
#include "src/core/lib/slice/b64.h" |
||||
|
||||
#define EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE \ |
||||
"urn:ietf:params:oauth:grant-type:token-exchange" |
||||
#define EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE \ |
||||
"urn:ietf:params:oauth:token-type:access_token" |
||||
#define GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE \ |
||||
"https://www.googleapis.com/auth/cloud-platform" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
ExternalAccountCredentials::ExternalAccountCredentials( |
||||
ExternalAccountCredentialsOptions options, std::vector<std::string> scopes) |
||||
: options_(std::move(options)) { |
||||
if (scopes.empty()) { |
||||
scopes.push_back(GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE); |
||||
} |
||||
scopes_ = std::move(scopes); |
||||
} |
||||
|
||||
ExternalAccountCredentials::~ExternalAccountCredentials() {} |
||||
|
||||
std::string ExternalAccountCredentials::debug_string() { |
||||
return absl::StrFormat("ExternalAccountCredentials{Audience:%s,%s}", |
||||
options_.audience, |
||||
grpc_oauth2_token_fetcher_credentials::debug_string()); |
||||
} |
||||
|
||||
// The token fetching flow:
|
||||
// 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called
|
||||
// and the subject token is received in OnRetrieveSubjectTokenInternal().
|
||||
// 2. Exchange token - ExchangeToken() gets called with the
|
||||
// subject token from #1. Receive the response in OnExchangeTokenInternal().
|
||||
// 3. (Optional) Impersonate service account - ImpersenateServiceAccount() gets
|
||||
// called with the access token of the response from #2. Get an impersonated
|
||||
// access token in OnImpersenateServiceAccountInternal().
|
||||
// 4. Finish token fetch - Return back the response that contains an access
|
||||
// token in FinishTokenFetch().
|
||||
// TODO(chuanr): Avoid starting the remaining requests if the channel gets shut
|
||||
// down.
|
||||
void ExternalAccountCredentials::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) { |
||||
GPR_ASSERT(ctx_ == nullptr); |
||||
ctx_ = new HTTPRequestContext(httpcli_context, pollent, deadline); |
||||
metadata_req_ = metadata_req; |
||||
response_cb_ = response_cb; |
||||
auto cb = [this](std::string token, grpc_error* error) { |
||||
OnRetrieveSubjectTokenInternal(token, error); |
||||
}; |
||||
RetrieveSubjectToken(ctx_, options_, cb); |
||||
} |
||||
|
||||
void ExternalAccountCredentials::OnRetrieveSubjectTokenInternal( |
||||
absl::string_view subject_token, grpc_error* error) { |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishTokenFetch(error); |
||||
} else { |
||||
ExchangeToken(subject_token); |
||||
} |
||||
} |
||||
|
||||
void ExternalAccountCredentials::ExchangeToken( |
||||
absl::string_view subject_token) { |
||||
grpc_uri* uri = grpc_uri_parse(options_.token_url, false); |
||||
if (uri == nullptr) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Invalid token url: %s.", options_.token_url).c_str())); |
||||
return; |
||||
} |
||||
grpc_httpcli_request request; |
||||
memset(&request, 0, sizeof(grpc_httpcli_request)); |
||||
request.host = const_cast<char*>(uri->authority); |
||||
request.http.path = gpr_strdup(uri->path); |
||||
grpc_http_header* headers = nullptr; |
||||
if (!options_.client_id.empty() && !options_.client_secret.empty()) { |
||||
request.http.hdr_count = 2; |
||||
headers = static_cast<grpc_http_header*>( |
||||
gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count)); |
||||
headers[0].key = gpr_strdup("Content-Type"); |
||||
headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); |
||||
std::string raw_cred = |
||||
absl::StrFormat("%s:%s", options_.client_id, options_.client_secret); |
||||
char* encoded_cred = |
||||
grpc_base64_encode(raw_cred.c_str(), raw_cred.length(), 0, 0); |
||||
std::string str = absl::StrFormat("Basic %s", std::string(encoded_cred)); |
||||
headers[1].key = gpr_strdup("Authorization"); |
||||
headers[1].value = gpr_strdup(str.c_str()); |
||||
gpr_free(encoded_cred); |
||||
} else { |
||||
request.http.hdr_count = 1; |
||||
headers = static_cast<grpc_http_header*>( |
||||
gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count)); |
||||
headers[0].key = gpr_strdup("Content-Type"); |
||||
headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); |
||||
} |
||||
request.http.hdrs = headers; |
||||
request.handshaker = (strcmp(uri->scheme, "https") == 0) |
||||
? &grpc_httpcli_ssl |
||||
: &grpc_httpcli_plaintext; |
||||
std::vector<std::string> body_parts; |
||||
body_parts.push_back(absl::StrFormat("%s=%s", "audience", options_.audience)); |
||||
body_parts.push_back(absl::StrFormat( |
||||
"%s=%s", "grant_type", EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE)); |
||||
body_parts.push_back( |
||||
absl::StrFormat("%s=%s", "requested_token_type", |
||||
EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE)); |
||||
body_parts.push_back(absl::StrFormat("%s=%s", "subject_token_type", |
||||
options_.subject_token_type)); |
||||
body_parts.push_back( |
||||
absl::StrFormat("%s=%s", "subject_token", subject_token)); |
||||
std::string scope = GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE; |
||||
if (options_.service_account_impersonation_url.empty()) { |
||||
scope = absl::StrJoin(scopes_, " "); |
||||
} |
||||
body_parts.push_back(absl::StrFormat("%s=%s", "scope", scope)); |
||||
std::string body = absl::StrJoin(body_parts, "&"); |
||||
grpc_resource_quota* resource_quota = |
||||
grpc_resource_quota_create("external_account_credentials"); |
||||
grpc_http_response_destroy(&ctx_->response); |
||||
ctx_->response = {}; |
||||
GRPC_CLOSURE_INIT(&ctx_->closure, OnExchangeToken, this, nullptr); |
||||
grpc_httpcli_post(ctx_->httpcli_context, ctx_->pollent, resource_quota, |
||||
&request, body.c_str(), body.size(), ctx_->deadline, |
||||
&ctx_->closure, &ctx_->response); |
||||
grpc_resource_quota_unref_internal(resource_quota); |
||||
grpc_http_request_destroy(&request.http); |
||||
grpc_uri_destroy(uri); |
||||
} |
||||
|
||||
void ExternalAccountCredentials::OnExchangeToken(void* arg, grpc_error* error) { |
||||
ExternalAccountCredentials* self = |
||||
static_cast<ExternalAccountCredentials*>(arg); |
||||
self->OnExchangeTokenInternal(GRPC_ERROR_REF(error)); |
||||
} |
||||
|
||||
void ExternalAccountCredentials::OnExchangeTokenInternal(grpc_error* error) { |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishTokenFetch(error); |
||||
} else { |
||||
if (options_.service_account_impersonation_url.empty()) { |
||||
metadata_req_->response = ctx_->response; |
||||
metadata_req_->response.body = gpr_strdup(ctx_->response.body); |
||||
FinishTokenFetch(GRPC_ERROR_NONE); |
||||
} else { |
||||
ImpersenateServiceAccount(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ExternalAccountCredentials::ImpersenateServiceAccount() { |
||||
grpc_error* error = GRPC_ERROR_NONE; |
||||
absl::string_view response_body(ctx_->response.body, |
||||
ctx_->response.body_length); |
||||
Json json = Json::Parse(response_body, &error); |
||||
if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( |
||||
"Invalid token exchange response.", &error, 1)); |
||||
GRPC_ERROR_UNREF(error); |
||||
return; |
||||
} |
||||
auto it = json.object_value().find("access_token"); |
||||
if (it == json.object_value().end() || |
||||
it->second.type() != Json::Type::STRING) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Missing or invalid access_token in %s.", response_body) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
std::string access_token = it->second.string_value(); |
||||
grpc_uri* uri = |
||||
grpc_uri_parse(options_.service_account_impersonation_url, false); |
||||
if (uri == nullptr) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Invalid service account impersonation url: %s.", |
||||
options_.service_account_impersonation_url) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
grpc_httpcli_request request; |
||||
memset(&request, 0, sizeof(grpc_httpcli_request)); |
||||
request.host = const_cast<char*>(uri->authority); |
||||
request.http.path = gpr_strdup(uri->path); |
||||
request.http.hdr_count = 2; |
||||
grpc_http_header* headers = static_cast<grpc_http_header*>( |
||||
gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count)); |
||||
headers[0].key = gpr_strdup("Content-Type"); |
||||
headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); |
||||
std::string str = absl::StrFormat("Bearer %s", access_token); |
||||
headers[1].key = gpr_strdup("Authorization"); |
||||
headers[1].value = gpr_strdup(str.c_str()); |
||||
request.http.hdrs = headers; |
||||
request.handshaker = (strcmp(uri->scheme, "https") == 0) |
||||
? &grpc_httpcli_ssl |
||||
: &grpc_httpcli_plaintext; |
||||
std::string scope = absl::StrJoin(scopes_, " "); |
||||
std::string body = absl::StrFormat("%s=%s", "scope", scope); |
||||
grpc_resource_quota* resource_quota = |
||||
grpc_resource_quota_create("external_account_credentials"); |
||||
grpc_http_response_destroy(&ctx_->response); |
||||
ctx_->response = {}; |
||||
GRPC_CLOSURE_INIT(&ctx_->closure, OnImpersenateServiceAccount, this, nullptr); |
||||
grpc_httpcli_post(ctx_->httpcli_context, ctx_->pollent, resource_quota, |
||||
&request, body.c_str(), body.size(), ctx_->deadline, |
||||
&ctx_->closure, &ctx_->response); |
||||
grpc_resource_quota_unref_internal(resource_quota); |
||||
grpc_http_request_destroy(&request.http); |
||||
grpc_uri_destroy(uri); |
||||
} |
||||
|
||||
void ExternalAccountCredentials::OnImpersenateServiceAccount( |
||||
void* arg, grpc_error* error) { |
||||
ExternalAccountCredentials* self = |
||||
static_cast<ExternalAccountCredentials*>(arg); |
||||
self->OnImpersenateServiceAccountInternal(GRPC_ERROR_REF(error)); |
||||
} |
||||
|
||||
void ExternalAccountCredentials::OnImpersenateServiceAccountInternal( |
||||
grpc_error* error) { |
||||
if (error != GRPC_ERROR_NONE) { |
||||
FinishTokenFetch(error); |
||||
return; |
||||
} |
||||
absl::string_view response_body(ctx_->response.body, |
||||
ctx_->response.body_length); |
||||
Json json = Json::Parse(response_body, &error); |
||||
if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( |
||||
"Invalid service account impersonation response.", &error, 1)); |
||||
GRPC_ERROR_UNREF(error); |
||||
return; |
||||
} |
||||
auto it = json.object_value().find("accessToken"); |
||||
if (it == json.object_value().end() || |
||||
it->second.type() != Json::Type::STRING) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Missing or invalid accessToken in %s.", response_body) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
std::string access_token = it->second.string_value(); |
||||
it = json.object_value().find("expireTime"); |
||||
if (it == json.object_value().end() || |
||||
it->second.type() != Json::Type::STRING) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING( |
||||
absl::StrFormat("Missing or invalid expireTime in %s.", response_body) |
||||
.c_str())); |
||||
return; |
||||
} |
||||
std::string expire_time = it->second.string_value(); |
||||
absl::Time t; |
||||
if (!absl::ParseTime(absl::RFC3339_full, expire_time, &t, nullptr)) { |
||||
FinishTokenFetch(GRPC_ERROR_CREATE_FROM_STATIC_STRING( |
||||
"Invalid expire time of service account impersonation response.")); |
||||
return; |
||||
} |
||||
int expire_in = (t - absl::Now()) / absl::Seconds(1); |
||||
std::string body = absl::StrFormat( |
||||
"{\"access_token\":\"%s\",\"expires_in\":%d,\"token_type\":\"Bearer\"}", |
||||
access_token, expire_in); |
||||
metadata_req_->response = ctx_->response; |
||||
metadata_req_->response.body = gpr_strdup(body.c_str()); |
||||
metadata_req_->response.body_length = body.length(); |
||||
FinishTokenFetch(GRPC_ERROR_NONE); |
||||
} |
||||
|
||||
void ExternalAccountCredentials::FinishTokenFetch(grpc_error* error) { |
||||
GRPC_LOG_IF_ERROR("Fetch external account credentials access token", |
||||
GRPC_ERROR_REF(error)); |
||||
// Move object state into local variables.
|
||||
auto* cb = response_cb_; |
||||
response_cb_ = nullptr; |
||||
auto* metadata_req = metadata_req_; |
||||
metadata_req_ = nullptr; |
||||
auto* ctx = ctx_; |
||||
ctx_ = nullptr; |
||||
// Invoke the callback.
|
||||
cb(metadata_req, error); |
||||
// Delete context.
|
||||
delete ctx; |
||||
GRPC_ERROR_UNREF(error); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,119 @@ |
||||
//
|
||||
// Copyright 2020 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_EXTERNAL_ACCOUNT_CREDENTIALS_H |
||||
#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_EXTERNAL_ACCOUNT_CREDENTIALS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "src/core/lib/json/json.h" |
||||
#include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Base external account credentials. The base class implements common logic for
|
||||
// exchanging external account credentials for GCP access token to authorize
|
||||
// requests to GCP APIs. The specific logic of retrieving subject token is
|
||||
// implemented in subclasses.
|
||||
class ExternalAccountCredentials |
||||
: public grpc_oauth2_token_fetcher_credentials { |
||||
public: |
||||
// External account credentials json interface.
|
||||
struct ExternalAccountCredentialsOptions { |
||||
std::string type; |
||||
std::string audience; |
||||
std::string subject_token_type; |
||||
std::string service_account_impersonation_url; |
||||
std::string token_url; |
||||
std::string token_info_url; |
||||
Json credential_source; |
||||
std::string quota_project_id; |
||||
std::string client_id; |
||||
std::string client_secret; |
||||
}; |
||||
|
||||
ExternalAccountCredentials(ExternalAccountCredentialsOptions options, |
||||
std::vector<std::string> scopes); |
||||
~ExternalAccountCredentials() override; |
||||
std::string debug_string() override; |
||||
|
||||
protected: |
||||
// This is a helper struct to pass information between multiple callback based
|
||||
// asynchronous calls.
|
||||
struct HTTPRequestContext { |
||||
HTTPRequestContext(grpc_httpcli_context* httpcli_context, |
||||
grpc_polling_entity* pollent, grpc_millis deadline) |
||||
: httpcli_context(httpcli_context), |
||||
pollent(pollent), |
||||
deadline(deadline) {} |
||||
~HTTPRequestContext() { grpc_http_response_destroy(&response); } |
||||
|
||||
// Contextual parameters passed from
|
||||
// grpc_oauth2_token_fetcher_credentials::fetch_oauth2().
|
||||
grpc_httpcli_context* httpcli_context; |
||||
grpc_polling_entity* pollent; |
||||
grpc_millis deadline; |
||||
|
||||
// Reusable token fetch http response and closure.
|
||||
grpc_closure closure; |
||||
grpc_http_response response; |
||||
}; |
||||
|
||||
// Subclasses of base external account credentials need to override this
|
||||
// method to implement the specific subject token retrieval logic.
|
||||
// Once the subject token is ready, subclasses need to invoke
|
||||
// the callback function (cb) to pass the subject token (or error)
|
||||
// back.
|
||||
virtual void RetrieveSubjectToken( |
||||
const HTTPRequestContext* ctx, |
||||
const ExternalAccountCredentialsOptions& options, |
||||
std::function<void(std::string, grpc_error*)> cb) = 0; |
||||
|
||||
private: |
||||
// This method implements the common token fetch logic and it will be called
|
||||
// when grpc_oauth2_token_fetcher_credentials request a new access token.
|
||||
void fetch_oauth2(grpc_credentials_metadata_request* req, |
||||
grpc_httpcli_context* httpcli_context, |
||||
grpc_polling_entity* pollent, grpc_iomgr_cb_func cb, |
||||
grpc_millis deadline) override; |
||||
|
||||
void OnRetrieveSubjectTokenInternal(absl::string_view subject_token, |
||||
grpc_error* error); |
||||
|
||||
void ExchangeToken(absl::string_view subject_token); |
||||
static void OnExchangeToken(void* arg, grpc_error* error); |
||||
void OnExchangeTokenInternal(grpc_error* error); |
||||
|
||||
void ImpersenateServiceAccount(); |
||||
static void OnImpersenateServiceAccount(void* arg, grpc_error* error); |
||||
void OnImpersenateServiceAccountInternal(grpc_error* error); |
||||
|
||||
void FinishTokenFetch(grpc_error* error); |
||||
|
||||
ExternalAccountCredentialsOptions options_; |
||||
std::vector<std::string> scopes_; |
||||
|
||||
HTTPRequestContext* ctx_ = nullptr; |
||||
grpc_credentials_metadata_request* metadata_req_ = nullptr; |
||||
grpc_iomgr_cb_func response_cb_ = nullptr; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_EXTERNAL_ACCOUNT_CREDENTIALS_H
|
Loading…
Reference in new issue