Add implementation of base external account credentials

pull/24208/head
Chuan Ren 4 years ago
parent dca24b8de5
commit 0cf672d42e
  1. 2
      BUILD
  2. 2
      BUILD.gn
  3. 1
      CMakeLists.txt
  4. 2
      Makefile
  5. 2
      build_autogenerated.yaml
  6. 2
      config.m4
  7. 2
      config.w32
  8. 2
      gRPC-C++.podspec
  9. 3
      gRPC-Core.podspec
  10. 2
      grpc.gemspec
  11. 1
      grpc.gyp
  12. 2
      package.xml
  13. 311
      src/core/lib/security/credentials/external/external_account_credentials.cc
  14. 119
      src/core/lib/security/credentials/external/external_account_credentials.h
  15. 1
      src/core/lib/security/util/json_util.h
  16. 1
      src/python/grpcio/grpc_core_dependencies.py
  17. 307
      test/core/security/credentials_test.cc
  18. 2
      tools/doxygen/Doxyfile.c++.internal
  19. 2
      tools/doxygen/Doxyfile.core.internal

@ -1777,6 +1777,7 @@ grpc_cc_library(
"src/core/lib/security/credentials/composite/composite_credentials.cc",
"src/core/lib/security/credentials/credentials.cc",
"src/core/lib/security/credentials/credentials_metadata.cc",
"src/core/lib/security/credentials/external/external_account_credentials.cc",
"src/core/lib/security/credentials/fake/fake_credentials.cc",
"src/core/lib/security/credentials/google_default/credentials_generic.cc",
"src/core/lib/security/credentials/google_default/google_default_credentials.cc",
@ -1815,6 +1816,7 @@ grpc_cc_library(
"src/core/lib/security/credentials/alts/alts_credentials.h",
"src/core/lib/security/credentials/composite/composite_credentials.h",
"src/core/lib/security/credentials/credentials.h",
"src/core/lib/security/credentials/external/external_account_credentials.h",
"src/core/lib/security/credentials/fake/fake_credentials.h",
"src/core/lib/security/credentials/google_default/google_default_credentials.h",
"src/core/lib/security/credentials/iam/iam_credentials.h",

@ -812,6 +812,8 @@ config("grpc_config") {
"src/core/lib/security/credentials/credentials.cc",
"src/core/lib/security/credentials/credentials.h",
"src/core/lib/security/credentials/credentials_metadata.cc",
"src/core/lib/security/credentials/external/external_account_credentials.cc",
"src/core/lib/security/credentials/external/external_account_credentials.h",
"src/core/lib/security/credentials/fake/fake_credentials.cc",
"src/core/lib/security/credentials/fake/fake_credentials.h",
"src/core/lib/security/credentials/google_default/credentials_generic.cc",

@ -1735,6 +1735,7 @@ add_library(grpc
src/core/lib/security/credentials/composite/composite_credentials.cc
src/core/lib/security/credentials/credentials.cc
src/core/lib/security/credentials/credentials_metadata.cc
src/core/lib/security/credentials/external/external_account_credentials.cc
src/core/lib/security/credentials/fake/fake_credentials.cc
src/core/lib/security/credentials/google_default/credentials_generic.cc
src/core/lib/security/credentials/google_default/google_default_credentials.cc

@ -2338,6 +2338,7 @@ LIBGRPC_SRC = \
src/core/lib/security/credentials/composite/composite_credentials.cc \
src/core/lib/security/credentials/credentials.cc \
src/core/lib/security/credentials/credentials_metadata.cc \
src/core/lib/security/credentials/external/external_account_credentials.cc \
src/core/lib/security/credentials/fake/fake_credentials.cc \
src/core/lib/security/credentials/google_default/credentials_generic.cc \
src/core/lib/security/credentials/google_default/google_default_credentials.cc \
@ -4804,6 +4805,7 @@ src/core/lib/security/credentials/alts/grpc_alts_credentials_server_options.cc:
src/core/lib/security/credentials/composite/composite_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/credentials_metadata.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/external/external_account_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/fake/fake_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/google_default/credentials_generic.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/google_default/google_default_credentials.cc: $(OPENSSL_DEP)

@ -661,6 +661,7 @@ libs:
- src/core/lib/security/credentials/alts/grpc_alts_credentials_options.h
- src/core/lib/security/credentials/composite/composite_credentials.h
- src/core/lib/security/credentials/credentials.h
- src/core/lib/security/credentials/external/external_account_credentials.h
- src/core/lib/security/credentials/fake/fake_credentials.h
- src/core/lib/security/credentials/google_default/google_default_credentials.h
- src/core/lib/security/credentials/iam/iam_credentials.h
@ -1077,6 +1078,7 @@ libs:
- src/core/lib/security/credentials/composite/composite_credentials.cc
- src/core/lib/security/credentials/credentials.cc
- src/core/lib/security/credentials/credentials_metadata.cc
- src/core/lib/security/credentials/external/external_account_credentials.cc
- src/core/lib/security/credentials/fake/fake_credentials.cc
- src/core/lib/security/credentials/google_default/credentials_generic.cc
- src/core/lib/security/credentials/google_default/google_default_credentials.cc

@ -407,6 +407,7 @@ if test "$PHP_GRPC" != "no"; then
src/core/lib/security/credentials/composite/composite_credentials.cc \
src/core/lib/security/credentials/credentials.cc \
src/core/lib/security/credentials/credentials_metadata.cc \
src/core/lib/security/credentials/external/external_account_credentials.cc \
src/core/lib/security/credentials/fake/fake_credentials.cc \
src/core/lib/security/credentials/google_default/credentials_generic.cc \
src/core/lib/security/credentials/google_default/google_default_credentials.cc \
@ -979,6 +980,7 @@ if test "$PHP_GRPC" != "no"; then
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/alts)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/composite)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/external)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/fake)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/google_default)
PHP_ADD_BUILD_DIR($ext_builddir/src/core/lib/security/credentials/iam)

@ -374,6 +374,7 @@ if (PHP_GRPC != "no") {
"src\\core\\lib\\security\\credentials\\composite\\composite_credentials.cc " +
"src\\core\\lib\\security\\credentials\\credentials.cc " +
"src\\core\\lib\\security\\credentials\\credentials_metadata.cc " +
"src\\core\\lib\\security\\credentials\\external\\external_account_credentials.cc " +
"src\\core\\lib\\security\\credentials\\fake\\fake_credentials.cc " +
"src\\core\\lib\\security\\credentials\\google_default\\credentials_generic.cc " +
"src\\core\\lib\\security\\credentials\\google_default\\google_default_credentials.cc " +
@ -1021,6 +1022,7 @@ if (PHP_GRPC != "no") {
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\alts");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\composite");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\external");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\fake");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\google_default");
FSO.CreateFolder(base_dir+"\\ext\\grpc\\src\\core\\lib\\security\\credentials\\iam");

@ -524,6 +524,7 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/alts/grpc_alts_credentials_options.h',
'src/core/lib/security/credentials/composite/composite_credentials.h',
'src/core/lib/security/credentials/credentials.h',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/fake/fake_credentials.h',
'src/core/lib/security/credentials/google_default/google_default_credentials.h',
'src/core/lib/security/credentials/iam/iam_credentials.h',
@ -1018,6 +1019,7 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/alts/grpc_alts_credentials_options.h',
'src/core/lib/security/credentials/composite/composite_credentials.h',
'src/core/lib/security/credentials/credentials.h',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/fake/fake_credentials.h',
'src/core/lib/security/credentials/google_default/google_default_credentials.h',
'src/core/lib/security/credentials/iam/iam_credentials.h',

@ -867,6 +867,8 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/credentials.cc',
'src/core/lib/security/credentials/credentials.h',
'src/core/lib/security/credentials/credentials_metadata.cc',
'src/core/lib/security/credentials/external/external_account_credentials.cc',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/fake/fake_credentials.cc',
'src/core/lib/security/credentials/fake/fake_credentials.h',
'src/core/lib/security/credentials/google_default/credentials_generic.cc',
@ -1429,6 +1431,7 @@ Pod::Spec.new do |s|
'src/core/lib/security/credentials/alts/grpc_alts_credentials_options.h',
'src/core/lib/security/credentials/composite/composite_credentials.h',
'src/core/lib/security/credentials/credentials.h',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/fake/fake_credentials.h',
'src/core/lib/security/credentials/google_default/google_default_credentials.h',
'src/core/lib/security/credentials/iam/iam_credentials.h',

@ -785,6 +785,8 @@ Gem::Specification.new do |s|
s.files += %w( src/core/lib/security/credentials/credentials.cc )
s.files += %w( src/core/lib/security/credentials/credentials.h )
s.files += %w( src/core/lib/security/credentials/credentials_metadata.cc )
s.files += %w( src/core/lib/security/credentials/external/external_account_credentials.cc )
s.files += %w( src/core/lib/security/credentials/external/external_account_credentials.h )
s.files += %w( src/core/lib/security/credentials/fake/fake_credentials.cc )
s.files += %w( src/core/lib/security/credentials/fake/fake_credentials.h )
s.files += %w( src/core/lib/security/credentials/google_default/credentials_generic.cc )

@ -771,6 +771,7 @@
'src/core/lib/security/credentials/composite/composite_credentials.cc',
'src/core/lib/security/credentials/credentials.cc',
'src/core/lib/security/credentials/credentials_metadata.cc',
'src/core/lib/security/credentials/external/external_account_credentials.cc',
'src/core/lib/security/credentials/fake/fake_credentials.cc',
'src/core/lib/security/credentials/google_default/credentials_generic.cc',
'src/core/lib/security/credentials/google_default/google_default_credentials.cc',

@ -765,6 +765,8 @@
<file baseinstalldir="/" name="src/core/lib/security/credentials/credentials.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/credentials.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/credentials_metadata.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/external/external_account_credentials.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/external/external_account_credentials.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/fake/fake_credentials.cc" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/fake/fake_credentials.h" role="src" />
<file baseinstalldir="/" name="src/core/lib/security/credentials/google_default/credentials_generic.cc" role="src" />

@ -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

@ -30,6 +30,7 @@
#define GRPC_AUTH_JSON_TYPE_INVALID "invalid"
#define GRPC_AUTH_JSON_TYPE_SERVICE_ACCOUNT "service_account"
#define GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER "authorized_user"
#define GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT "external_account"
// Gets a child property from a json node.
const char* grpc_json_get_string_property(const grpc_core::Json& json,

@ -383,6 +383,7 @@ CORE_SOURCE_FILES = [
'src/core/lib/security/credentials/composite/composite_credentials.cc',
'src/core/lib/security/credentials/credentials.cc',
'src/core/lib/security/credentials/credentials_metadata.cc',
'src/core/lib/security/credentials/external/external_account_credentials.cc',
'src/core/lib/security/credentials/fake/fake_credentials.cc',
'src/core/lib/security/credentials/google_default/credentials_generic.cc',
'src/core/lib/security/credentials/google_default/google_default_credentials.cc',

@ -43,6 +43,7 @@
#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/external/external_account_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"
@ -135,6 +136,16 @@ static const char test_sts_endpoint_url[] =
static const char test_method[] = "ThisIsNotAMethod";
static const char valid_external_account_creds_token_exchange_response[] =
"{\"access_token\":\"token_exchange_access_token\","
" \"expires_in\":3599,"
" \"token_type\":\"Bearer\"}";
static const char
valid_external_account_creds_service_account_impersonation_response[] =
"{\"accessToken\":\"service_account_impersonation_access_token\","
" \"expireTime\":\"2050-01-01T00:00:00Z\"}";
/* -- Global state flags. -- */
static bool g_test_is_on_gce = false;
@ -1881,6 +1892,297 @@ static void test_auth_metadata_context(void) {
}
}
static void validate_external_account_creds_token_exchage_request(
const grpc_httpcli_request* request, const char* body, size_t body_size,
bool expect_actor_token) {
// Check that the body is constructed properly.
GPR_ASSERT(body != nullptr);
GPR_ASSERT(body_size != 0);
GPR_ASSERT(request->handshaker == &grpc_httpcli_ssl);
std::string get_url_equivalent =
absl::StrFormat("%s?%s", "https://foo.com:5555/token", body);
grpc_uri* uri = grpc_uri_parse(get_url_equivalent.c_str(), false);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(uri, "audience"), "audience") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(uri, "grant_type"),
"urn:ietf:params:oauth:grant-type:token-exchange") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(uri, "requested_token_type"),
"urn:ietf:params:oauth:token-type:access_token") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(uri, "subject_token"),
"test_subject_token") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(uri, "subject_token_type"),
"subject_token_type") == 0);
GPR_ASSERT(strcmp(grpc_uri_get_query_arg(uri, "scope"),
"https://www.googleapis.com/auth/cloud-platform") == 0);
grpc_uri_destroy(uri);
// Check the rest of the request.
GPR_ASSERT(strcmp(request->host, "foo.com:5555") == 0);
GPR_ASSERT(strcmp(request->http.path, "/token") == 0);
GPR_ASSERT(request->http.hdr_count == 2);
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);
GPR_ASSERT(strcmp(request->http.hdrs[1].key, "Authorization") == 0);
GPR_ASSERT(strcmp(request->http.hdrs[1].value,
"Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=") == 0);
}
static void
validate_external_account_creds_service_account_impersonation_request(
const grpc_httpcli_request* request, const char* body, size_t body_size,
bool expect_actor_token) {
// Check that the body is constructed properly.
GPR_ASSERT(body != nullptr);
GPR_ASSERT(body_size != 0);
GPR_ASSERT(request->handshaker == &grpc_httpcli_ssl);
GPR_ASSERT(strcmp(body, "scope=scope_1 scope_2") == 0);
// Check the rest of the request.
GPR_ASSERT(strcmp(request->host, "foo.com:5555") == 0);
GPR_ASSERT(strcmp(request->http.path, "/service_account_impersonation") == 0);
GPR_ASSERT(request->http.hdr_count == 2);
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);
GPR_ASSERT(strcmp(request->http.hdrs[1].key, "Authorization") == 0);
GPR_ASSERT(strcmp(request->http.hdrs[1].value,
"Bearer token_exchange_access_token") == 0);
}
static int external_account_creds_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) {
if (strcmp(request->http.path, "/token") == 0) {
validate_external_account_creds_token_exchage_request(request, body,
body_size, true);
*response = http_response(
200, valid_external_account_creds_token_exchange_response);
} else if (strcmp(request->http.path, "/service_account_impersonation") ==
0) {
validate_external_account_creds_service_account_impersonation_request(
request, body, body_size, true);
*response = http_response(
200,
valid_external_account_creds_service_account_impersonation_response);
}
grpc_core::ExecCtx::Run(DEBUG_LOCATION, on_done, GRPC_ERROR_NONE);
return 1;
}
static int
external_account_creds_httpcli_post_failure_token_exchange_response_missing_access_token(
const grpc_httpcli_request* request, const char* body, size_t body_size,
grpc_millis /*deadline*/, grpc_closure* on_done,
grpc_httpcli_response* response) {
if (strcmp(request->http.path, "/token") == 0) {
*response = http_response(200,
"{\"not_access_token\":\"not_access_token\","
"\"expires_in\":3599,"
" \"token_type\":\"Bearer\"}");
} else if (strcmp(request->http.path, "/service_account_impersonation") ==
0) {
*response = http_response(
200,
valid_external_account_creds_service_account_impersonation_response);
}
grpc_core::ExecCtx::Run(DEBUG_LOCATION, on_done, GRPC_ERROR_NONE);
return 1;
}
// The subclass of ExternalAccountCredentials for testing.
// ExternalAccountCredentials is an abstract class so we can't directly test
// against it.
class TestExternalAccountCredentials final
: public grpc_core::ExternalAccountCredentials {
public:
TestExternalAccountCredentials(ExternalAccountCredentialsOptions options,
std::vector<std::string> scopes)
: ExternalAccountCredentials(std::move(options), std::move(scopes)) {}
protected:
void RetrieveSubjectToken(const HTTPRequestContext* ctx,
const ExternalAccountCredentialsOptions& options,
std::function<void(std::string, grpc_error*)> cb) {
cb("test_subject_token", GRPC_ERROR_NONE);
}
};
static void test_external_account_creds_success(void) {
expected_md emd[] = {{"authorization", "Bearer token_exchange_access_token"}};
grpc_core::ExecCtx exec_ctx;
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
grpc_core::Json credential_source("");
TestExternalAccountCredentials::ExternalAccountCredentialsOptions options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"", // service_account_impersonation_url;
"https://foo.com:5555/token", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
};
TestExternalAccountCredentials creds(options, {});
/* Check security level. */
GPR_ASSERT(creds.min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
/* 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,
external_account_creds_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();
grpc_httpcli_set_override(nullptr, nullptr);
}
static void
test_external_account_creds_success_with_service_account_impersonation(void) {
expected_md emd[] = {
{"authorization", "Bearer service_account_impersonation_access_token"}};
grpc_core::ExecCtx exec_ctx;
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
grpc_core::Json credential_source("");
TestExternalAccountCredentials::ExternalAccountCredentialsOptions options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
"https://foo.com:5555/token", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
};
TestExternalAccountCredentials creds(options, {"scope_1", "scope_2"});
/* Check security level. */
GPR_ASSERT(creds.min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
/* 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,
external_account_creds_httpcli_post_success);
run_request_metadata_test(&creds, auth_md_ctx, state);
grpc_core::ExecCtx::Get()->Flush();
grpc_httpcli_set_override(nullptr, nullptr);
}
static void test_external_account_creds_faiure_invalid_token_url(void) {
grpc_core::ExecCtx exec_ctx;
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
grpc_core::Json credential_source("");
TestExternalAccountCredentials::ExternalAccountCredentialsOptions options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
"invalid_token_url", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
};
TestExternalAccountCredentials creds(options, {});
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
httpcli_post_should_not_be_called);
grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid token url: invalid_token_url.");
grpc_error* expected_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"Error occurred when fetching oauth2 token.", &error, 1);
request_metadata_state* state =
make_request_metadata_state(expected_error, nullptr, 0);
run_request_metadata_test(&creds, auth_md_ctx, state);
GRPC_ERROR_UNREF(error);
grpc_core::ExecCtx::Get()->Flush();
grpc_httpcli_set_override(nullptr, nullptr);
}
static void
test_external_account_creds_faiure_invalid_service_account_impersonation_url(
void) {
grpc_core::ExecCtx exec_ctx;
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
grpc_core::Json credential_source("");
TestExternalAccountCredentials::ExternalAccountCredentialsOptions options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"invalid_service_account_impersonation_url", // service_account_impersonation_url;
"https://foo.com:5555/token", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
};
TestExternalAccountCredentials creds(options, {});
grpc_httpcli_set_override(httpcli_get_should_not_be_called,
external_account_creds_httpcli_post_success);
grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Invalid service account impersonation url: "
"invalid_service_account_impersonation_url.");
grpc_error* expected_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"Error occurred when fetching oauth2 token.", &error, 1);
request_metadata_state* state =
make_request_metadata_state(expected_error, nullptr, 0);
run_request_metadata_test(&creds, auth_md_ctx, state);
GRPC_ERROR_UNREF(error);
grpc_core::ExecCtx::Get()->Flush();
grpc_httpcli_set_override(nullptr, nullptr);
}
static void
test_external_account_creds_faiure_token_exchange_response_missing_access_token(
void) {
grpc_core::ExecCtx exec_ctx;
grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
nullptr, nullptr};
grpc_core::Json credential_source("");
TestExternalAccountCredentials::ExternalAccountCredentialsOptions options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
"https://foo.com:5555/token", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
};
TestExternalAccountCredentials creds(options, {});
grpc_httpcli_set_override(
httpcli_get_should_not_be_called,
external_account_creds_httpcli_post_failure_token_exchange_response_missing_access_token);
grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
"Missing or invalid access_token in "
"{\"not_access_token\":\"not_access_token\",\"expires_in\":3599,\"token_"
"type\":\"Bearer\"}.");
grpc_error* expected_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"Error occurred when fetching oauth2 token.", &error, 1);
request_metadata_state* state =
make_request_metadata_state(expected_error, nullptr, 0);
run_request_metadata_test(&creds, auth_md_ctx, state);
GRPC_ERROR_UNREF(error);
grpc_core::ExecCtx::Get()->Flush();
grpc_httpcli_set_override(nullptr, nullptr);
}
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(argc, argv);
grpc_init();
@ -1925,6 +2227,11 @@ int main(int argc, char** argv) {
test_get_well_known_google_credentials_file_path();
test_channel_creds_duplicate_without_call_creds();
test_auth_metadata_context();
test_external_account_creds_success();
test_external_account_creds_success_with_service_account_impersonation();
test_external_account_creds_faiure_invalid_token_url();
test_external_account_creds_faiure_invalid_service_account_impersonation_url();
test_external_account_creds_faiure_token_exchange_response_missing_access_token();
grpc_shutdown();
return 0;
}

@ -1734,6 +1734,8 @@ src/core/lib/security/credentials/composite/composite_credentials.h \
src/core/lib/security/credentials/credentials.cc \
src/core/lib/security/credentials/credentials.h \
src/core/lib/security/credentials/credentials_metadata.cc \
src/core/lib/security/credentials/external/external_account_credentials.cc \
src/core/lib/security/credentials/external/external_account_credentials.h \
src/core/lib/security/credentials/fake/fake_credentials.cc \
src/core/lib/security/credentials/fake/fake_credentials.h \
src/core/lib/security/credentials/google_default/credentials_generic.cc \

@ -1561,6 +1561,8 @@ src/core/lib/security/credentials/composite/composite_credentials.h \
src/core/lib/security/credentials/credentials.cc \
src/core/lib/security/credentials/credentials.h \
src/core/lib/security/credentials/credentials_metadata.cc \
src/core/lib/security/credentials/external/external_account_credentials.cc \
src/core/lib/security/credentials/external/external_account_credentials.h \
src/core/lib/security/credentials/fake/fake_credentials.cc \
src/core/lib/security/credentials/fake/fake_credentials.h \
src/core/lib/security/credentials/google_default/credentials_generic.cc \

Loading…
Cancel
Save