From 0cf672d42e93144cda884b57830e4232d1385311 Mon Sep 17 00:00:00 2001 From: Chuan Ren Date: Mon, 21 Sep 2020 15:34:38 -0700 Subject: [PATCH] Add implementation of base external account credentials --- BUILD | 2 + BUILD.gn | 2 + CMakeLists.txt | 1 + Makefile | 2 + build_autogenerated.yaml | 2 + config.m4 | 2 + config.w32 | 2 + gRPC-C++.podspec | 2 + gRPC-Core.podspec | 3 + grpc.gemspec | 2 + grpc.gyp | 1 + package.xml | 2 + .../external/external_account_credentials.cc | 311 ++++++++++++++++++ .../external/external_account_credentials.h | 119 +++++++ src/core/lib/security/util/json_util.h | 1 + src/python/grpcio/grpc_core_dependencies.py | 1 + test/core/security/credentials_test.cc | 307 +++++++++++++++++ tools/doxygen/Doxyfile.c++.internal | 2 + tools/doxygen/Doxyfile.core.internal | 2 + 19 files changed, 766 insertions(+) create mode 100644 src/core/lib/security/credentials/external/external_account_credentials.cc create mode 100644 src/core/lib/security/credentials/external/external_account_credentials.h diff --git a/BUILD b/BUILD index 5ec511c504e..22421b0ed02 100644 --- a/BUILD +++ b/BUILD @@ -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", diff --git a/BUILD.gn b/BUILD.gn index 56d77fc6700..88f738f0319 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b63d02f1e3..63aaeaa1a4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Makefile b/Makefile index afeb11e1f3a..76d8d915fde 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index f62b8017c15..62d5aa22d5d 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -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 diff --git a/config.m4 b/config.m4 index 3e74b3090af..d4c86f727a3 100644 --- a/config.m4 +++ b/config.m4 @@ -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) diff --git a/config.w32 b/config.w32 index 98ab01a799c..c6798364404 100644 --- a/config.w32 +++ b/config.w32 @@ -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"); diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 37efd951d34..291219934fd 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -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', diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index bd418e8f858..abde8269a40 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -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', diff --git a/grpc.gemspec b/grpc.gemspec index 7d434c02d98..fd1e52db547 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -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 ) diff --git a/grpc.gyp b/grpc.gyp index 56e41d761f1..896590a2091 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -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', diff --git a/package.xml b/package.xml index ed80ad00315..714925a5f61 100644 --- a/package.xml +++ b/package.xml @@ -765,6 +765,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 5b2b78dcfb5..63a76598a01 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -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', 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 b645161be23..cb6201ebf03 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -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 \ diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index f4afb777a81..4080267059f 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -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 \