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 \