diff --git a/BUILD b/BUILD
index 1c84d9f4b68..120aad21a06 100644
--- a/BUILD
+++ b/BUILD
@@ -1741,6 +1741,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",
@@ -1786,6 +1787,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",
diff --git a/BUILD.gn b/BUILD.gn
index 9cc7c83d959..e30c5e268df 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -845,6 +845,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",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3dba3dd03a4..caa1f818190 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1755,6 +1755,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
diff --git a/Makefile b/Makefile
index 699afe6f0b0..ae10e17629b 100644
--- a/Makefile
+++ b/Makefile
@@ -2156,6 +2156,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 \
@@ -4602,6 +4603,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)
diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml
index a9228fefe89..f13ad017dd3 100644
--- a/build_autogenerated.yaml
+++ b/build_autogenerated.yaml
@@ -683,6 +683,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
@@ -1113,6 +1114,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
diff --git a/config.m4 b/config.m4
index 6ace72cd08a..f41f94ee407 100644
--- a/config.m4
+++ b/config.m4
@@ -418,6 +418,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 \
@@ -992,6 +993,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)
diff --git a/config.w32 b/config.w32
index 66bfada67e5..7e7d025c831 100644
--- a/config.w32
+++ b/config.w32
@@ -385,6 +385,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 " +
@@ -1035,6 +1036,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");
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index 5e09e3cf732..67509b76579 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -530,6 +530,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',
@@ -1049,6 +1050,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',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index d748105ec56..a11224f65dd 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -900,6 +900,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',
@@ -1491,6 +1493,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',
diff --git a/grpc.gemspec b/grpc.gemspec
index 269ac44a4b6..4437badcb0a 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -818,6 +818,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 )
diff --git a/grpc.gyp b/grpc.gyp
index d615edb3be3..2c66c4637c7 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -782,6 +782,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',
diff --git a/package.xml b/package.xml
index 208e4878576..f9dfce3a06d 100644
--- a/package.xml
+++ b/package.xml
@@ -798,6 +798,8 @@
+
+
diff --git a/src/core/lib/security/credentials/external/external_account_credentials.cc b/src/core/lib/security/credentials/external/external_account_credentials.cc
new file mode 100644
index 00000000000..599b718655f
--- /dev/null
+++ b/src/core/lib/security/credentials/external/external_account_credentials.cc
@@ -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
+
+#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 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(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(
+ 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(
+ 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 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(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(uri->authority);
+ request.http.path = gpr_strdup(uri->path);
+ request.http.hdr_count = 2;
+ grpc_http_header* headers = static_cast(
+ 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(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
diff --git a/src/core/lib/security/credentials/external/external_account_credentials.h b/src/core/lib/security/credentials/external/external_account_credentials.h
new file mode 100644
index 00000000000..96a02232739
--- /dev/null
+++ b/src/core/lib/security/credentials/external/external_account_credentials.h
@@ -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
+
+#include
+#include
+
+#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 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 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 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
diff --git a/src/core/lib/security/util/json_util.h b/src/core/lib/security/util/json_util.h
index 42f7005e00e..cde9ca97ffd 100644
--- a/src/core/lib/security/util/json_util.h
+++ b/src/core/lib/security/util/json_util.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,
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index 74cbc4235b6..35edeb799df 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -394,6 +394,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',
diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc
index 22f6cdb7573..32c00179acf 100644
--- a/test/core/security/credentials_test.cc
+++ b/test/core/security/credentials_test.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 scopes)
+ : ExternalAccountCredentials(std::move(options), std::move(scopes)) {}
+
+ protected:
+ void RetrieveSubjectToken(const HTTPRequestContext* ctx,
+ const ExternalAccountCredentialsOptions& options,
+ std::function 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;
}
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 45a8ace92c7..488cfb8b4c8 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -1751,6 +1751,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 \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index 2c88021a1ff..166c68f0cee 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -1594,6 +1594,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 \