diff --git a/BUILD b/BUILD
index c515a46c81e..561329e33f8 100644
--- a/BUILD
+++ b/BUILD
@@ -1812,6 +1812,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/aws_external_account_credentials.cc",
"src/core/lib/security/credentials/external/aws_request_signer.cc",
"src/core/lib/security/credentials/external/external_account_credentials.cc",
"src/core/lib/security/credentials/external/file_external_account_credentials.cc",
@@ -1858,6 +1859,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/aws_external_account_credentials.h",
"src/core/lib/security/credentials/external/aws_request_signer.h",
"src/core/lib/security/credentials/external/external_account_credentials.h",
"src/core/lib/security/credentials/external/file_external_account_credentials.h",
diff --git a/BUILD.gn b/BUILD.gn
index 6c93c6ce40b..ade80c7c1e2 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1009,6 +1009,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/aws_external_account_credentials.cc",
+ "src/core/lib/security/credentials/external/aws_external_account_credentials.h",
"src/core/lib/security/credentials/external/aws_request_signer.cc",
"src/core/lib/security/credentials/external/aws_request_signer.h",
"src/core/lib/security/credentials/external/external_account_credentials.cc",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8ff51c92d68..73e650ddb42 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1851,6 +1851,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/aws_external_account_credentials.cc
src/core/lib/security/credentials/external/aws_request_signer.cc
src/core/lib/security/credentials/external/external_account_credentials.cc
src/core/lib/security/credentials/external/file_external_account_credentials.cc
diff --git a/Makefile b/Makefile
index 793d95d281a..743d6c01b7c 100644
--- a/Makefile
+++ b/Makefile
@@ -2255,6 +2255,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/aws_external_account_credentials.cc \
src/core/lib/security/credentials/external/aws_request_signer.cc \
src/core/lib/security/credentials/external/external_account_credentials.cc \
src/core/lib/security/credentials/external/file_external_account_credentials.cc \
@@ -4800,6 +4801,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/aws_external_account_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/external/aws_request_signer.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/external/external_account_credentials.cc: $(OPENSSL_DEP)
src/core/lib/security/credentials/external/file_external_account_credentials.cc: $(OPENSSL_DEP)
diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml
index e8c02401d51..8ef027dfdfa 100644
--- a/build_autogenerated.yaml
+++ b/build_autogenerated.yaml
@@ -767,6 +767,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/aws_external_account_credentials.h
- src/core/lib/security/credentials/external/aws_request_signer.h
- src/core/lib/security/credentials/external/external_account_credentials.h
- src/core/lib/security/credentials/external/file_external_account_credentials.h
@@ -1282,6 +1283,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/aws_external_account_credentials.cc
- src/core/lib/security/credentials/external/aws_request_signer.cc
- src/core/lib/security/credentials/external/external_account_credentials.cc
- src/core/lib/security/credentials/external/file_external_account_credentials.cc
diff --git a/config.m4 b/config.m4
index 358acfc0235..aabd536cccc 100644
--- a/config.m4
+++ b/config.m4
@@ -502,6 +502,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/aws_external_account_credentials.cc \
src/core/lib/security/credentials/external/aws_request_signer.cc \
src/core/lib/security/credentials/external/external_account_credentials.cc \
src/core/lib/security/credentials/external/file_external_account_credentials.cc \
diff --git a/config.w32 b/config.w32
index 35a69c160c9..5070ec056af 100644
--- a/config.w32
+++ b/config.w32
@@ -469,6 +469,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\\aws_external_account_credentials.cc " +
"src\\core\\lib\\security\\credentials\\external\\aws_request_signer.cc " +
"src\\core\\lib\\security\\credentials\\external\\external_account_credentials.cc " +
"src\\core\\lib\\security\\credentials\\external\\file_external_account_credentials.cc " +
diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec
index 001a7826ea4..bf79b1b74dd 100644
--- a/gRPC-C++.podspec
+++ b/gRPC-C++.podspec
@@ -612,6 +612,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/aws_external_account_credentials.h',
'src/core/lib/security/credentials/external/aws_request_signer.h',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/external/file_external_account_credentials.h',
@@ -1222,6 +1223,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/aws_external_account_credentials.h',
'src/core/lib/security/credentials/external/aws_request_signer.h',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/external/file_external_account_credentials.h',
diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec
index bab2cef2f0f..9e174fc6446 100644
--- a/gRPC-Core.podspec
+++ b/gRPC-Core.podspec
@@ -1065,6 +1065,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/aws_external_account_credentials.cc',
+ 'src/core/lib/security/credentials/external/aws_external_account_credentials.h',
'src/core/lib/security/credentials/external/aws_request_signer.cc',
'src/core/lib/security/credentials/external/aws_request_signer.h',
'src/core/lib/security/credentials/external/external_account_credentials.cc',
@@ -1753,6 +1755,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/aws_external_account_credentials.h',
'src/core/lib/security/credentials/external/aws_request_signer.h',
'src/core/lib/security/credentials/external/external_account_credentials.h',
'src/core/lib/security/credentials/external/file_external_account_credentials.h',
diff --git a/grpc.gemspec b/grpc.gemspec
index 9338ae2e412..9888b99cdfa 100644
--- a/grpc.gemspec
+++ b/grpc.gemspec
@@ -982,6 +982,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/aws_external_account_credentials.cc )
+ s.files += %w( src/core/lib/security/credentials/external/aws_external_account_credentials.h )
s.files += %w( src/core/lib/security/credentials/external/aws_request_signer.cc )
s.files += %w( src/core/lib/security/credentials/external/aws_request_signer.h )
s.files += %w( src/core/lib/security/credentials/external/external_account_credentials.cc )
diff --git a/grpc.gyp b/grpc.gyp
index 7a8ad7cd0bb..be34e21f7ab 100644
--- a/grpc.gyp
+++ b/grpc.gyp
@@ -868,6 +868,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/aws_external_account_credentials.cc',
'src/core/lib/security/credentials/external/aws_request_signer.cc',
'src/core/lib/security/credentials/external/external_account_credentials.cc',
'src/core/lib/security/credentials/external/file_external_account_credentials.cc',
diff --git a/package.xml b/package.xml
index ad8f7f1885b..2b220409bf8 100644
--- a/package.xml
+++ b/package.xml
@@ -962,6 +962,8 @@
+
+
diff --git a/src/core/lib/security/credentials/external/aws_external_account_credentials.cc b/src/core/lib/security/credentials/external/aws_external_account_credentials.cc
new file mode 100644
index 00000000000..12428c8b2a6
--- /dev/null
+++ b/src/core/lib/security/credentials/external/aws_external_account_credentials.cc
@@ -0,0 +1,415 @@
+//
+// 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/aws_external_account_credentials.h"
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/str_replace.h"
+
+#include "src/core/lib/gpr/env.h"
+
+namespace grpc_core {
+
+namespace {
+
+const char* kExpectedEnvironmentId = "aws1";
+
+const char* kRegionEnvVar = "AWS_REGION";
+const char* kAccessKeyIdEnvVar = "AWS_ACCESS_KEY_ID";
+const char* kSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY";
+const char* kSessionTokenEnvVar = "AWS_SESSION_TOKEN";
+
+std::string UrlEncode(const absl::string_view& s) {
+ const char* hex = "0123456789ABCDEF";
+ std::string result;
+ result.reserve(s.length());
+ for (auto c : s) {
+ if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '!' ||
+ c == '\'' || c == '(' || c == ')' || c == '*' || c == '~' || c == '.') {
+ result.push_back(c);
+ } else {
+ result.push_back('%');
+ result.push_back(hex[static_cast(c) >> 4]);
+ result.push_back(hex[static_cast(c) & 15]);
+ }
+ }
+ return result;
+}
+
+} // namespace
+
+RefCountedPtr
+AwsExternalAccountCredentials::Create(ExternalAccountCredentialsOptions options,
+ std::vector scopes,
+ grpc_error** error) {
+ auto creds = MakeRefCounted(
+ std::move(options), std::move(scopes), error);
+ if (*error == GRPC_ERROR_NONE) {
+ return creds;
+ } else {
+ return nullptr;
+ }
+}
+
+AwsExternalAccountCredentials::AwsExternalAccountCredentials(
+ ExternalAccountCredentialsOptions options, std::vector scopes,
+ grpc_error** error)
+ : ExternalAccountCredentials(options, std::move(scopes)) {
+ auto it = options.credential_source.object_value().find("environment_id");
+ if (it == options.credential_source.object_value().end()) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "environment_id field not present.");
+ return;
+ }
+ if (it->second.type() != Json::Type::STRING) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "environment_id field must be a string.");
+ return;
+ }
+ if (it->second.string_value() != kExpectedEnvironmentId) {
+ *error =
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING("environment_id does not match.");
+ return;
+ }
+ it = options.credential_source.object_value().find("region_url");
+ if (it == options.credential_source.object_value().end()) {
+ *error =
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING("region_url field not present.");
+ return;
+ }
+ if (it->second.type() != Json::Type::STRING) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "region_url field must be a string.");
+ return;
+ }
+ region_url_ = it->second.string_value();
+ it = options.credential_source.object_value().find("url");
+ if (it != options.credential_source.object_value().end() &&
+ it->second.type() == Json::Type::STRING) {
+ url_ = it->second.string_value();
+ }
+ it = options.credential_source.object_value().find(
+ "regional_cred_verification_url");
+ if (it == options.credential_source.object_value().end()) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "regional_cred_verification_url field not present.");
+ return;
+ }
+ if (it->second.type() != Json::Type::STRING) {
+ *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "regional_cred_verification_url field must be a string.");
+ return;
+ }
+ regional_cred_verification_url_ = it->second.string_value();
+}
+
+void AwsExternalAccountCredentials::RetrieveSubjectToken(
+ HTTPRequestContext* ctx, const ExternalAccountCredentialsOptions& options,
+ std::function cb) {
+ if (ctx == nullptr) {
+ FinishRetrieveSubjectToken(
+ "",
+ GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Missing HTTPRequestContext to start subject token retrieval."));
+ return;
+ }
+ ctx_ = ctx;
+ cb_ = cb;
+ if (signer_ != nullptr) {
+ BuildSubjectToken();
+ } else {
+ RetrieveRegion();
+ }
+}
+
+void AwsExternalAccountCredentials::RetrieveRegion() {
+ UniquePtr region_from_env(gpr_getenv(kRegionEnvVar));
+ if (region_from_env != nullptr) {
+ region_ = std::string(region_from_env.get());
+ if (url_.empty()) {
+ RetrieveSigningKeys();
+ } else {
+ RetrieveRoleName();
+ }
+ return;
+ }
+ grpc_uri* uri = grpc_uri_parse(region_url_, false);
+ if (uri == nullptr) {
+ FinishRetrieveSubjectToken(
+ "",
+ GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrFormat("Invalid region url: %s.", region_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.handshaker = (strcmp(uri->scheme, "https") == 0)
+ ? &grpc_httpcli_ssl
+ : &grpc_httpcli_plaintext;
+ 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, OnRetrieveRegion, this, nullptr);
+ grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
+ &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
+ grpc_resource_quota_unref_internal(resource_quota);
+ grpc_http_request_destroy(&request.http);
+ grpc_uri_destroy(uri);
+}
+
+void AwsExternalAccountCredentials::OnRetrieveRegion(void* arg,
+ grpc_error* error) {
+ AwsExternalAccountCredentials* self =
+ static_cast(arg);
+ self->OnRetrieveRegionInternal(GRPC_ERROR_REF(error));
+}
+
+void AwsExternalAccountCredentials::OnRetrieveRegionInternal(
+ grpc_error* error) {
+ if (error != GRPC_ERROR_NONE) {
+ FinishRetrieveSubjectToken("", error);
+ return;
+ }
+ // Remove the last letter of availability zone to get pure region
+ absl::string_view response_body(ctx_->response.body,
+ ctx_->response.body_length);
+ region_ = std::string(response_body.substr(0, response_body.size() - 1));
+ if (url_.empty()) {
+ RetrieveSigningKeys();
+ } else {
+ RetrieveRoleName();
+ }
+}
+
+void AwsExternalAccountCredentials::RetrieveRoleName() {
+ grpc_uri* uri = grpc_uri_parse(url_, false);
+ if (uri == nullptr) {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrFormat("Invalid url: %s.", 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.handshaker = (strcmp(uri->scheme, "https") == 0)
+ ? &grpc_httpcli_ssl
+ : &grpc_httpcli_plaintext;
+ 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, OnRetrieveRoleName, this, nullptr);
+ grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
+ &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
+ grpc_resource_quota_unref_internal(resource_quota);
+ grpc_http_request_destroy(&request.http);
+ grpc_uri_destroy(uri);
+}
+
+void AwsExternalAccountCredentials::OnRetrieveRoleName(void* arg,
+ grpc_error* error) {
+ AwsExternalAccountCredentials* self =
+ static_cast(arg);
+ self->OnRetrieveRoleNameInternal(GRPC_ERROR_REF(error));
+}
+
+void AwsExternalAccountCredentials::OnRetrieveRoleNameInternal(
+ grpc_error* error) {
+ if (error != GRPC_ERROR_NONE) {
+ FinishRetrieveSubjectToken("", error);
+ return;
+ }
+ role_name_ = std::string(ctx_->response.body);
+ RetrieveSigningKeys();
+}
+
+void AwsExternalAccountCredentials::RetrieveSigningKeys() {
+ UniquePtr access_key_id_from_env(gpr_getenv(kAccessKeyIdEnvVar));
+ UniquePtr secret_access_key_from_env(
+ gpr_getenv(kSecretAccessKeyEnvVar));
+ UniquePtr token_from_env(gpr_getenv(kSessionTokenEnvVar));
+ if (access_key_id_from_env != nullptr &&
+ secret_access_key_from_env != nullptr && token_from_env != nullptr) {
+ access_key_id_ = std::string(access_key_id_from_env.get());
+ secret_access_key_ = std::string(secret_access_key_from_env.get());
+ token_ = std::string(token_from_env.get());
+ BuildSubjectToken();
+ return;
+ }
+ if (role_name_.empty()) {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Missing role name when retrieving signing keys."));
+ return;
+ }
+ std::string url_with_role_name = absl::StrCat(url_, "/", role_name_);
+ grpc_uri* uri = grpc_uri_parse(url_with_role_name, false);
+ if (uri == nullptr) {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrFormat("Invalid url with role name: %s.",
+ url_with_role_name)
+ .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.handshaker = (strcmp(uri->scheme, "https") == 0)
+ ? &grpc_httpcli_ssl
+ : &grpc_httpcli_plaintext;
+ 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, OnRetrieveSigningKeys, this, nullptr);
+ grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
+ &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
+ grpc_resource_quota_unref_internal(resource_quota);
+ grpc_http_request_destroy(&request.http);
+ grpc_uri_destroy(uri);
+}
+
+void AwsExternalAccountCredentials::OnRetrieveSigningKeys(void* arg,
+ grpc_error* error) {
+ AwsExternalAccountCredentials* self =
+ static_cast(arg);
+ self->OnRetrieveSigningKeysInternal(GRPC_ERROR_REF(error));
+}
+
+void AwsExternalAccountCredentials::OnRetrieveSigningKeysInternal(
+ grpc_error* error) {
+ if (error != GRPC_ERROR_NONE) {
+ FinishRetrieveSubjectToken("", 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) {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
+ "Invalid retrieve signing keys response.", &error, 1));
+ GRPC_ERROR_UNREF(error);
+ return;
+ }
+ auto it = json.object_value().find("access_key_id");
+ if (it != json.object_value().end() &&
+ it->second.type() == Json::Type::STRING) {
+ access_key_id_ = it->second.string_value();
+ } else {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrFormat("Missing or invalid access_key_id in %s.",
+ response_body)
+ .c_str()));
+ return;
+ }
+ it = json.object_value().find("secret_access_key");
+ if (it != json.object_value().end() &&
+ it->second.type() == Json::Type::STRING) {
+ secret_access_key_ = it->second.string_value();
+ } else {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrFormat("Missing or invalid secret_access_key in %s.",
+ response_body)
+ .c_str()));
+ return;
+ }
+ it = json.object_value().find("token");
+ if (it != json.object_value().end() &&
+ it->second.type() == Json::Type::STRING) {
+ token_ = it->second.string_value();
+ } else {
+ FinishRetrieveSubjectToken(
+ "",
+ GRPC_ERROR_CREATE_FROM_COPIED_STRING(
+ absl::StrFormat("Missing or invalid token in %s.", response_body)
+ .c_str()));
+ return;
+ }
+ BuildSubjectToken();
+}
+
+void AwsExternalAccountCredentials::BuildSubjectToken() {
+ grpc_error* error = GRPC_ERROR_NONE;
+ if (signer_ == nullptr) {
+ cred_verification_url_ = absl::StrReplaceAll(
+ regional_cred_verification_url_, {{"{region}", region_}});
+ signer_ = absl::make_unique(
+ access_key_id_, secret_access_key_, token_, "POST",
+ cred_verification_url_, region_, "",
+ std::map(), &error);
+ if (error != GRPC_ERROR_NONE) {
+ FinishRetrieveSubjectToken(
+ "", GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
+ "Creating aws request signer failed.", &error, 1));
+ GRPC_ERROR_UNREF(error);
+ return;
+ }
+ }
+ auto signed_headers = signer_->GetSignedRequestHeaders();
+ if (error != GRPC_ERROR_NONE) {
+ FinishRetrieveSubjectToken("",
+ GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
+ "Invalid getting signed request"
+ "headers.",
+ &error, 1));
+ GRPC_ERROR_UNREF(error);
+ return;
+ }
+ // Construct subject token
+ Json::Array headers;
+ headers.push_back(Json(
+ {{"key", "Authorization"}, {"value", signed_headers["Authorization"]}}));
+ headers.push_back(Json({{"key", "host"}, {"value", signed_headers["host"]}}));
+ headers.push_back(
+ Json({{"key", "x-amz-date"}, {"value", signed_headers["x-amz-date"]}}));
+ Json::Object object{{"url", Json(cred_verification_url_)},
+ {"method", Json("POST")},
+ {"body", Json("")},
+ {"headers", Json(headers)}};
+ Json subject_token_json(object);
+ std::string subject_token = UrlEncode(subject_token_json.Dump());
+ FinishRetrieveSubjectToken(subject_token, GRPC_ERROR_NONE);
+}
+
+void AwsExternalAccountCredentials::FinishRetrieveSubjectToken(
+ std::string subject_token, grpc_error* error) {
+ // Reset context
+ ctx_ = nullptr;
+ // Move object state into local variables.
+ auto cb = cb_;
+ cb_ = nullptr;
+ // Invoke the callback.
+ if (error != GRPC_ERROR_NONE) {
+ cb("", error);
+ } else {
+ cb(subject_token, GRPC_ERROR_NONE);
+ }
+}
+
+} // namespace grpc_core
diff --git a/src/core/lib/security/credentials/external/aws_external_account_credentials.h b/src/core/lib/security/credentials/external/aws_external_account_credentials.h
new file mode 100644
index 00000000000..306ee33c633
--- /dev/null
+++ b/src/core/lib/security/credentials/external/aws_external_account_credentials.h
@@ -0,0 +1,79 @@
+//
+// 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_AWS_EXTERNAL_ACCOUNT_CREDENTIALS_H
+#define GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_AWS_EXTERNAL_ACCOUNT_CREDENTIALS_H
+
+#include
+
+#include "src/core/lib/security/credentials/external/external_account_credentials.h"
+
+#include "src/core/lib/security/credentials/external/aws_request_signer.h"
+
+namespace grpc_core {
+
+class AwsExternalAccountCredentials final : public ExternalAccountCredentials {
+ public:
+ static RefCountedPtr Create(
+ ExternalAccountCredentialsOptions options,
+ std::vector scopes, grpc_error** error);
+
+ AwsExternalAccountCredentials(ExternalAccountCredentialsOptions options,
+ std::vector scopes,
+ grpc_error** error);
+
+ private:
+ void RetrieveSubjectToken(
+ HTTPRequestContext* ctx, const ExternalAccountCredentialsOptions& options,
+ std::function cb) override;
+
+ void RetrieveRegion();
+ static void OnRetrieveRegion(void* arg, grpc_error* error);
+ void OnRetrieveRegionInternal(grpc_error* error);
+
+ void RetrieveRoleName();
+ static void OnRetrieveRoleName(void* arg, grpc_error* error);
+ void OnRetrieveRoleNameInternal(grpc_error* error);
+
+ void RetrieveSigningKeys();
+ static void OnRetrieveSigningKeys(void* arg, grpc_error* error);
+ void OnRetrieveSigningKeysInternal(grpc_error* error);
+
+ void BuildSubjectToken();
+ void FinishRetrieveSubjectToken(std::string subject_token, grpc_error* error);
+
+ // Fields of credential source
+ std::string region_url_;
+ std::string url_;
+ std::string regional_cred_verification_url_;
+
+ // Information required by request signer
+ std::string region_;
+ std::string role_name_;
+ std::string access_key_id_;
+ std::string secret_access_key_;
+ std::string token_;
+
+ std::unique_ptr signer_;
+ std::string cred_verification_url_;
+
+ HTTPRequestContext* ctx_ = nullptr;
+ std::function cb_ = nullptr;
+};
+
+} // namespace grpc_core
+
+#endif // GRPC_CORE_LIB_SECURITY_CREDENTIALS_EXTERNAL_AWS_EXTERNAL_ACCOUNT_CREDENTIALS_H
diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py
index f9482c04eea..13422cf5f60 100644
--- a/src/python/grpcio/grpc_core_dependencies.py
+++ b/src/python/grpcio/grpc_core_dependencies.py
@@ -478,6 +478,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/aws_external_account_credentials.cc',
'src/core/lib/security/credentials/external/aws_request_signer.cc',
'src/core/lib/security/credentials/external/external_account_credentials.cc',
'src/core/lib/security/credentials/external/file_external_account_credentials.cc',
diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc
index bd3b964586f..35f9a7db472 100644
--- a/test/core/security/credentials_test.cc
+++ b/test/core/security/credentials_test.cc
@@ -44,6 +44,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/aws_external_account_credentials.h"
#include "src/core/lib/security/credentials/external/external_account_credentials.h"
#include "src/core/lib/security/credentials/external/file_external_account_credentials.h"
#include "src/core/lib/security/credentials/external/url_external_account_credentials.h"
@@ -174,6 +175,58 @@ static const char
"{\"url\":\"invalid_credential_source_url\","
"\"headers\":{\"Metadata-Flavor\":\"Google\"}}";
+static const char
+ valid_aws_external_account_creds_retrieve_signing_keys_response[] =
+ "{\"access_key_id\":\"test_access_key_id\",\"secret_access_key\":"
+ "\"test_secret_access_key\",\"token\":\"test_token\"}";
+
+static const char valid_aws_external_account_creds_options_credential_source[] =
+ "{\"environment_id\":\"aws1\","
+ "\"region_url\":\"https://foo.com:5555/region_url\","
+ "\"url\":\"https://foo.com:5555/url\","
+ "\"regional_cred_verification_url\":\"https://foo.com:5555/"
+ "regional_cred_verification_url_{region}\"}";
+
+static const char
+ invalid_aws_external_account_creds_options_credential_source_unmatched_environment_id
+ [] = "{\"environment_id\":\"unsupported_aws_version\","
+ "\"region_url\":\"https://foo.com:5555/region_url\","
+ "\"url\":\"https://foo.com:5555/url\","
+ "\"regional_cred_verification_url\":\"https://foo.com:5555/"
+ "regional_cred_verification_url_{region}\"}";
+
+static const char
+ invalid_aws_external_account_creds_options_credential_source_invalid_region_url
+ [] = "{\"environment_id\":\"aws1\","
+ "\"region_url\":\"invalid_region_url\","
+ "\"url\":\"https://foo.com:5555/url\","
+ "\"regional_cred_verification_url\":\"https://foo.com:5555/"
+ "regional_cred_verification_url_{region}\"}";
+
+static const char
+ invalid_aws_external_account_creds_options_credential_source_invalid_url[] =
+ "{\"environment_id\":\"aws1\","
+ "\"region_url\":\"https://foo.com:5555/region_url\","
+ "\"url\":\"invalid_url\","
+ "\"regional_cred_verification_url\":\"https://foo.com:5555/"
+ "regional_cred_verification_url_{region}\"}";
+
+static const char
+ invalid_aws_external_account_creds_options_credential_source_missing_role_name
+ [] = "{\"environment_id\":\"aws1\","
+ "\"region_url\":\"https://foo.com:5555/region_url\","
+ "\"url\":\"https://foo.com:5555/url_no_role_name\","
+ "\"regional_cred_verification_url\":\"https://foo.com:5555/"
+ "regional_cred_verification_url_{region}\"}";
+
+static const char
+ invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url
+ [] = "{\"environment_id\":\"aws1\","
+ "\"region_url\":\"https://foo.com:5555/region_url\","
+ "\"url\":\"https://foo.com:5555/url_no_role_name\","
+ "\"regional_cred_verification_url\":\"invalid_regional_cred_"
+ "verification_url\"}";
+
/* -- Global state flags. -- */
static bool g_test_is_on_gce = false;
@@ -1942,7 +1995,6 @@ static void validate_external_account_creds_token_exchage_request(
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);
@@ -1964,7 +2016,6 @@ validate_external_account_creds_service_account_impersonation_request(
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);
@@ -2035,6 +2086,69 @@ static int url_external_account_creds_httpcli_get_success(
return 1;
}
+static void validate_aws_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_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 int aws_external_account_creds_httpcli_get_success(
+ const grpc_httpcli_request* request, grpc_millis /*deadline*/,
+ grpc_closure* on_done, grpc_httpcli_response* response) {
+ if (strcmp(request->http.path, "/region_url") == 0) {
+ *response = http_response(200, "test_regionz");
+ } else if (strcmp(request->http.path, "/url") == 0) {
+ *response = http_response(200, "test_role_name");
+ } else if (strcmp(request->http.path, "/url_no_role_name") == 0) {
+ *response = http_response(200, "");
+ } else if (strcmp(request->http.path, "/url/test_role_name") == 0) {
+ *response = http_response(
+ 200, valid_aws_external_account_creds_retrieve_signing_keys_response);
+ }
+ grpc_core::ExecCtx::Run(DEBUG_LOCATION, on_done, GRPC_ERROR_NONE);
+ return 1;
+}
+
+static int aws_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_aws_external_account_creds_token_exchage_request(request, body,
+ body_size, true);
+ *response = http_response(
+ 200, valid_external_account_creds_token_exchange_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.
@@ -2515,6 +2629,366 @@ static void test_file_external_account_creds_failure_invalid_json_content(
gpr_free(subject_token_path);
}
+static void test_aws_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_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ valid_aws_external_account_creds_options_credential_source, &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ request_metadata_state* state =
+ make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+}
+
+static void test_aws_external_account_creds_success_path_region_env_keys_url(
+ 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};
+ gpr_setenv("AWS_REGION", "test_regionz");
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ valid_aws_external_account_creds_options_credential_source, &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ request_metadata_state* state =
+ make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ gpr_unsetenv("AWS_REGION");
+}
+
+static void test_aws_external_account_creds_success_path_region_url_keys_env(
+ 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};
+ gpr_setenv("AWS_ACCESS_KEY_ID", "test_access_key_id");
+ gpr_setenv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
+ gpr_setenv("AWS_SESSION_TOKEN", "test_token");
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ valid_aws_external_account_creds_options_credential_source, &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ request_metadata_state* state =
+ make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ gpr_unsetenv("AWS_ACCESS_KEY_ID");
+ gpr_unsetenv("AWS_SECRET_ACCESS_KEY");
+ gpr_unsetenv("AWS_SESSION_TOKEN");
+}
+
+static void test_aws_external_account_creds_success_path_region_env_keys_env(
+ 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};
+ gpr_setenv("AWS_REGION", "test_regionz");
+ gpr_setenv("AWS_ACCESS_KEY_ID", "test_access_key_id");
+ gpr_setenv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
+ gpr_setenv("AWS_SESSION_TOKEN", "test_token");
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ valid_aws_external_account_creds_options_credential_source, &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ request_metadata_state* state =
+ make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd));
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ gpr_unsetenv("AWS_REGION");
+ gpr_unsetenv("AWS_ACCESS_KEY_ID");
+ gpr_unsetenv("AWS_SECRET_ACCESS_KEY");
+ gpr_unsetenv("AWS_SESSION_TOKEN");
+}
+
+static void test_aws_external_account_creds_failure_unmatched_environment_id(
+ void) {
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ invalid_aws_external_account_creds_options_credential_source_unmatched_environment_id,
+ &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds == nullptr);
+ grpc_slice expected_error_slice =
+ grpc_slice_from_static_string("environment_id does not match.");
+ grpc_slice actual_error_slice;
+ GPR_ASSERT(grpc_error_get_str(error, GRPC_ERROR_STR_DESCRIPTION,
+ &actual_error_slice));
+ GPR_ASSERT(grpc_slice_cmp(expected_error_slice, actual_error_slice) == 0);
+ GRPC_ERROR_UNREF(error);
+}
+
+static void test_aws_external_account_creds_failure_invalid_region_url(void) {
+ grpc_core::ExecCtx exec_ctx;
+ grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
+ nullptr, nullptr};
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ invalid_aws_external_account_creds_options_credential_source_invalid_region_url,
+ &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Invalid region url: invalid_region_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);
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ GRPC_ERROR_UNREF(error);
+}
+
+static void test_aws_external_account_creds_failure_invalid_url(void) {
+ grpc_core::ExecCtx exec_ctx;
+ grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
+ nullptr, nullptr};
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ invalid_aws_external_account_creds_options_credential_source_invalid_url,
+ &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid url: invalid_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);
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ GRPC_ERROR_UNREF(error);
+}
+
+static void test_aws_external_account_creds_failure_missing_role_name(void) {
+ grpc_core::ExecCtx exec_ctx;
+ grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
+ nullptr, nullptr};
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ invalid_aws_external_account_creds_options_credential_source_missing_role_name,
+ &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Missing role name when retrieving signing keys.");
+ 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);
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ GRPC_ERROR_UNREF(error);
+}
+
+static void
+test_aws_external_account_creds_failure_invalid_regional_cred_verification_url(
+ void) {
+ grpc_core::ExecCtx exec_ctx;
+ grpc_auth_metadata_context auth_md_ctx = {test_service_url, test_method,
+ nullptr, nullptr};
+ grpc_error* error = GRPC_ERROR_NONE;
+ grpc_core::Json credential_source = grpc_core::Json::Parse(
+ invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url,
+ &error);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ grpc_core::ExternalAccountCredentials::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;
+ };
+ auto creds =
+ grpc_core::AwsExternalAccountCredentials::Create(options, {}, &error);
+ GPR_ASSERT(creds != nullptr);
+ GPR_ASSERT(error == GRPC_ERROR_NONE);
+ GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
+ error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+ "Creating aws request signer failed.");
+ 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);
+ grpc_httpcli_set_override(aws_external_account_creds_httpcli_get_success,
+ aws_external_account_creds_httpcli_post_success);
+ run_request_metadata_test(creds.get(), auth_md_ctx, state);
+ grpc_core::ExecCtx::Get()->Flush();
+ grpc_httpcli_set_override(nullptr, nullptr);
+ GRPC_ERROR_UNREF(error);
+}
+
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(argc, argv);
grpc_init();
@@ -2571,6 +3045,15 @@ int main(int argc, char** argv) {
test_file_external_account_creds_success_format_json();
test_file_external_account_creds_failure_file_not_found();
test_file_external_account_creds_failure_invalid_json_content();
+ test_aws_external_account_creds_success();
+ test_aws_external_account_creds_success_path_region_env_keys_url();
+ test_aws_external_account_creds_success_path_region_url_keys_env();
+ test_aws_external_account_creds_success_path_region_env_keys_env();
+ test_aws_external_account_creds_failure_unmatched_environment_id();
+ test_aws_external_account_creds_failure_invalid_region_url();
+ test_aws_external_account_creds_failure_invalid_url();
+ test_aws_external_account_creds_failure_missing_role_name();
+ test_aws_external_account_creds_failure_invalid_regional_cred_verification_url();
grpc_shutdown();
return 0;
}
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index c65098ecf92..ebb64f0ce66 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -1914,6 +1914,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/aws_external_account_credentials.cc \
+src/core/lib/security/credentials/external/aws_external_account_credentials.h \
src/core/lib/security/credentials/external/aws_request_signer.cc \
src/core/lib/security/credentials/external/aws_request_signer.h \
src/core/lib/security/credentials/external/external_account_credentials.cc \
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index fd1cc399c75..d5135c5a7a8 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -1756,6 +1756,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/aws_external_account_credentials.cc \
+src/core/lib/security/credentials/external/aws_external_account_credentials.h \
src/core/lib/security/credentials/external/aws_request_signer.cc \
src/core/lib/security/credentials/external/aws_request_signer.h \
src/core/lib/security/credentials/external/external_account_credentials.cc \