From e7e29ccda5eb890d8215f0664a2dee6ac6c07644 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Fri, 29 Sep 2023 23:05:23 +0000 Subject: [PATCH 01/15] use http async client instead of libcurl Signed-off-by: Sunil Narasimhamurthy --- .gitignore | 1 + source/common/runtime/runtime_features.cc | 3 + source/extensions/common/aws/BUILD | 20 +- .../common/aws/credentials_provider.h | 2 + .../common/aws/credentials_provider_impl.cc | 255 ++- .../common/aws/credentials_provider_impl.h | 221 ++- .../extensions/common/aws/metadata_fetcher.cc | 161 ++ .../extensions/common/aws/metadata_fetcher.h | 90 ++ source/extensions/common/aws/utility.cc | 63 +- source/extensions/common/aws/utility.h | 15 + .../filters/http/aws_lambda/config.cc | 4 +- .../http/aws_request_signing/config.cc | 6 +- .../grpc_credentials/aws_iam/config.cc | 8 +- test/extensions/common/aws/BUILD | 21 + .../aws_metadata_fetcher_integration_test.cc | 7 +- .../aws/credentials_provider_impl_test.cc | 1376 +++++++++++++++-- .../common/aws/metadata_fetcher_test.cc | 273 ++++ test/extensions/common/aws/mocks.cc | 40 + test/extensions/common/aws/mocks.h | 48 +- test/extensions/common/aws/utility_test.cc | 42 + tools/spelling/spelling_dictionary.txt | 1 + 21 files changed, 2424 insertions(+), 233 deletions(-) create mode 100644 source/extensions/common/aws/metadata_fetcher.cc create mode 100644 source/extensions/common/aws/metadata_fetcher.h create mode 100644 test/extensions/common/aws/metadata_fetcher_test.cc diff --git a/.gitignore b/.gitignore index 6aad749804db6..18c3326cd76af 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ distribution/custom examples/websocket/certs /contrib/golang/**/test_data/go.sum /contrib/golang/**/test_data/*/go.sum +env/ diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index ceaf22acc2a47..1073144f20721 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -120,6 +120,9 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); // TODO(adisuissa): enable by default once this is tested in prod. FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); +// TODO(suniltheta): Once the newly added http async technique proves effective and +// is stabilized get rid of this feature flag and code path that relies on libcurl. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_use_libcurl_to_fetch_aws_credentials); // TODO(#10646) change to true when UHV is sufficiently tested // For more information about Universal Header Validation, please see // https://github.com/envoyproxy/envoy/issues/10646 diff --git a/source/extensions/common/aws/BUILD b/source/extensions/common/aws/BUILD index 96382e2095c20..39d5ff381ea89 100644 --- a/source/extensions/common/aws/BUILD +++ b/source/extensions/common/aws/BUILD @@ -40,6 +40,18 @@ envoy_cc_library( external_deps = ["abseil_optional"], ) +envoy_cc_library( + name = "metadata_fetcher_lib", + srcs = ["metadata_fetcher.cc"], + hdrs = ["metadata_fetcher.h"], + deps = [ + ":utility_lib", + "//envoy/upstream:cluster_manager_interface", + "//source/common/http:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "credentials_provider_impl_lib", srcs = ["credentials_provider_impl.cc"], @@ -47,12 +59,15 @@ envoy_cc_library( external_deps = ["abseil_time"], deps = [ ":credentials_provider_interface", - ":utility_lib", + ":metadata_fetcher_lib", "//envoy/api:api_interface", "//source/common/common:logger_lib", "//source/common/common:thread_lib", "//source/common/http:utility_lib", + "//source/common/init:target_lib", "//source/common/json:json_loader_lib", + "//source/common/runtime:runtime_features_lib", + "//source/common/tracing:http_tracer_lib", ], ) @@ -63,10 +78,13 @@ envoy_cc_library( external_deps = ["curl"], deps = [ "//envoy/http:message_interface", + "//envoy/upstream:cluster_manager_interface", "//source/common/common:empty_string", "//source/common/common:matchers_lib", "//source/common/common:utility_lib", "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + "//source/common/runtime:runtime_features_lib", ], ) diff --git a/source/extensions/common/aws/credentials_provider.h b/source/extensions/common/aws/credentials_provider.h index 9de0fe8b7a4df..dc06c0c779880 100644 --- a/source/extensions/common/aws/credentials_provider.h +++ b/source/extensions/common/aws/credentials_provider.h @@ -68,6 +68,8 @@ class CredentialsProvider { virtual Credentials getCredentials() PURE; }; +using CredentialsConstSharedPtr = std::shared_ptr; +using CredentialsConstUniquePtr = std::unique_ptr; using CredentialsProviderSharedPtr = std::shared_ptr; } // namespace Aws diff --git a/source/extensions/common/aws/credentials_provider_impl.cc b/source/extensions/common/aws/credentials_provider_impl.cc index 9cba9e82c265a..6a0a28c154102 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -9,6 +9,7 @@ #include "source/common/http/utility.h" #include "source/common/json/json_loader.h" #include "source/common/runtime/runtime_features.h" +#include "source/common/tracing/http_tracer_impl.h" #include "source/extensions/common/aws/utility.h" #include "absl/strings/str_format.h" @@ -44,14 +45,15 @@ constexpr char AWS_EC2_METADATA_DISABLED[] = "AWS_EC2_METADATA_DISABLED"; constexpr std::chrono::hours REFRESH_INTERVAL{1}; constexpr std::chrono::seconds REFRESH_GRACE_PERIOD{5}; -constexpr char EC2_METADATA_HOST[] = "169.254.169.254:80"; -constexpr char CONTAINER_METADATA_HOST[] = "169.254.170.2:80"; constexpr char EC2_IMDS_TOKEN_RESOURCE[] = "/latest/api/token"; constexpr char EC2_IMDS_TOKEN_HEADER[] = "X-aws-ec2-metadata-token"; constexpr char EC2_IMDS_TOKEN_TTL_HEADER[] = "X-aws-ec2-metadata-token-ttl-seconds"; constexpr char EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE[] = "21600"; constexpr char SECURITY_CREDENTIALS_PATH[] = "/latest/meta-data/iam/security-credentials"; +constexpr char EC2_METADATA_CLUSTER[] = "ec2_instance_metadata_server_internal"; +constexpr char CONTAINER_METADATA_CLUSTER[] = "ecs_task_metadata_server_internal"; + } // namespace Credentials EnvironmentCredentialsProvider::getCredentials() { @@ -80,6 +82,35 @@ void CachedCredentialsProviderBase::refreshIfNeeded() { } } +Credentials MetadataCredentialsProviderBase::getCredentials() { + refreshIfNeeded(); + if (!use_libcurl_ && context_ && tls_) { + // If server factor context was supplied then we would have thread local slot initialized. + return *(*tls_)->credentials_.get(); + } else { + return cached_credentials_; + } +} + +std::chrono::seconds MetadataCredentialsProviderBase::getCacheDuration() { + // TODO(suniltheta): This value should be configurable. + return std::chrono::seconds( + REFRESH_INTERVAL * 60 * 60 - + REFRESH_GRACE_PERIOD /*TODO: Add jitter from context.api().randomGenerator()*/); +} + +void MetadataCredentialsProviderBase::handleFetchDone() { + if (!use_libcurl_ && context_) { + if (init_target_) { + init_target_->ready(); + init_target_.reset(); + } + if (cache_duration_timer_ && !cache_duration_timer_->enabled()) { + cache_duration_timer_->enableTimer(cache_duration_); + } + } +} + bool CredentialsFileCredentialsProvider::needsRefresh() { return api_.timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; } @@ -171,24 +202,50 @@ void InstanceProfileCredentialsProvider::refresh() { // First request for a session TOKEN so that we can call EC2MetadataService securely. // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html Http::RequestMessageImpl token_req_message; + token_req_message.headers().setScheme(Http::Headers::get().SchemeValues.Http); token_req_message.headers().setMethod(Http::Headers::get().MethodValues.Put); token_req_message.headers().setHost(EC2_METADATA_HOST); token_req_message.headers().setPath(EC2_IMDS_TOKEN_RESOURCE); token_req_message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_TTL_HEADER), EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE); - const auto token_string = metadata_fetcher_(token_req_message); - if (token_string) { - ENVOY_LOG(debug, "Obtained token to make secure call to EC2MetadataService"); - fetchInstanceRole(token_string.value()); + + if (use_libcurl_ || !context_) { + // Using curl to fetch the AWS credentials where we first get the token. + const auto token_string = fetch_metadata_using_curl_(token_req_message); + if (token_string) { + ENVOY_LOG(debug, "Obtained token to make secure call to EC2MetadataService"); + fetchInstanceRole(std::move(token_string.value())); + } else { + ENVOY_LOG(warn, + "Failed to get token from EC2MetadataService, falling back to less secure way"); + fetchInstanceRole(std::move("")); + } } else { - ENVOY_LOG(warn, "Failed to get token from EC2MetadataService, falling back to less secure way"); - fetchInstanceRole(""); + // Stop any existing timer. + if (cache_duration_timer_ && cache_duration_timer_->enabled()) { + cache_duration_timer_->disableTimer(); + } + // Using Http async client to fetch the AWS credentials where we first get the token. + if (!metadata_fetcher_) { + metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); + } else { + metadata_fetcher_->cancel(); + } + on_async_fetch_cb_ = [this](const std::string&& arg) { + return this->fetchInstanceRoleAsync(std::move(arg)); + }; + continue_on_async_fetch_failure_ = true; // FIXME: Unit test this part + continue_on_async_fetch_failure_reason_ = + "Failed to get token from EC2MetadataService, falling back to less secure way"; + metadata_fetcher_->fetch(token_req_message, Tracing::NullSpan::instance(), *this); } } -void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string& token_string) { +void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string&& token_string, + bool async /*default = false*/) { // Discover the Role of this instance. Http::RequestMessageImpl message; + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost(EC2_METADATA_HOST); message.headers().setPath(SECURITY_CREDENTIALS_PATH); @@ -196,22 +253,47 @@ void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string& to message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_HEADER), StringUtil::trim(token_string)); } - const auto instance_role_string = metadata_fetcher_(message); - if (!instance_role_string) { - ENVOY_LOG(error, "Could not retrieve credentials listing from the EC2MetadataService"); - return; + + if (!async) { + // Using curl to fetch the Instance Role. + const auto instance_role_string = fetch_metadata_using_curl_(message); + if (!instance_role_string) { + ENVOY_LOG(error, "Could not retrieve credentials listing from the EC2MetadataService"); + return; + } + fetchCredentialFromInstanceRole(std::move(instance_role_string.value()), + std::move(token_string)); + } else { + // Using Http async client to fetch the Instance Role. + if (!metadata_fetcher_) { + metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); + } else { + metadata_fetcher_->cancel(); + } + on_async_fetch_cb_ = [this, token_string = std::move(token_string)](const std::string&& arg) { + return this->fetchCredentialFromInstanceRoleAsync(std::move(arg), std::move(token_string)); + }; + metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this); } - fetchCredentialFromInstanceRole(instance_role_string.value(), token_string); } void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( - const std::string& instance_role, const std::string& token_string) { + const std::string&& instance_role, const std::string&& token_string, + bool async /*default = false*/) { + if (instance_role.empty()) { + ENVOY_LOG(error, "No roles found to fetch AWS credentials from the EC2MetadataService"); + if (async) { + handleFetchDone(); + } return; } const auto instance_role_list = StringUtil::splitToken(StringUtil::trim(instance_role), "\n"); if (instance_role_list.empty()) { - ENVOY_LOG(error, "No AWS credentials were found in the EC2MetadataService"); + ENVOY_LOG(error, "No roles found to fetch AWS credentials from the EC2MetadataService"); + if (async) { + handleFetchDone(); + } return; } ENVOY_LOG(debug, "AWS credentials list:\n{}", instance_role); @@ -223,8 +305,8 @@ void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( std::string(instance_role_list[0].data(), instance_role_list[0].size()); ENVOY_LOG(debug, "AWS credentials path: {}", credential_path); - // Then fetch and parse the credentials Http::RequestMessageImpl message; + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost(EC2_METADATA_HOST); message.headers().setPath(credential_path); @@ -232,23 +314,45 @@ void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_HEADER), StringUtil::trim(token_string)); } - const auto credential_document = metadata_fetcher_(message); - if (!credential_document) { - ENVOY_LOG(error, "Could not load AWS credentials document from the EC2MetadataService"); - return; + + if (!async) { + // Fetch and parse the credentials. + const auto credential_document = fetch_metadata_using_curl_(message); + if (!credential_document) { + ENVOY_LOG(error, "Could not load AWS credentials document from the EC2MetadataService"); + return; + } + extractCredentials(std::move(credential_document.value())); + } else { + // Using Http async client to fetch and parse the AWS credentials. + if (!metadata_fetcher_) { + metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); + } else { + metadata_fetcher_->cancel(); + } + + on_async_fetch_cb_ = [this](const std::string&& arg) { + return this->extractCredentialsAsync(std::move(arg)); + }; + metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this); } - extractCredentials(credential_document.value()); } void InstanceProfileCredentialsProvider::extractCredentials( - const std::string& credential_document_value) { + const std::string&& credential_document_value, bool async /*default = false*/) { if (credential_document_value.empty()) { + if (async) { + handleFetchDone(); + } return; } Json::ObjectSharedPtr document_json; TRY_NEEDS_AUDIT { document_json = Json::Factory::loadFromString(credential_document_value); } END_TRY catch (EnvoyException& e) { ENVOY_LOG(error, "Could not parse AWS credentials document: {}", e.what()); + if (async) { + handleFetchDone(); + } return; } @@ -262,14 +366,40 @@ void InstanceProfileCredentialsProvider::extractCredentials( secret_access_key.empty() ? "" : "*****", AWS_SESSION_TOKEN, session_token.empty() ? "" : "*****"); - cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); last_updated_ = api_.timeSource().systemTime(); + if (!use_libcurl_ && context_) { + setCredentialsToAllThreads( + std::make_unique(access_key_id, secret_access_key, session_token)); + } else { + cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); + } + handleFetchDone(); +} + +void InstanceProfileCredentialsProvider::onMetadataSuccess(const std::string&& body) { + // TODO(suniltheta): increment fetch success stats + ENVOY_LOG(info, "AWS Instance metadata fetch success, calling callback func"); + on_async_fetch_cb_(std::move(body)); +} + +void InstanceProfileCredentialsProvider::onMetadataError(Failure) { + // TODO(suniltheta): increment fetch failed stats + if (continue_on_async_fetch_failure_) { + ENVOY_LOG(warn, continue_on_async_fetch_failure_reason_); + continue_on_async_fetch_failure_ = false; + continue_on_async_fetch_failure_reason_ = ""; + on_async_fetch_cb_(std::move("")); + } else { + ENVOY_LOG(error, "AWS Instance metadata fetch failure"); + handleFetchDone(); + } } bool TaskRoleCredentialsProvider::needsRefresh() { const auto now = api_.timeSource().systemTime(); - return (now - last_updated_ > REFRESH_INTERVAL) || - (expiration_time_ - now < REFRESH_GRACE_PERIOD); + bool needs_refresh = + (now - last_updated_ > REFRESH_INTERVAL) || (expiration_time_ - now < REFRESH_GRACE_PERIOD); + return needs_refresh; } void TaskRoleCredentialsProvider::refresh() { @@ -280,26 +410,48 @@ void TaskRoleCredentialsProvider::refresh() { Http::Utility::extractHostPathFromUri(credential_uri_, host, path); Http::RequestMessageImpl message; + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost(host); message.headers().setPath(path); message.headers().setCopy(Http::CustomHeaders::get().Authorization, authorization_token_); - const auto credential_document = metadata_fetcher_(message); - if (!credential_document) { - ENVOY_LOG(error, "Could not load AWS credentials document from the task role"); - return; + if (use_libcurl_ || !context_) { + // Using curl to fetch the AWS credentials. + const auto credential_document = fetch_metadata_using_curl_(message); + if (!credential_document) { + ENVOY_LOG(error, "Could not load AWS credentials document from the task role"); + return; + } + extractCredentials(std::move(credential_document.value())); + } else { + // Stop any existing timer. + if (cache_duration_timer_ && cache_duration_timer_->enabled()) { + cache_duration_timer_->disableTimer(); + } + // Using Http async client to fetch the AWS credentials. + if (!metadata_fetcher_) { + metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); + } else { + metadata_fetcher_->cancel(); + } + on_async_fetch_cb_ = [this](const std::string&& arg) { + return this->extractCredentials(std::move(arg)); + }; + metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this); } - extractCredentials(credential_document.value()); } -void TaskRoleCredentialsProvider::extractCredentials(const std::string& credential_document_value) { +void TaskRoleCredentialsProvider::extractCredentials( + const std::string&& credential_document_value) { if (credential_document_value.empty()) { + handleFetchDone(); return; } Json::ObjectSharedPtr document_json; TRY_NEEDS_AUDIT { document_json = Json::Factory::loadFromString(credential_document_value); } END_TRY catch (EnvoyException& e) { ENVOY_LOG(error, "Could not parse AWS credentials document from the task role: {}", e.what()); + handleFetchDone(); return; } @@ -322,7 +474,25 @@ void TaskRoleCredentialsProvider::extractCredentials(const std::string& credenti } last_updated_ = api_.timeSource().systemTime(); - cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); + if (!use_libcurl_ && context_) { + setCredentialsToAllThreads( + std::make_unique(access_key_id, secret_access_key, session_token)); + } else { + cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); + } + handleFetchDone(); +} + +void TaskRoleCredentialsProvider::onMetadataSuccess(const std::string&& body) { + // TODO(suniltheta): increment fetch success stats + ENVOY_LOG(debug, "AWS metadata fetch success, calling callback func"); + on_async_fetch_cb_(std::move(body)); +} + +void TaskRoleCredentialsProvider::onMetadataError(Failure) { + // TODO(suniltheta): increment fetch failed stats + ENVOY_LOG(error, "AWS metadata fetch failure"); + handleFetchDone(); } Credentials CredentialsProviderChain::getCredentials() { @@ -338,7 +508,8 @@ Credentials CredentialsProviderChain::getCredentials() { } DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( - Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, const CredentialsProviderChainFactories& factories) { ENVOY_LOG(debug, "Using environment credentials provider"); add(factories.createEnvironmentCredentialsProvider()); @@ -358,7 +529,9 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( if (!relative_uri.empty()) { const auto uri = absl::StrCat(CONTAINER_METADATA_HOST, relative_uri); ENVOY_LOG(debug, "Using task role credentials provider with URI: {}", uri); - add(factories.createTaskRoleCredentialsProvider(api, metadata_fetcher, uri)); + add(factories.createTaskRoleCredentialsProvider(api, context, fetch_metadata_using_curl, + MetadataFetcher::create, + CONTAINER_METADATA_CLUSTER, uri)); } else if (!full_uri.empty()) { const auto authorization_token = absl::NullSafeStringView(std::getenv(AWS_CONTAINER_AUTHORIZATION_TOKEN)); @@ -367,15 +540,19 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( "Using task role credentials provider with URI: " "{} and authorization token", full_uri); - add(factories.createTaskRoleCredentialsProvider(api, metadata_fetcher, full_uri, - authorization_token)); + add(factories.createTaskRoleCredentialsProvider( + api, context, fetch_metadata_using_curl, MetadataFetcher::create, + CONTAINER_METADATA_CLUSTER, full_uri, authorization_token)); } else { ENVOY_LOG(debug, "Using task role credentials provider with URI: {}", full_uri); - add(factories.createTaskRoleCredentialsProvider(api, metadata_fetcher, full_uri)); + add(factories.createTaskRoleCredentialsProvider(api, context, fetch_metadata_using_curl, + MetadataFetcher::create, + CONTAINER_METADATA_CLUSTER, full_uri)); } } else if (metadata_disabled != TRUE) { ENVOY_LOG(debug, "Using instance profile credentials provider"); - add(factories.createInstanceProfileCredentialsProvider(api, metadata_fetcher)); + add(factories.createInstanceProfileCredentialsProvider( + api, context, fetch_metadata_using_curl, MetadataFetcher::create, EC2_METADATA_CLUSTER)); } } diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index 0b207620ad882..d86eded9c207b 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -1,14 +1,24 @@ #pragma once #include +#include +#include #include "envoy/api/api.h" +#include "envoy/common/optref.h" #include "envoy/event/timer.h" #include "envoy/http/message.h" +#include "envoy/server/factory_context.h" +#include "source/common/common/lock_guard.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" +#include "source/common/init/target_impl.h" +#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" #include "source/extensions/common/aws/credentials_provider.h" +#include "source/extensions/common/aws/metadata_fetcher.h" #include "absl/strings/string_view.h" @@ -17,6 +27,18 @@ namespace Extensions { namespace Common { namespace Aws { +namespace { +constexpr char EC2_METADATA_HOST[] = "169.254.169.254:80"; +constexpr char CONTAINER_METADATA_HOST[] = "169.254.170.2:80"; +}; // namespace + +/** + * CreateMetadataFetcherCb is a callback interface for creating a MetadataFetcher instance. + */ +using CreateMetadataFetcherCb = + std::function; +using ServerFactoryContextOptRef = OptRef; + /** * Retrieve AWS credentials from the environment variables. * @@ -68,14 +90,111 @@ class CredentialsFileCredentialsProvider : public CachedCredentialsProviderBase class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { public: - using MetadataFetcher = std::function(Http::RequestMessage&)>; + using FetchMetadataUsingCurl = std::function(Http::RequestMessage&)>; + using OnAsyncFetchCb = std::function; + + MetadataCredentialsProviderBase(Api::Api& api, ServerFactoryContextOptRef context, + const FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, + absl::string_view cluster_name, absl::string_view host) + : api_(api), context_(context), fetch_metadata_using_curl_(fetch_metadata_using_curl), + create_metadata_fetcher_cb_(create_metadata_fetcher_cb), + cluster_name_(std::string(cluster_name)), cache_duration_(getCacheDuration()), + debug_name_(absl::StrCat("Fetching aws credentials from cluster=", cluster_name)), + use_libcurl_(Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials")) { + + if (!use_libcurl_ && context_) { + context_->mainThreadDispatcher().post([this, host]() { + if (!Utility::addInternalClusterStatic(context_->clusterManager(), cluster_name_, "STATIC", + host)) { + ENVOY_LOG(critical, + "Failed to add [STATIC cluster = {} with address = {}] or cluster not found", + cluster_name_, host); + return; + } + }); + + tls_ = + ThreadLocal::TypedSlot::makeUnique(context_->threadLocal()); + tls_->set([](Envoy::Event::Dispatcher&) { + return std::make_shared(); + }); + + cache_duration_timer_ = context_->mainThreadDispatcher().createTimer([this]() -> void { + const Thread::LockGuard lock(lock_); + refresh(); + }); + + // Register with init_manager, force the listener to wait for fetching (refresh). + init_target_ = + std::make_unique(debug_name_, [this]() -> void { refresh(); }); + context_->initManager().add(*init_target_); + } + } - MetadataCredentialsProviderBase(Api::Api& api, const MetadataFetcher& metadata_fetcher) - : api_(api), metadata_fetcher_(metadata_fetcher) {} + Credentials getCredentials() override; + + const std::string& clusterName() { return cluster_name_; } + + // Handle fetch done. + void handleFetchDone(); + + // Get the Metadata credentials cache duration. + static std::chrono::seconds getCacheDuration(); protected: + struct ThreadLocalCredentialsCache : public ThreadLocal::ThreadLocalObject { + ThreadLocalCredentialsCache() { + // Creating empty credentials as default. + credentials_ = std::make_shared(); + } + // The credentials object. + CredentialsConstSharedPtr credentials_; + }; + + // Set Credentials shared_ptr on all threads. + void setCredentialsToAllThreads(CredentialsConstUniquePtr&& creds) { + CredentialsConstSharedPtr shared_credentials = std::move(creds); + if (tls_) { + tls_->runOnAllThreads([shared_credentials](OptRef obj) { + obj->credentials_ = shared_credentials; + }); + } + } + Api::Api& api_; - MetadataFetcher metadata_fetcher_; + // The optional server factory context. + ServerFactoryContextOptRef context_; + // Store the method to fetch metadata from libcurl (deprecated) + FetchMetadataUsingCurl fetch_metadata_using_curl_; + // The callback used to create a MetadataFetcher instance. + CreateMetadataFetcherCb create_metadata_fetcher_cb_; + // The cluster name to use for internal static cluster pointing towards the credentials provider. + std::string cluster_name_; + // The cache duration of the fetched credentials. + const std::chrono::seconds cache_duration_; + // The thread local slot for cache. + ThreadLocal::TypedSlotPtr tls_; + // The timer to trigger fetch due to cache duration. + Envoy::Event::TimerPtr cache_duration_timer_; + // The Metadata fetcher object. + MetadataFetcherPtr metadata_fetcher_; + + OnAsyncFetchCb on_async_fetch_cb_; + bool continue_on_async_fetch_failure_ = false; + std::string continue_on_async_fetch_failure_reason_ = ""; + SystemTime last_updated_; + Credentials cached_credentials_; + Thread::MutexBasicLockable lock_; + + // The init target. + std::unique_ptr init_target_; + + // Used in logs. + const std::string debug_name_; + + const bool use_libcurl_; }; /** @@ -83,17 +202,38 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { * * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials */ -class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBase { +class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBase, + public MetadataFetcher::MetadataReceiver { public: - InstanceProfileCredentialsProvider(Api::Api& api, const MetadataFetcher& metadata_fetcher) - : MetadataCredentialsProviderBase(api, metadata_fetcher) {} + InstanceProfileCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, + const FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, + absl::string_view cluster_name) + : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, + create_metadata_fetcher_cb, cluster_name, + EC2_METADATA_HOST) {} + + // Following functions are for MetadataFetcher::MetadataReceiver interface + void onMetadataSuccess(const std::string&& body) override; + void onMetadataError(Failure reason) override; private: bool needsRefresh() override; void refresh() override; - void fetchInstanceRole(const std::string& token); - void fetchCredentialFromInstanceRole(const std::string& instance_role, const std::string& token); - void extractCredentials(const std::string& credential_document_value); + void fetchInstanceRole(const std::string&& token, bool async = false); + void fetchInstanceRoleAsync(const std::string&& token) { + fetchInstanceRole(std::move(token), true); + } + void fetchCredentialFromInstanceRole(const std::string&& instance_role, const std::string&& token, + bool async = false); + void fetchCredentialFromInstanceRoleAsync(const std::string&& instance_role, + const std::string&& token) { + fetchCredentialFromInstanceRole(std::move(instance_role), std::move(token), true); + } + void extractCredentials(const std::string&& credential_document_value, bool async = false); + void extractCredentialsAsync(const std::string&& credential_document_value) { + extractCredentials(std::move(credential_document_value), true); + } }; /** @@ -101,13 +241,23 @@ class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBas * * https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#enable_task_iam_roles */ -class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase { +class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase, + public MetadataFetcher::MetadataReceiver { public: - TaskRoleCredentialsProvider(Api::Api& api, const MetadataFetcher& metadata_fetcher, + TaskRoleCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, + const FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view credential_uri, - absl::string_view authorization_token = {}) - : MetadataCredentialsProviderBase(api, metadata_fetcher), credential_uri_(credential_uri), - authorization_token_(authorization_token) {} + absl::string_view authorization_token = {}, + absl::string_view cluster_name = {}) + : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, + create_metadata_fetcher_cb, cluster_name, + CONTAINER_METADATA_HOST), + credential_uri_(credential_uri), authorization_token_(authorization_token) {} + + // Following functions are for MetadataFetcher::MetadataReceiver interface + void onMetadataSuccess(const std::string&& body) override; + void onMetadataError(Failure reason) override; private: SystemTime expiration_time_; @@ -116,7 +266,7 @@ class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase { bool needsRefresh() override; void refresh() override; - void extractCredentials(const std::string& credential_document_value); + void extractCredentials(const std::string&& credential_document_value); }; /** @@ -147,12 +297,16 @@ class CredentialsProviderChainFactories { createCredentialsFileCredentialsProvider(Api::Api& api) const PURE; virtual CredentialsProviderSharedPtr createTaskRoleCredentialsProvider( - Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, absl::string_view credential_uri, absl::string_view authorization_token = {}) const PURE; virtual CredentialsProviderSharedPtr createInstanceProfileCredentialsProvider( - Api::Api& api, - const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher) const PURE; + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, + absl::string_view cluster_name) const PURE; }; /** @@ -165,11 +319,13 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, public CredentialsProviderChainFactories { public: DefaultCredentialsProviderChain( - Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher) - : DefaultCredentialsProviderChain(api, metadata_fetcher, *this) {} + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl) + : DefaultCredentialsProviderChain(api, context, fetch_metadata_using_curl, *this) {} DefaultCredentialsProviderChain( - Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, const CredentialsProviderChainFactories& factories); private: @@ -183,19 +339,28 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, } CredentialsProviderSharedPtr createTaskRoleCredentialsProvider( - Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, absl::string_view credential_uri, absl::string_view authorization_token = {}) const override { - return std::make_shared(api, metadata_fetcher, credential_uri, - authorization_token); + return std::make_shared(api, context, fetch_metadata_using_curl, + create_metadata_fetcher_cb, credential_uri, + authorization_token, cluster_name); } CredentialsProviderSharedPtr createInstanceProfileCredentialsProvider( - Api::Api& api, - const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher) const override { - return std::make_shared(api, metadata_fetcher); + Api::Api& api, ServerFactoryContextOptRef context, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, + absl::string_view cluster_name) const override { + return std::make_shared( + api, context, fetch_metadata_using_curl, create_metadata_fetcher_cb, cluster_name); } }; +using InstanceProfileCredentialsProviderPtr = std::shared_ptr; +using TaskRoleCredentialsProviderPtr = std::shared_ptr; + } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/source/extensions/common/aws/metadata_fetcher.cc b/source/extensions/common/aws/metadata_fetcher.cc new file mode 100644 index 0000000000000..4a3ddc64a041a --- /dev/null +++ b/source/extensions/common/aws/metadata_fetcher.cc @@ -0,0 +1,161 @@ +#include "source/extensions/common/aws/metadata_fetcher.h" + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/core/v3/http_uri.pb.h" + +#include "source/common/common/enum_to_int.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Aws { + +namespace { + +class MetadataFetcherImpl : public MetadataFetcher, + public Logger::Loggable, + public Http::AsyncClient::Callbacks { + +public: + MetadataFetcherImpl(Upstream::ClusterManager& cm, absl::string_view cluster_name) + : cm_(cm), cluster_name_(std::string(cluster_name)) {} + + ~MetadataFetcherImpl() override { cancel(); } + + void cancel() override { + if (request_ && !complete_) { + request_->cancel(); + ENVOY_LOG(debug, "fetch AWS Metadata [cluster = {}]: cancelled", cluster_name_); + } + reset(); + } + + void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, + MetadataFetcher::MetadataReceiver& receiver) override { + complete_ = false; + if (!receiver_) { + receiver_ = &receiver; + } + const auto thread_local_cluster = cm_.getThreadLocalCluster(cluster_name_); + if (thread_local_cluster == nullptr) { + ENVOY_LOG(error, "{} AWS Metadata failed: [cluster = {}] not found", __func__, cluster_name_); + complete_ = true; + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::MissingConfig); + reset(); + return; + } + + constexpr uint64_t MAX_RETRIES = 3; + constexpr uint64_t RETRY_DELAY = 1000; + constexpr uint64_t TIMEOUT = 5 * 1000; + + const auto host_attributes = Http::Utility::parseAuthority(message.headers().getHostValue()); + const auto host = host_attributes.host_; + const auto path = message.headers().getPathValue(); + const auto scheme = message.headers().getSchemeValue(); + const auto method = message.headers().getMethodValue(); + ENVOY_LOG(debug, "fetch AWS Metadata at [uri = {}]: start from cluster {}", + fmt::format("{}://{}{}", scheme, host, path), cluster_name_); + + Http::RequestHeaderMapPtr headersPtr = + Envoy::Http::createHeaderMap( + {{Envoy::Http::Headers::get().Method, std::string(method)}, + {Envoy::Http::Headers::get().Host, std::string(host)}, + {Envoy::Http::Headers::get().Scheme, std::string(scheme)}, + {Envoy::Http::Headers::get().Path, std::string(path)}}); + + // Copy the remaining headers. + message.headers().iterate( + [&headersPtr](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate { + // Skip pseudo-headers + if (!entry.key().getStringView().empty() && entry.key().getStringView()[0] == ':') { + return Http::HeaderMap::Iterate::Continue; + } + headersPtr->addCopy(Http::LowerCaseString(entry.key().getStringView()), + entry.value().getStringView()); + return Http::HeaderMap::Iterate::Continue; + }); + + auto messagePtr = std::make_unique(std::move(headersPtr)); + + auto options = Http::AsyncClient::RequestOptions() + .setTimeout(std::chrono::milliseconds(TIMEOUT)) + .setParentSpan(parent_span) + .setSendXff(false) + .setChildSpanName("AWS Metadata Fetch"); + + envoy::config::route::v3::RetryPolicy route_retry_policy; + route_retry_policy.mutable_num_retries()->set_value(MAX_RETRIES); + route_retry_policy.mutable_per_try_timeout()->CopyFrom( + Protobuf::util::TimeUtil::MillisecondsToDuration(TIMEOUT)); + route_retry_policy.mutable_per_try_idle_timeout()->CopyFrom( + Protobuf::util::TimeUtil::MillisecondsToDuration(RETRY_DELAY)); + route_retry_policy.set_retry_on("5xx,gateway-error,connect-failure,reset,refused-stream"); + + options.setRetryPolicy(route_retry_policy); + options.setBufferBodyForRetry(true); + request_ = thread_local_cluster->httpAsyncClient().send(std::move(messagePtr), *this, options); + } + + // HTTP async receive method on success. + void onSuccess(const Http::AsyncClient::Request&, Http::ResponseMessagePtr&& response) override { + complete_ = true; + const uint64_t status_code = Http::Utility::getResponseStatus(response->headers()); + if (status_code == enumToInt(Http::Code::OK)) { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: success", __func__, cluster_name_); + if (response->body().length() != 0) { + const auto body = response->bodyAsString(); + receiver_->onMetadataSuccess(std::move(body)); + } else { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: body is empty", __func__, + cluster_name_); + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } + } else { + if (response->body().length() != 0) { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: response status code {}, body: {}", + __func__, cluster_name_, status_code, response->bodyAsString()); + } else { + ENVOY_LOG(debug, + "{}: fetch AWS Metadata [cluster = {}]: response status code {}, body is empty", + __func__, cluster_name_, status_code); + } + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } + reset(); + } + + // HTTP async receive method on failure. + void onFailure(const Http::AsyncClient::Request&, + Http::AsyncClient::FailureReason reason) override { + ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: network error {}", __func__, + cluster_name_, enumToInt(reason)); + complete_ = true; + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + reset(); + } + + void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} + +private: + Upstream::ClusterManager& cm_; + bool complete_{}; + MetadataFetcher::MetadataReceiver* receiver_{}; + std::string cluster_name_; + Http::AsyncClient::Request* request_{}; + + void reset() { request_ = nullptr; } +}; +} // namespace + +MetadataFetcherPtr MetadataFetcher::create(Upstream::ClusterManager& cm, + absl::string_view cluster_name) { + return std::make_unique(cm, cluster_name); +} +} // namespace Aws +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/aws/metadata_fetcher.h b/source/extensions/common/aws/metadata_fetcher.h new file mode 100644 index 0000000000000..dca68c86bdd1d --- /dev/null +++ b/source/extensions/common/aws/metadata_fetcher.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/http/message.h" +#include "envoy/upstream/cluster_manager.h" + +#include "source/common/http/message_impl.h" +#include "source/extensions/common/aws/utility.h" + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Aws { + +class MetadataFetcher; +using MetadataFetcherPtr = std::unique_ptr; + +/** + * MetadataFetcher interface can be used to retrieve AWS Metadata from various providers. + * An instance of this interface is designed to retrieve one AWS Metadata at a time. + * The implementation of AWS Metadata Fetcher is similar to JwksFetcher. + */ + +class MetadataFetcher { +public: + class MetadataReceiver { + public: + enum class Failure { + /* A network error occurred causing AWS Metadata retrieval failure. */ + Network, + /* A failure occurred when trying to parse the retrieved AWS Metadata data. */ + InvalidMetadata, + /* A missing config causing AWS Metadata retrieval failure. */ + MissingConfig, + }; + + virtual ~MetadataReceiver() = default; + + /** + * @brief Successful retrieval callback of returned AWS Metadata. + * @param body Fetched AWS Metadata. + */ + virtual void onMetadataSuccess(const std::string&& body) PURE; + + /** + * @brief Retrieval error callback. + * @param reason the failure reason. + */ + virtual void onMetadataError(Failure reason) PURE; + }; + + virtual ~MetadataFetcher() = default; + + /** + * @brief Cancel any in-flight request. + */ + virtual void cancel() PURE; + + /** + * @brief Retrieve a AWS Metadata from a remote HTTP host. + * At most one outstanding request may be in-flight. + * i.e. from the invocation of `fetch()` until either + * a callback or `cancel()` is invoked, no additional + * `fetch()` may be issued. The URI to fetch is to pre + * determined based on the credentials provider source. + * + * @param receiver the receiver of the fetched AWS Metadata or error + */ + virtual void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, + MetadataReceiver& receiver) PURE; + + /** + * @brief Factory method for creating a Metadata Fetcher. + * + * @param cm the cluster manager to use during AWS Metadata retrieval + * @param provider the AWS Metadata provider + * @return a MetadataFetcher instance + */ + static MetadataFetcherPtr create(Upstream::ClusterManager& cm, absl::string_view cluster_name); +}; +} // namespace Aws +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index 9d5669b2113a0..bb0fad253e5d5 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -1,13 +1,18 @@ #include "source/extensions/common/aws/utility.h" +#include "envoy/upstream/cluster_manager.h" + #include "source/common/common/empty_string.h" #include "source/common/common/fmt.h" #include "source/common/common/utility.h" +#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/protobuf/utility.h" #include "absl/strings/match.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "curl/curl.h" +#include "fmt/printf.h" namespace Envoy { namespace Extensions { @@ -236,8 +241,9 @@ absl::optional Utility::fetchMetadata(Http::RequestMessage& message const auto host = message.headers().getHostValue(); const auto path = message.headers().getPathValue(); const auto method = message.headers().getMethodValue(); + const auto scheme = message.headers().getSchemeValue(); - const std::string url = fmt::format("http://{}{}", host, path); + const std::string url = fmt::format("{}://{}{}", scheme, host, path); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT.count()); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); @@ -294,6 +300,61 @@ absl::optional Utility::fetchMetadata(Http::RequestMessage& message return buffer.empty() ? absl::nullopt : absl::optional(buffer); } +bool Utility::addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, + absl::string_view cluster_type, absl::string_view host) { + // Check if local cluster exists with that name. + if (cm.getThreadLocalCluster(cluster_name) == nullptr) { + // Make sure we run this on main thread. + TRY_ASSERT_MAIN_THREAD { + envoy::config::cluster::v3::Cluster cluster; + const auto host_attributes = Http::Utility::parseAuthority(host); + const auto host = host_attributes.host_; + if (!host_attributes.port_) { + ENVOY_LOG_MISC( + error, "Failed to add internal cluster with port value missing from host: {}", host); + return false; + } + const auto port = host_attributes.port_.value(); + MessageUtil::loadFromYaml(fmt::format(R"EOF( +name: {} +type: {} +connectTimeout: 5s +lb_policy: ROUND_ROBIN +loadAssignment: + clusterName: {} + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: {} + portValue: {} +typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http_protocol_options: + accept_http_10: true + )EOF", + cluster_name, cluster_type, cluster_name, host, port), + cluster, ProtobufMessage::getNullValidationVisitor()); + + // TODO(suniltheta): use random number generator here for cluster version. + cm.addOrUpdateCluster(cluster, "12345"); + ENVOY_LOG_MISC(info, + "Added a {} internal cluster [name: {}, address:{}:{}] to fetch aws " + "credentials", + cluster_type, cluster_name, host, port); + } + END_TRY + CATCH(const EnvoyException& e, { + ENVOY_LOG_MISC(error, "Failed to add internal cluster {}: {}", cluster_name, e.what()); + return false; + }); + } + return true; +} + } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/source/extensions/common/aws/utility.h b/source/extensions/common/aws/utility.h index 2ec7cae045cd3..d0a38a9ecc12c 100644 --- a/source/extensions/common/aws/utility.h +++ b/source/extensions/common/aws/utility.h @@ -4,6 +4,7 @@ #include "source/common/common/matchers.h" #include "source/common/http/headers.h" +#include "source/common/http/utility.h" namespace Envoy { namespace Extensions { @@ -92,6 +93,20 @@ class Utility { * gRPC auth plugins that are able to schedule blocking plugins on a different thread. */ static absl::optional fetchMetadata(Http::RequestMessage& message); + + /** + * @brief Adds a static cluster towards a credentials provider + * to fetch the credentials using http async client. + * + * @param cm cluster manager + * @param cluster_name a name for credentials provider cluster + * @param cluster_type STATIC or STRICT_DNS or LOGICAL_DNS etc + * @param host provider's IP (STATIC cluster) or URL (STRICT_DNS) + * @return true if successfully added the cluster + * @return false if failed to add the cluster + */ + static bool addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, + absl::string_view cluster_type, absl::string_view host); }; } // namespace Aws diff --git a/source/extensions/filters/http/aws_lambda/config.cc b/source/extensions/filters/http/aws_lambda/config.cc index de5a1cfc0a394..adfbf2c277fe6 100644 --- a/source/extensions/filters/http/aws_lambda/config.cc +++ b/source/extensions/filters/http/aws_lambda/config.cc @@ -1,5 +1,6 @@ #include "source/extensions/filters/http/aws_lambda/config.h" +#include "envoy/common/optref.h" #include "envoy/extensions/filters/http/aws_lambda/v3/aws_lambda.pb.validate.h" #include "envoy/registry/registry.h" #include "envoy/stats/scope.h" @@ -45,7 +46,8 @@ Http::FilterFactoryCb AwsLambdaFilterFactory::createFilterFactoryFromProtoTyped( auto credentials_provider = std::make_shared( - context.api(), Extensions::Common::Aws::Utility::fetchMetadata); + context.api(), makeOptRef(context.getServerFactoryContext()), + Extensions::Common::Aws::Utility::fetchMetadata); auto signer = std::make_shared( service_name, region, std::move(credentials_provider), diff --git a/source/extensions/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index c277f8e600c64..6ad378b41f42f 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -1,5 +1,6 @@ #include "source/extensions/filters/http/aws_request_signing/config.h" +#include "envoy/common/optref.h" #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.validate.h" #include "envoy/registry/registry.h" @@ -20,7 +21,8 @@ Http::FilterFactoryCb AwsRequestSigningFilterFactory::createFilterFactoryFromPro auto credentials_provider = std::make_shared( - context.api(), Extensions::Common::Aws::Utility::fetchMetadata); + context.api(), makeOptRef(context.getServerFactoryContext()), + Extensions::Common::Aws::Utility::fetchMetadata); const auto matcher_config = Extensions::Common::Aws::AwsSigV4HeaderExclusionVector( config.match_excluded_headers().begin(), config.match_excluded_headers().end()); auto signer = std::make_unique( @@ -41,7 +43,7 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { auto credentials_provider = std::make_shared( - context.api(), Extensions::Common::Aws::Utility::fetchMetadata); + context.api(), makeOptRef(context), Extensions::Common::Aws::Utility::fetchMetadata); const auto matcher_config = Extensions::Common::Aws::AwsSigV4HeaderExclusionVector( per_route_config.aws_request_signing().match_excluded_headers().begin(), per_route_config.aws_request_signing().match_excluded_headers().end()); diff --git a/source/extensions/grpc_credentials/aws_iam/config.cc b/source/extensions/grpc_credentials/aws_iam/config.cc index 42f7f28a9c41e..87fe1c408e8dc 100644 --- a/source/extensions/grpc_credentials/aws_iam/config.cc +++ b/source/extensions/grpc_credentials/aws_iam/config.cc @@ -44,8 +44,14 @@ std::shared_ptr AwsIamGrpcCredentialsFactory::getChann const auto& config = Envoy::MessageUtil::downcastAndValidate< const envoy::config::grpc_credential::v3::AwsIamConfig&>( *config_message, ProtobufMessage::getNullValidationVisitor()); + // TODO(suniltheta): Due to the reasons explained in + // https://github.com/envoyproxy/envoy/issues/27586 this aws iam plugin is not able to + // utilize http async client to fetch AWS credentials. For time being this is still using + // libcurl to fetch the credentials. To fully get rid of curl, need to address the below + // usage of AWS credentials common utils. Until then we are setting nullopt for server + // factory context. auto credentials_provider = std::make_shared( - api, Common::Aws::Utility::fetchMetadata); + api, absl::nullopt /*Empty factory context*/, Common::Aws::Utility::fetchMetadata); auto signer = std::make_unique( config.service_name(), getRegion(config), credentials_provider, api.timeSource(), // TODO: extend API to allow specifying header exclusion. ref: diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index 55ebdf79f19fa..493873d4ed466 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -14,8 +14,11 @@ envoy_cc_mock( srcs = ["mocks.cc"], hdrs = ["mocks.h"], deps = [ + "//source/common/http:message_lib", "//source/extensions/common/aws:credentials_provider_interface", + "//source/extensions/common/aws:metadata_fetcher_lib", "//source/extensions/common/aws:signer_interface", + "//test/mocks/upstream:cluster_manager_mocks", ], ) @@ -37,6 +40,7 @@ envoy_cc_test( srcs = ["utility_test.cc"], deps = [ "//source/extensions/common/aws:utility_lib", + "//test/extensions/common/aws:aws_mocks", "//test/test_common:utility_lib", ], ) @@ -50,15 +54,32 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "metadata_fetcher_test", + srcs = ["metadata_fetcher_test.cc"], + deps = [ + "//source/extensions/common/aws:metadata_fetcher_lib", + "//test/extensions/common/aws:aws_mocks", + "//test/mocks/api:api_mocks", + "//test/mocks/event:event_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "credentials_provider_impl_test", srcs = ["credentials_provider_impl_test.cc"], deps = [ "//source/extensions/common/aws:credentials_provider_impl_lib", + "//source/extensions/common/aws:metadata_fetcher_lib", "//test/extensions/common/aws:aws_mocks", "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/runtime:runtime_mocks", + "//test/mocks/server:factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc index 6e71984ee8a7a..89946ef92e063 100644 --- a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc +++ b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc @@ -99,7 +99,7 @@ TEST_F(AwsMetadataIntegrationTestSuccess, Success) { const auto authority = fmt::format("{}:{}", Network::Test::getLoopbackAddressUrlString(version_), lookupPort("listener_0")); auto headers = Http::RequestHeaderMapPtr{new Http::TestRequestHeaderMapImpl{ - {":path", "/"}, {":authority", authority}, {":method", "GET"}}}; + {":path", "/"}, {":authority", authority}, {":scheme", "http"}, {":method", "GET"}}}; Http::RequestMessageImpl message(std::move(headers)); const auto response = Utility::fetchMetadata(message); @@ -116,6 +116,7 @@ TEST_F(AwsMetadataIntegrationTestSuccess, AuthToken) { auto headers = Http::RequestHeaderMapPtr{ new Http::TestRequestHeaderMapImpl{{":path", "/"}, {":authority", authority}, + {":scheme", "http"}, {":method", "GET"}, {"authorization", "AUTH_TOKEN"}}}; Http::RequestMessageImpl message(std::move(headers)); @@ -155,6 +156,7 @@ TEST_F(AwsMetadataIntegrationTestSuccess, Redirect) { auto headers = Http::RequestHeaderMapPtr{ new Http::TestRequestHeaderMapImpl{{":path", "/redirect"}, {":authority", authority}, + {":scheme", "http"}, {":method", "GET"}, {"authorization", "AUTH_TOKEN"}}}; Http::RequestMessageImpl message(std::move(headers)); @@ -182,6 +184,7 @@ TEST_F(AwsMetadataIntegrationTestFailure, Failure) { auto headers = Http::RequestHeaderMapPtr{ new Http::TestRequestHeaderMapImpl{{":path", "/"}, {":authority", authority}, + {":scheme", "http"}, {":method", "GET"}, {"authorization", "AUTH_TOKEN"}}}; @@ -210,7 +213,7 @@ TEST_F(AwsMetadataIntegrationTestTimeout, Timeout) { const auto authority = fmt::format("{}:{}", Network::Test::getLoopbackAddressUrlString(version_), lookupPort("listener_0")); auto headers = Http::RequestHeaderMapPtr{new Http::TestRequestHeaderMapImpl{ - {":path", "/"}, {":authority", authority}, {":method", "GET"}}}; + {":path", "/"}, {":authority", authority}, {":scheme", "http"}, {":method", "GET"}}}; Http::RequestMessageImpl message(std::move(headers)); const auto start_time = timeSystem().monotonicTime(); diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index a93c9ccf770bd..1eb590c1c2e10 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -1,18 +1,27 @@ +#include +#include + #include "source/extensions/common/aws/credentials_provider_impl.h" +#include "source/extensions/common/aws/metadata_fetcher.h" #include "test/extensions/common/aws/mocks.h" #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/runtime/mocks.h" +#include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" +using Envoy::Extensions::Common::Aws::MetadataFetcher; +using Envoy::Extensions::Common::Aws::MetadataFetcherPtr; +using Envoy::Extensions::Common::Aws::MockMetadataFetcher; using testing::_; using testing::InSequence; using testing::NiceMock; using testing::Ref; using testing::Return; +using testing::Throw; namespace Envoy { namespace Extensions { @@ -48,6 +57,22 @@ aws_secret_access_key = profile4_secret aws_session_token = profile4_token )"; +MATCHER_P(WithName, expectedName, "") { return arg.name() == expectedName; } + +MATCHER_P(WithAttribute, expectedCluster, "") { + const auto argSocketAddress = + arg.load_assignment().endpoints()[0].lb_endpoints()[0].endpoint().address().socket_address(); + const auto expectedSocketAddress = expectedCluster.load_assignment() + .endpoints()[0] + .lb_endpoints()[0] + .endpoint() + .address() + .socket_address(); + return arg.name() == expectedCluster.name() && + argSocketAddress.address() == expectedSocketAddress.address() && + argSocketAddress.port_value() == expectedSocketAddress.port_value(); +} + class EvironmentCredentialsProviderTest : public testing::Test { public: ~EvironmentCredentialsProviderTest() override { @@ -253,334 +278,1361 @@ messageMatches(const Http::TestRequestHeaderMapImpl& expected_headers) { class InstanceProfileCredentialsProviderTest : public testing::Test { public: InstanceProfileCredentialsProviderTest() - : api_(Api::createApiForTest(time_system_)), - provider_(*api_, [this](Http::RequestMessage& message) -> absl::optional { + : api_(Api::createApiForTest(time_system_)), raw_metadata_fetcher_(new MockMetadataFetcher) {} + + void setupProvider() { + ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); + provider_ = std::make_shared( + *api_, context_, + [this](Http::RequestMessage& message) -> absl::optional { return this->fetch_metadata_.fetch(message); - }) {} + }, + [this](Upstream::ClusterManager&, absl::string_view) { + metadata_fetcher_.reset(raw_metadata_fetcher_); + return std::move(metadata_fetcher_); + }, + "credentials_provider_cluster"); + } - void expectSessionToken(const absl::optional& token) { + void setupProviderWithContext() { + EXPECT_CALL(context_.init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { + init_target_handle_ = target.createHandle("test"); + })); + + setupProvider(); + expected_duration_ = provider_->getCacheDuration(); + init_target_handle_->initialize(init_watcher_); + } + + void setupProviderForLibcurl() { + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); + provider_ = std::make_shared( + *api_, absl::nullopt, + [this](Http::RequestMessage& message) -> absl::optional { + return this->fetch_metadata_.fetch(message); + }, + [this](Upstream::ClusterManager&, absl::string_view) { + metadata_fetcher_.reset(raw_metadata_fetcher_); + return std::move(metadata_fetcher_); + }, + "credentials_provider_cluster"); + } + + void expectSessionTokenCurl(const absl::optional& token) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, {":authority", "169.254.169.254:80"}, {":method", "PUT"}, + {":scheme", "http"}, {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(token)); } - void expectCredentialListing(const absl::optional& listing) { + void expectSessionTokenHttpAsync(const std::string&& token) { + Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, + {":authority", "169.254.169.254:80"}, + {":method", "PUT"}, + {":scheme", "http"}, + {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; + EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) + .WillRepeatedly( + Invoke([token = std::move(token)](Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + receiver.onMetadataSuccess(std::move(token)); + })); + } + + void expectCredentialListingCurl(const absl::optional& listing) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, {":method", "GET"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); } - void expectCredentialListingSecure(const absl::optional& listing) { + void expectCredentialListingCurlSecure(const absl::optional& listing) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, {":method", "GET"}, + {":scheme", "http"}, {"X-aws-ec2-metadata-token", "TOKEN"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); } - void expectDocument(const absl::optional& document) { + void expectCredentialListingHttpAsync(const std::string&& instance_role) { + Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, + {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, + {":method", "GET"}}; + EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) + .WillRepeatedly(Invoke([instance_role = std::move(instance_role)]( + Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + receiver.onMetadataSuccess(std::move(instance_role)); + })); + } + + void expectCredentialListingHttpAsyncSecure(const std::string&& instance_role) { + Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, + {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, + {":method", "GET"}, + {"X-aws-ec2-metadata-token", "TOKEN"}}; + EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) + .WillRepeatedly(Invoke([instance_role = std::move(instance_role)]( + Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + receiver.onMetadataSuccess(std::move(instance_role)); + })); + } + + void expectDocumentCurl(const absl::optional& document) { Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, {":method", "GET"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } - void expectDocumentSecure(const absl::optional& document) { + void expectDocumentCurlSecure(const absl::optional& document) { Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, {":method", "GET"}, + {":scheme", "http"}, {"X-aws-ec2-metadata-token", "TOKEN"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } + void expectDocumentHttpAsync(const std::string&& credential_document_value) { + Http::TestRequestHeaderMapImpl headers{ + {":path", "/latest/meta-data/iam/security-credentials/doc1"}, + {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, + {":method", "GET"}}; + EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) + .WillRepeatedly(Invoke([credential_document_value = std::move(credential_document_value)]( + Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + receiver.onMetadataSuccess(std::move(credential_document_value)); + })); + } + + void expectDocumentHttpAsyncSecure(const std::string&& credential_document_value) { + Http::TestRequestHeaderMapImpl headers{ + {":path", "/latest/meta-data/iam/security-credentials/doc1"}, + {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, + {":method", "GET"}, + {"X-aws-ec2-metadata-token", "TOKEN"}}; + EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) + .WillRepeatedly(Invoke([credential_document_value = std::move(credential_document_value)]( + Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + receiver.onMetadataSuccess(std::move(credential_document_value)); + })); + } + + TestScopedRuntime scoped_runtime; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; NiceMock fetch_metadata_; - InstanceProfileCredentialsProvider provider_; + MockMetadataFetcher* raw_metadata_fetcher_; + MetadataFetcherPtr metadata_fetcher_; + NiceMock cluster_manager_; + NiceMock context_; + InstanceProfileCredentialsProviderPtr provider_; + Init::TargetHandlePtr init_target_handle_; + NiceMock init_watcher_; + Event::MockTimer* timer_{}; + std::chrono::milliseconds expected_duration_; }; -TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListing) { - expectSessionToken(absl::optional()); - expectCredentialListing(absl::optional()); - const auto credentials = provider_.getCredentials(); +// Begin unit test for new option via Http Async +TEST_F(InstanceProfileCredentialsProviderTest, TestAddMissingCluster) { + // Setup without thread local cluster yet + envoy::config::cluster::v3::Cluster expected_cluster; + constexpr static const char* kStaticCluster = R"EOF( +name: credentials_provider_cluster +type: static +connectTimeout: 2s +lb_policy: ROUND_ROBIN +loadAssignment: + clusterName: credentials_provider_cluster + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: "169.254.169.254" + portValue: 80 +typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http_protocol_options: + accept_http_10: true + )EOF"; + MessageUtil::loadFromYaml(kStaticCluster, expected_cluster, + ProtobufMessage::getNullValidationVisitor()); + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) + .WillOnce(Return(true)); + + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + expectDocumentHttpAsyncSecure(std::move(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF")); + + setupProviderWithContext(); +} + +TEST_F(InstanceProfileCredentialsProviderTest, TestClusterMissingExpectEnvoyException) { + // Setup without thread local cluster + Http::RequestMessageImpl message; + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithName("credentials_provider_cluster"), _)) + .WillOnce(Throw(EnvoyException("exeption message"))); + + // init_watcher ready is not called. + init_watcher_.expectReady().Times(0); + setupProvider(); +} + +TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { - expectSessionToken("TOKEN"); - expectCredentialListingSecure(absl::optional()); - const auto credentials = provider_.getCredentials(); + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListing) { - expectSessionToken(absl::optional()); - expectCredentialListing(""); - const auto credentials = provider_.getCredentials(); +TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string(""))); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called once for fetching once again as previous attempt wasn't a success. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { - expectSessionToken("TOKEN"); - expectCredentialListingSecure(""); - const auto credentials = provider_.getCredentials(); + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string(""))); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, MissingDocument) { - expectSessionToken(absl::optional()); - expectCredentialListing("doc1\ndoc2\ndoc3"); - expectDocument(absl::optional()); - const auto credentials = provider_.getCredentials(); +TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentHttpAsync(std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called thrice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { - expectSessionToken("TOKEN"); - expectCredentialListingSecure("doc1\ndoc2\ndoc3"); - expectDocumentSecure(absl::optional()); - const auto credentials = provider_.getCredentials(); + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentHttpAsyncSecure(std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called thrice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumenet) { - expectSessionToken(absl::optional()); - expectCredentialListing("doc1"); - expectDocument(R"EOF( -not json -)EOF"); - const auto credentials = provider_.getCredentials(); +TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string("doc1"))); + expectDocumentHttpAsync(std::move(R"EOF( + not json + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called thrice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { - expectSessionToken("TOKEN"); - expectCredentialListingSecure("doc1"); - expectDocumentSecure(R"EOF( -not json -)EOF"); - const auto credentials = provider_.getCredentials(); + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(std::move(R"EOF( + not json + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called thrice + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyValues) { - expectSessionToken(absl::optional()); - expectCredentialListing("doc1"); - expectDocument(R"EOF( -{ - "AccessKeyId": "", - "SecretAccessKey": "", - "Token": "" +TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string("doc1"))); + expectDocumentHttpAsync(std::move(R"EOF( + { + "AccessKeyId": "", + "SecretAccessKey": "", + "Token": "" + } + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is not called again as we don't expect any more call to fetch until timeout. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(std::move(R"EOF( + { + "AccessKeyId": "", + "SecretAccessKey": "", + "Token": "" + } + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is not called again as we don't expect any more call to fetch until timeout. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string("doc1"))); + expectDocumentHttpAsync(std::move(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Cancel is not called again as we don't expect any more call to fetch until timeout. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // We don't expect any more call to fetch again. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto cached_credentials = provider_->getCredentials(); + EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); + EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); + EXPECT_EQ("token", cached_credentials.sessionToken().value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(std::move(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Cancel is not called again as we don't expect any more call to fetch until timeout. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // We don't expect any more call to fetch again. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto cached_credentials = provider_->getCredentials(); + EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); + EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); + EXPECT_EQ("token", cached_credentials.sessionToken().value()); } + +TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string("doc1"))); + expectDocumentHttpAsync(std::move(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Cancel is not called again as we don't expect any more call to fetch until timeout. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + expectSessionTokenHttpAsync(std::move(std::string())); + expectCredentialListingHttpAsync(std::move(std::string("doc1"))); + expectDocumentHttpAsync(std::move(R"EOF( + { + "AccessKeyId": "new_akid", + "SecretAccessKey": "new_secret", + "Token": "new_token1" + } + )EOF")); + + // Expect timer to have expired but we would re-start the timer eventually after refresh. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + // Cancel will be called thrice back to back to back. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + time_system_.advanceTimeWait(std::chrono::minutes(61)); + timer_->invokeCallback(); + + // We don't expect timer to be reset again for new fetch. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Similarly we won't call fetch or cancel on metadata fetcher. + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + + const auto new_credentials = provider_->getCredentials(); + EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); + EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); + EXPECT_EQ("new_token1", new_credentials.sessionToken().value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(std::move(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Cancel is not called again as we don't expect any more call to fetch until timeout. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + expectSessionTokenHttpAsync(std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(std::move(R"EOF( + { + "AccessKeyId": "new_akid", + "SecretAccessKey": "new_secret", + "Token": "new_token1" + } + )EOF")); + + // Expect timer to have expired but we would re-start the timer eventually after refresh. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + // Cancel will be called thrice back to back to back. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + time_system_.advanceTimeWait(std::chrono::minutes(61)); + timer_->invokeCallback(); + + // We don't expect timer to be reset again for new fetch. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Similarly we won't call fetch or cancel on metadata fetcher. + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + + const auto new_credentials = provider_->getCredentials(); + EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); + EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); + EXPECT_EQ("new_token1", new_credentials.sessionToken().value()); +} +// End unit test for new option via Http Async + +// Begin unit test for deprecated option Libcurl +TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl(absl::optional()); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure(absl::optional()); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl(""); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure(""); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("doc1\ndoc2\ndoc3"); + expectDocumentCurl(absl::optional()); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("doc1\ndoc2\ndoc3"); + expectDocumentCurlSecure(absl::optional()); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("doc1"); + expectDocumentCurl(R"EOF( + not json + )EOF"); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("doc1"); + expectDocumentCurlSecure(R"EOF( +not json )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { - expectSessionToken("TOKEN"); - expectCredentialListingSecure("doc1"); - expectDocumentSecure(R"EOF( +TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("doc1"); + expectDocumentCurl(R"EOF( + { + "AccessKeyId": "", + "SecretAccessKey": "", + "Token": "" + } + )EOF"); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("doc1"); + expectDocumentCurlSecure(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", "Token": "" } )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentials) { - expectSessionToken(absl::optional()); - expectCredentialListing("doc1"); - expectDocument(R"EOF( -{ - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" -} -)EOF"); - const auto credentials = provider_.getCredentials(); +TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("doc1"); + expectDocumentCurl(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF"); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - const auto cached_credentials = provider_.getCredentials(); + const auto cached_credentials = provider_->getCredentials(); EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { - expectSessionToken("TOKEN"); - expectCredentialListingSecure("doc1"); - expectDocumentSecure(R"EOF( +TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("doc1"); + expectDocumentCurlSecure(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", "Token": "token" } )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - const auto cached_credentials = provider_.getCredentials(); + const auto cached_credentials = provider_->getCredentials(); EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpiration) { +TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlUnsecure) { + setupProviderForLibcurl(); InSequence sequence; - expectSessionToken(absl::optional()); - expectCredentialListing("doc1"); - expectDocument(R"EOF( -{ - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" -} -)EOF"); - const auto credentials = provider_.getCredentials(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("doc1"); + expectDocumentCurl(R"EOF( + { + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" + } + )EOF"); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); time_system_.advanceTimeWait(std::chrono::hours(2)); - expectSessionToken(absl::optional()); - expectCredentialListing("doc1"); - expectDocument(R"EOF( -{ - "AccessKeyId": "new_akid", - "SecretAccessKey": "new_secret", - "Token": "new_token" -} -)EOF"); - const auto new_credentials = provider_.getCredentials(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("doc1"); + expectDocumentCurl(R"EOF( + { + "AccessKeyId": "new_akid", + "SecretAccessKey": "new_secret", + "Token": "new_token" + } + )EOF"); + const auto new_credentials = provider_->getCredentials(); EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", new_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationSecure) { +TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlSecure) { + setupProviderForLibcurl(); InSequence sequence; - expectSessionToken("TOKEN"); - expectCredentialListingSecure("doc1"); - expectDocumentSecure(R"EOF( + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("doc1"); + expectDocumentCurlSecure(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", "Token": "token" } )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); time_system_.advanceTimeWait(std::chrono::hours(2)); - expectSessionToken("TOKEN"); - expectCredentialListingSecure("doc1"); - expectDocumentSecure(R"EOF( + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("doc1"); + expectDocumentCurlSecure(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", "Token": "new_token" } )EOF"); - const auto new_credentials = provider_.getCredentials(); + const auto new_credentials = provider_->getCredentials(); EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", new_credentials.sessionToken().value()); } +// End unit test for deprecated option Libcurl class TaskRoleCredentialsProviderTest : public testing::Test { public: TaskRoleCredentialsProviderTest() - : api_(Api::createApiForTest(time_system_)), - provider_( - *api_, - [this](Http::RequestMessage& message) -> absl::optional { - return this->fetch_metadata_.fetch(message); - }, - "169.254.170.2:80/path/to/doc", "auth_token") { + : api_(Api::createApiForTest(time_system_)), raw_metadata_fetcher_(new MockMetadataFetcher) { // Tue Jan 2 03:04:05 UTC 2018 time_system_.setSystemTime(std::chrono::milliseconds(1514862245000)); } - void expectDocument(const absl::optional& document) { + void setupProvider() { + ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); + provider_ = std::make_shared( + *api_, context_, + [this](Http::RequestMessage& message) -> absl::optional { + return this->fetch_metadata_.fetch(message); + }, + [this](Upstream::ClusterManager&, absl::string_view) { + metadata_fetcher_.reset(raw_metadata_fetcher_); + return std::move(metadata_fetcher_); + }, + "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); + } + + void setupProviderWithContext() { + EXPECT_CALL(context_.init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { + init_target_handle_ = target.createHandle("test"); + })); + setupProvider(); + expected_duration_ = provider_->getCacheDuration(); + init_target_handle_->initialize(init_watcher_); + } + + void setupProviderForLibcurl() { + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); + provider_ = std::make_shared( + *api_, absl::nullopt, + [this](Http::RequestMessage& message) -> absl::optional { + return this->fetch_metadata_.fetch(message); + }, + [this](Upstream::ClusterManager&, absl::string_view) { + metadata_fetcher_.reset(raw_metadata_fetcher_); + return std::move(metadata_fetcher_); + }, + "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); + } + + void expectDocumentCurl(const absl::optional& document) { Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, {":authority", "169.254.170.2:80"}, + {":scheme", "http"}, {":method", "GET"}, {"authorization", "auth_token"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } + void expectDocumentHttpAsync(const std::string&& document) { + Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, + {":authority", "169.254.170.2:80"}, + {":scheme", "http"}, + {":method", "GET"}, + {"authorization", "auth_token"}}; + EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) + .WillRepeatedly( + Invoke([document = std::move(document)](Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + receiver.onMetadataSuccess(std::move(document)); + })); + } + + TestScopedRuntime scoped_runtime; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; NiceMock fetch_metadata_; - TaskRoleCredentialsProvider provider_; + MockMetadataFetcher* raw_metadata_fetcher_; + MetadataFetcherPtr metadata_fetcher_; + NiceMock cluster_manager_; + NiceMock context_; + TaskRoleCredentialsProviderPtr provider_; + Init::TargetHandlePtr init_target_handle_; + NiceMock init_watcher_; + Event::MockTimer* timer_{}; + std::chrono::milliseconds expected_duration_; }; +// Begin unit test for new option via Http Async +TEST_F(TaskRoleCredentialsProviderTest, TestAddMissingCluster) { + // Setup without thread local cluster yet + envoy::config::cluster::v3::Cluster expected_cluster; + constexpr static const char* kStaticCluster = R"EOF( +name: credentials_provider_cluster +type: static +connectTimeout: 2s +lb_policy: ROUND_ROBIN +loadAssignment: + clusterName: credentials_provider_cluster + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: "169.254.170.2" + portValue: 80 +typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http_protocol_options: + accept_http_10: true + )EOF"; + MessageUtil::loadFromYaml(kStaticCluster, expected_cluster, + ProtobufMessage::getNullValidationVisitor()); + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) + .WillOnce(Return(true)); + + expectDocumentHttpAsync(std::move(R"EOF( +{ + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token", + "Expiration": "2018-01-02T03:05:00Z" +} +)EOF")); + + setupProviderWithContext(); +} + +TEST_F(TaskRoleCredentialsProviderTest, TestClusterMissingExpectEnvoyException) { + // Setup without thread local cluster + Http::RequestMessageImpl message; + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithName("credentials_provider_cluster"), _)) + .WillOnce(Throw(EnvoyException("exeption message"))); + // init_watcher ready is not called. + init_watcher_.expectReady().Times(0); + setupProvider(); +} + TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocument) { - expectDocument(absl::optional()); - const auto credentials = provider_.getCredentials(); + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectDocumentHttpAsync(std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // Cancel is called for fetching once again as previous attempt wasn't a success. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderTest, MalformedDocumenet) { - expectDocument(R"EOF( +TEST_F(TaskRoleCredentialsProviderTest, MalformedDocument) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + + expectDocumentHttpAsync(std::move(R"EOF( not json -)EOF"); - const auto credentials = provider_.getCredentials(); +)EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // Cancel is called for fetching once again as previous attempt wasn't a success. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { - expectDocument(R"EOF( + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + + expectDocumentHttpAsync(std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", "Token": "", "Expiration": "" } -)EOF"); - const auto credentials = provider_.getCredentials(); +)EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // Cancel is called for fetching once again as previous attempt wasn't a success with updating + // expiration time. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + + const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentials) { - expectDocument(R"EOF( + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectDocumentHttpAsync(std::move(R"EOF( +{ + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token", + "Expiration": "2018-01-02T03:05:00Z" +} +)EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // We don't expect any more call to cancel or fetch again. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // We don't expect any more call to cancel or fetch again. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto cached_credentials = provider_->getCredentials(); + EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); + EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); + EXPECT_EQ("token", cached_credentials.sessionToken().value()); +} + +TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + + expectDocumentHttpAsync(std::move(R"EOF( +{ + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token", + "Expiration": "2019-01-02T03:04:05Z" +} +)EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // No need to restart timer since credentials are fetched from cache. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // We don't expect any more call to cancel or fetch again. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + expectDocumentHttpAsync(std::move(R"EOF( +{ + "AccessKeyId": "new_akid", + "SecretAccessKey": "new_secret", + "Token": "new_token", + "Expiration": "2019-01-02T03:04:05Z" +} +)EOF")); + // Expect timer to have expired but we would re-start the timer eventually after refresh. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + // Cancel will be called once more. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + time_system_.advanceTimeWait(std::chrono::minutes(61)); + timer_->invokeCallback(); + + // We don't expect timer to be reset again for new fetch. + EXPECT_CALL(*timer_, disableTimer()).Times(0); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); + // Similarly we won't call fetch or cancel on metadata fetcher. + EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); + + const auto cached_credentials = provider_->getCredentials(); + EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); + EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); + EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); +} + +TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectDocumentHttpAsync(std::move(R"EOF( +{ + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token", + "Expiration": "2018-01-02T03:04:05Z" +} +)EOF")); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // init_watcher ready is not called again. + init_watcher_.expectReady().Times(0); + // Need to disable and restart timer since credentials are expired and fetched again + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + // We call cancel once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + + const auto credentials = provider_->getCredentials(); + EXPECT_EQ("akid", credentials.accessKeyId().value()); + EXPECT_EQ("secret", credentials.secretAccessKey().value()); + EXPECT_EQ("token", credentials.sessionToken().value()); + + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + expectDocumentHttpAsync(std::move(R"EOF( +{ + "AccessKeyId": "new_akid", + "SecretAccessKey": "new_secret", + "Token": "new_token", + "Expiration": "2019-01-02T03:04:05Z" +} +)EOF")); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto cached_credentials = provider_->getCredentials(); + EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); + EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); + EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); +} +// End unit test for new option via Http Async + +// Begin unit test for deprecated option Libcurl +TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocumentCurl) { + setupProviderForLibcurl(); + expectDocumentCurl(absl::optional()); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(TaskRoleCredentialsProviderTest, MalformedDocumentCurl) { + setupProviderForLibcurl(); + expectDocumentCurl(R"EOF( +not json +)EOF"); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(TaskRoleCredentialsProviderTest, EmptyValuesCurl) { + setupProviderForLibcurl(); + expectDocumentCurl(R"EOF( +{ + "AccessKeyId": "", + "SecretAccessKey": "", + "Token": "", + "Expiration": "" +} +)EOF"); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentialsCurl) { + setupProviderForLibcurl(); + expectDocumentCurl(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -588,19 +1640,20 @@ TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentials) { "Expiration": "2018-01-02T03:05:00Z" } )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - const auto cached_credentials = provider_.getCredentials(); + const auto cached_credentials = provider_->getCredentials(); EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpiration) { +TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpirationCurl) { + setupProviderForLibcurl(); InSequence sequence; - expectDocument(R"EOF( + expectDocumentCurl(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -608,12 +1661,12 @@ TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpiration) { "Expiration": "2019-01-02T03:04:05Z" } )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); time_system_.advanceTimeWait(std::chrono::hours(2)); - expectDocument(R"EOF( + expectDocumentCurl(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -621,15 +1674,16 @@ TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpiration) { "Expiration": "2019-01-02T03:04:05Z" } )EOF"); - const auto cached_credentials = provider_.getCredentials(); + const auto cached_credentials = provider_->getCredentials(); EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } -TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { +TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpirationCurl) { + setupProviderForLibcurl(); InSequence sequence; - expectDocument(R"EOF( + expectDocumentCurl(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -637,11 +1691,11 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { "Expiration": "2018-01-02T03:04:05Z" } )EOF"); - const auto credentials = provider_.getCredentials(); + const auto credentials = provider_->getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectDocument(R"EOF( + expectDocumentCurl(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -649,15 +1703,18 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { "Expiration": "2019-01-02T03:04:05Z" } )EOF"); - const auto cached_credentials = provider_.getCredentials(); + const auto cached_credentials = provider_->getCredentials(); EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } +// End unit test for deprecated option Libcurl class DefaultCredentialsProviderChainTest : public testing::Test { public: DefaultCredentialsProviderChainTest() : api_(Api::createApiForTest(time_system_)) { + ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); + cluster_manager_.initializeThreadLocalClusters({"credentials_provider_cluster"}); EXPECT_CALL(factories_, createEnvironmentCredentialsProvider()); } @@ -674,62 +1731,67 @@ class DefaultCredentialsProviderChainTest : public testing::Test { MOCK_METHOD(CredentialsProviderSharedPtr, createCredentialsFileCredentialsProvider, (Api::Api&), (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createTaskRoleCredentialsProvider, - (Api::Api&, const MetadataCredentialsProviderBase::MetadataFetcher&, - absl::string_view, absl::string_view), + (Api::Api&, ServerFactoryContextOptRef, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl&, + CreateMetadataFetcherCb, absl::string_view, absl::string_view, absl::string_view), (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createInstanceProfileCredentialsProvider, - (Api::Api&, const MetadataCredentialsProviderBase::MetadataFetcher& fetcher), + (Api::Api&, ServerFactoryContextOptRef, + const MetadataCredentialsProviderBase::FetchMetadataUsingCurl&, + CreateMetadataFetcherCb, absl::string_view), (const)); }; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; + NiceMock cluster_manager_; + NiceMock context_; NiceMock factories_; }; TEST_F(DefaultCredentialsProviderChainTest, NoEnvironmentVars) { EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, CredentialsFileDisabled) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues({{"envoy.reloadable_features.enable_aws_credentials_file", "false"}}); - EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))).Times(0); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, MetadataDisabled) { TestEnvironment::setEnvVar("AWS_EC2_METADATA_DISABLED", "true", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)).Times(0); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)) + .Times(0); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, MetadataNotDisabled) { TestEnvironment::setEnvVar("AWS_EC2_METADATA_DISABLED", "false", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, RelativeUri) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/path/to/creds", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createTaskRoleCredentialsProvider(Ref(*api_), _, + EXPECT_CALL(factories_, createTaskRoleCredentialsProvider(Ref(*api_), _, _, _, _, "169.254.170.2:80/path/to/creds", "")); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, FullUriNoAuthorizationToken) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://host/path/to/creds", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, - createTaskRoleCredentialsProvider(Ref(*api_), _, "http://host/path/to/creds", "")); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + EXPECT_CALL(factories_, createTaskRoleCredentialsProvider(Ref(*api_), _, _, _, _, + "http://host/path/to/creds", "")); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, FullUriWithAuthorizationToken) { @@ -737,8 +1799,8 @@ TEST_F(DefaultCredentialsProviderChainTest, FullUriWithAuthorizationToken) { TestEnvironment::setEnvVar("AWS_CONTAINER_AUTHORIZATION_TOKEN", "auth_token", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); EXPECT_CALL(factories_, createTaskRoleCredentialsProvider( - Ref(*api_), _, "http://host/path/to/creds", "auth_token")); - DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); + Ref(*api_), _, _, _, _, "http://host/path/to/creds", "auth_token")); + DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); } TEST(CredentialsProviderChainTest, getCredentials_noCredentials) { diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc new file mode 100644 index 0000000000000..75a7e39b70c14 --- /dev/null +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -0,0 +1,273 @@ +#include +#include +#include + +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include "source/common/http/utility.h" +#include "source/common/protobuf/utility.h" +#include "source/extensions/common/aws/metadata_fetcher.h" + +#include "test/extensions/common/aws/mocks.h" +#include "test/mocks/api/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/environment.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +using testing::_; +using testing::InSequence; +using testing::NiceMock; +using testing::Ref; +using testing::Return; +using testing::Throw; + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Aws { + +MATCHER_P(WithName, expectedName, "") { return arg.name() == expectedName; } + +MATCHER_P(WithAttribute, expectedCluster, "") { + const auto argSocketAddress = + arg.load_assignment().endpoints()[0].lb_endpoints()[0].endpoint().address().socket_address(); + const auto expectedSocketAddress = expectedCluster.load_assignment() + .endpoints()[0] + .lb_endpoints()[0] + .endpoint() + .address() + .socket_address(); + return arg.name() == expectedCluster.name() && + argSocketAddress.address() == expectedSocketAddress.address() && + argSocketAddress.port_value() == expectedSocketAddress.port_value(); +} + +class MetadataFetcherTest : public testing::Test { +public: + void setupFetcher() { + mock_factory_ctx_.cluster_manager_.initializeThreadLocalClusters({"cluster_name"}); + fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); + EXPECT_TRUE(fetcher_ != nullptr); + } + + testing::NiceMock mock_factory_ctx_; + std::unique_ptr fetcher_; + NiceMock parent_span_; +}; + +TEST_F(MetadataFetcherTest, TestGetSuccess) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)); + EXPECT_CALL(receiver, onMetadataError(testing::_)).Times(0); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestRequestMatch) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); + message.headers().setMethod(Http::Headers::get().MethodValues.Get); + message.headers().setHost("169.254.170.2:80"); + message.headers().setPath("/v2/credentials/c68caeb5-ef71-4914-8170-111111111111"); + message.headers().setCopy(Http::LowerCaseString(":pseudo-header"), "peudo-header-value"); + message.headers().setCopy(Http::LowerCaseString("X-aws-ec2-metadata-token"), "Token"); + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + + EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, + send_(_, _, _)) + .WillOnce(Invoke([](Http::RequestMessagePtr& request, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + EXPECT_EQ("169.254.170.2", request->headers().getHostValue()); + EXPECT_EQ("/v2/credentials/c68caeb5-ef71-4914-8170-111111111111", + request->headers().getPathValue()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, request->headers().getMethodValue()); + EXPECT_EQ(Http::Headers::get().SchemeValues.Http, request->headers().getSchemeValue()); + EXPECT_EQ("Token", request->headers() + .get(Http::LowerCaseString("X-aws-ec2-metadata-token"))[0] + ->value() + .getStringView()); + EXPECT_TRUE(request->headers().get(Http::LowerCaseString(":pseudo-header")).empty()); + return nullptr; + })); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestGet400) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "400", "not_empty"); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestGetNoBody) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", ""); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestHttpFailure) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, + Http::AsyncClient::FailureReason::Reset); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestWithValidCluster) { + // Setup with thread local cluster + Http::RequestMessageImpl message; + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); + message.headers().setMethod(Http::Headers::get().MethodValues.Get); + message.headers().setHost("169.254.170.2:80"); + message.headers().setPath("/v2/credentials/c68caeb5-ef71-4914-8170-111111111111"); + + NiceMock cluster_; + fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); + EXPECT_CALL(mock_factory_ctx_.cluster_manager_, getThreadLocalCluster(_)) + .WillOnce(Return(&cluster_)); + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataError(testing::_)).Times(0); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestClusterNotFound) { + // Setup without thread local cluster + fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); + Http::RequestMessageImpl message; + MockMetadataReceiver receiver; + + EXPECT_CALL(mock_factory_ctx_.cluster_manager_, getThreadLocalCluster(_)) + .WillOnce(Return(nullptr)); + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::MissingConfig)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestCancel) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + Http::MockAsyncClientRequest request( + &(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_)); + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, &request); + MockMetadataReceiver receiver; + EXPECT_CALL(request, cancel()); + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(testing::_)).Times(0); + + // Act + fetcher_->fetch(message, parent_span_, receiver); + // Proper cancel + fetcher_->cancel(); + // Re-entrant cancel + fetcher_->cancel(); +} + +TEST_F(MetadataFetcherTest, TestSpanPassedDown) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + + // Expectations for span + EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, + send_(_, _, _)) + .WillOnce(Invoke( + [this](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { + EXPECT_TRUE(options.parent_span_ == &this->parent_span_); + EXPECT_TRUE(options.child_span_name_ == "AWS Metadata Fetch"); + return nullptr; + })); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + MockMetadataReceiver receiver; + + EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, + send_(_, _, _)) + .WillOnce(Invoke( + [](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, + const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { + EXPECT_TRUE(options.retry_policy.has_value()); + EXPECT_TRUE(options.buffer_body_for_retry); + EXPECT_TRUE(options.retry_policy.value().has_num_retries()); + EXPECT_EQ(PROTOBUF_GET_WRAPPED_REQUIRED(options.retry_policy.value(), num_retries), 3); + + EXPECT_TRUE(options.retry_policy.value().has_per_try_timeout()); + EXPECT_EQ(PROTOBUF_GET_MS_REQUIRED(options.retry_policy.value(), per_try_timeout), + 5000); + + EXPECT_TRUE(options.retry_policy.value().has_per_try_idle_timeout()); + EXPECT_EQ(PROTOBUF_GET_MS_REQUIRED(options.retry_policy.value(), per_try_idle_timeout), + 1000); + + const std::string& retry_on = options.retry_policy.value().retry_on(); + std::set retry_on_modes = absl::StrSplit(retry_on, ','); + + EXPECT_EQ(retry_on_modes.count("5xx"), 1); + EXPECT_EQ(retry_on_modes.count("gateway-error"), 1); + EXPECT_EQ(retry_on_modes.count("connect-failure"), 1); + EXPECT_EQ(retry_on_modes.count("reset"), 1); + + return nullptr; + })); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + +} // namespace Aws +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/aws/mocks.cc b/test/extensions/common/aws/mocks.cc index 479ed4d44613e..bc678bbd8ad2b 100644 --- a/test/extensions/common/aws/mocks.cc +++ b/test/extensions/common/aws/mocks.cc @@ -13,6 +13,46 @@ MockSigner::MockSigner() = default; MockSigner::~MockSigner() = default; +// Mock HTTP upstream with response body. +MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, const std::string& status, + const std::string& response_body) + : request_(&mock_cm.thread_local_cluster_.async_client_), status_(status), + response_body_(response_body) { + ON_CALL(mock_cm.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillByDefault( + Invoke([this](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response_message( + new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", status_}}})); + response_message->body().add(response_body_); + cb.onSuccess(request_, std::move(response_message)); + called_count_++; + return &request_; + })); +} + +// Mock HTTP upstream with failure reason. +MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, + Http::AsyncClient::FailureReason reason) + : request_(&mock_cm.thread_local_cluster_.async_client_) { + ON_CALL(mock_cm.thread_local_cluster_.async_client_, send_(testing::_, testing::_, testing::_)) + .WillByDefault(testing::Invoke( + [this, reason](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + cb.onFailure(request_, reason); + return &request_; + })); +} + +// Mock HTTP upstream with request. +MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, + Http::MockAsyncClientRequest* request) + : request_(&mock_cm.thread_local_cluster_.async_client_) { + ON_CALL(mock_cm.thread_local_cluster_.async_client_, send_(testing::_, testing::_, testing::_)) + .WillByDefault(testing::Return(request)); +} + } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index 5c3b0c7041aff..92fe1cf253587 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -1,8 +1,14 @@ #pragma once +#include "envoy/http/message.h" + +#include "source/common/http/message_impl.h" #include "source/extensions/common/aws/credentials_provider.h" +#include "source/extensions/common/aws/metadata_fetcher.h" #include "source/extensions/common/aws/signer.h" +#include "test/mocks/upstream/cluster_manager.h" + #include "gmock/gmock.h" namespace Envoy { @@ -10,6 +16,20 @@ namespace Extensions { namespace Common { namespace Aws { +class MockMetadataFetcher : public MetadataFetcher { +public: + MOCK_METHOD(void, cancel, ()); + MOCK_METHOD(void, fetch, + (Http::RequestMessage & message, Tracing::Span& parent_span, + MetadataFetcher::MetadataReceiver& receiver)); +}; + +class MockMetadataReceiver : public MetadataFetcher::MetadataReceiver { +public: + MOCK_METHOD(void, onMetadataSuccess, (const std::string&& body)); + MOCK_METHOD(void, onMetadataError, (MetadataFetcher::MetadataReceiver::Failure reason)); +}; + class MockCredentialsProvider : public CredentialsProvider { public: MockCredentialsProvider(); @@ -36,11 +56,37 @@ class MockFetchMetadata { MOCK_METHOD(absl::optional, fetch, (Http::RequestMessage&), (const)); }; -class DummyMetadataFetcher { +class DummyFetchMetadata { public: absl::optional operator()(Http::RequestMessage&) { return absl::nullopt; } }; +// A mock HTTP upstream with response body. +class MockUpstream { +public: + /** + * Mock upstream which returns a given response body. + */ + MockUpstream(Upstream::MockClusterManager& mock_cm, const std::string& status, + const std::string& response_body); + /** + * Mock upstream which returns a given failure. + */ + MockUpstream(Upstream::MockClusterManager& mock_cm, Http::AsyncClient::FailureReason reason); + /** + * Mock upstream which returns the given request. + */ + MockUpstream(Upstream::MockClusterManager& mock_cm, Http::MockAsyncClientRequest* request); + + int called_count() const { return called_count_; } + +private: + Http::MockAsyncClientRequest request_; + std::string status_; + std::string response_body_; + int called_count_{}; +}; + } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/test/extensions/common/aws/utility_test.cc b/test/extensions/common/aws/utility_test.cc index 629f28c2c9577..038d8f0e3149f 100644 --- a/test/extensions/common/aws/utility_test.cc +++ b/test/extensions/common/aws/utility_test.cc @@ -1,11 +1,18 @@ #include "source/extensions/common/aws/utility.h" +#include "test/extensions/common/aws/mocks.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" +using testing::_; using testing::ElementsAre; +using testing::InSequence; +using testing::NiceMock; using testing::Pair; +using testing::Ref; +using testing::Return; +using testing::Throw; namespace Envoy { namespace Extensions { @@ -13,6 +20,8 @@ namespace Common { namespace Aws { namespace { +MATCHER_P(WithName, expectedName, "") { return arg.name() == expectedName; } + // Headers must be in alphabetical order by virtue of std::map TEST(UtilityTest, CanonicalizeHeadersInAlphabeticalOrder) { Http::TestRequestHeaderMapImpl headers{ @@ -346,6 +355,39 @@ TEST(UtilityTest, JoinCanonicalHeaderNamesWithEmptyMap) { EXPECT_EQ("", names); } +// Verify that we don't add a thread local cluster if it already exists. +TEST(UtilityTest, ThreadLocalClusterExistsAlready) { + NiceMock cluster_; + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(&cluster_)); + EXPECT_CALL(cm_, addOrUpdateCluster(_, _)).Times(0); + EXPECT_TRUE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "")); +} + +// Verify that if thread local cluster doesn't exist we can create a new one. +TEST(UtilityTest, AddStaticClusterSuccess) { + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)).WillOnce(Return(true)); + EXPECT_TRUE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1:80")); +} + +// Handle exception when adding thread local cluster fails. +TEST(UtilityTest, AddStaticClusterFailure) { + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)) + .WillOnce(Throw(EnvoyException("exeption message"))); + EXPECT_FALSE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1:80")); +} + +// Verify that missing port value from host will return false. +TEST(UtilityTest, AddStaticClusterFailureWithMissingPort) { + NiceMock cm_; + EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); + EXPECT_FALSE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1")); +} + } // namespace } // namespace Aws } // namespace Common diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index ea7b558f6f5e4..3dbfd0194a667 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -252,6 +252,7 @@ integrations iouring jkl lang +libcurl libsxg LLVM LPT From 63b4d1844358c485cb2422660015ecc6d993bf09 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Sat, 30 Sep 2023 00:28:57 +0000 Subject: [PATCH 02/15] docs and release notes update Signed-off-by: Sunil Narasimhamurthy --- changelogs/current.yaml | 4 ++++ .../http/http_filters/_include/aws_credentials.rst | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 964a4cd719cbf..715ee51834d40 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -50,6 +50,10 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: aws + change: | + uses http async client to fetch the credentials from EC2 instance metadata and ECS task metadata providers instead of libcurl + which is deprecated. To revert this behavior set ``envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials`` to true. - area: ext_authz change: | removing any query parameter in the presence of repeated query parameter keys no longer drops the repeats. diff --git a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst index 83fd1aea5bd0a..2b230fea09bea 100644 --- a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst +++ b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst @@ -13,4 +13,10 @@ secret access key (the session token is optional). 3. Either EC2 instance metadata or ECS task metadata. For EC2 instance metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and ``Token`` are used, and credentials are cached for 1 hour. For ECS task metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and - ``Token`` are used, and credentials are cached for 1 hour or until they expire (according to the field ``Expiration``). + ``Token`` are used, and credentials are cached for 1 hour or until they expire (according to the field ``Expiration``). Note the latest + update on AWS credentials provider utility uses http async client functionality by default instead of libcurl to fetch the credentials. + The usage of libcurl is on the deprecation path and will be removed soon. To fetch the credentials from either EC2 instance metadata or + ECS task metadata a static cluster is required pointing towards the credentials provider. The static cluster name has to be + ``ec2_instance_metadata_server_internal`` for fetching from EC2 instance metadata or ``ecs_task_metadata_server_internal`` for fetching + from ECS task metadata. If these clusters are not provided in the bootstrap configuration then either of these will be added by default. + This behavior can be changed by setting ``envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials`` to true. From 7ca9a641aa2c5a1ab30b41c4dcaccdf4d9267037 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Sat, 30 Sep 2023 03:08:15 +0000 Subject: [PATCH 03/15] ignore instance metadata for aws lambda integ test Signed-off-by: Sunil Narasimhamurthy --- .../http/aws_lambda/aws_lambda_filter_integration_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc index b42b5977b8397..bee8f3ad2a2ee 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc @@ -23,6 +23,7 @@ class AwsLambdaFilterIntegrationTest : public testing::TestWithParam Date: Sat, 30 Sep 2023 04:34:23 +0000 Subject: [PATCH 04/15] ignore expected_duration_ when it is not available yet Signed-off-by: Sunil Narasimhamurthy --- test/extensions/common/aws/credentials_provider_impl_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index 1eb590c1c2e10..751adbcae487e 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -874,7 +874,7 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnse // Cancel is called twice. EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); setupProviderWithContext(); // init_watcher ready is not called again. @@ -939,7 +939,7 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecu // Cancel is called twice. EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); setupProviderWithContext(); // init_watcher ready is not called again. From 773c17810edd9090d9f46f68f7ec5bca6de16c9a Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Mon, 2 Oct 2023 08:23:07 +0000 Subject: [PATCH 05/15] improve code coverage Signed-off-by: Sunil Narasimhamurthy --- .../common/aws/credentials_provider_impl.cc | 35 +- .../common/aws/credentials_provider_impl.h | 2 +- .../extensions/common/aws/metadata_fetcher.cc | 15 +- .../extensions/common/aws/metadata_fetcher.h | 7 + .../aws/credentials_provider_impl_test.cc | 405 ++++++++++++++---- .../common/aws/metadata_fetcher_test.cc | 17 +- test/extensions/common/aws/mocks.h | 1 + 7 files changed, 368 insertions(+), 114 deletions(-) diff --git a/source/extensions/common/aws/credentials_provider_impl.cc b/source/extensions/common/aws/credentials_provider_impl.cc index 6a0a28c154102..01ee4bec7ba8d 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -229,14 +229,14 @@ void InstanceProfileCredentialsProvider::refresh() { if (!metadata_fetcher_) { metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); } else { - metadata_fetcher_->cancel(); + metadata_fetcher_->cancel(); // Cancel if there is any inflight request. } on_async_fetch_cb_ = [this](const std::string&& arg) { return this->fetchInstanceRoleAsync(std::move(arg)); }; - continue_on_async_fetch_failure_ = true; // FIXME: Unit test this part + continue_on_async_fetch_failure_ = true; continue_on_async_fetch_failure_reason_ = - "Failed to get token from EC2MetadataService, falling back to less secure way"; + "Failed to get token from EC2MetadataService, reason:{}. Falling back to less secure way"; metadata_fetcher_->fetch(token_req_message, Tracing::NullSpan::instance(), *this); } } @@ -265,11 +265,7 @@ void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string&& t std::move(token_string)); } else { // Using Http async client to fetch the Instance Role. - if (!metadata_fetcher_) { - metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); - } else { - metadata_fetcher_->cancel(); - } + metadata_fetcher_->cancel(); // Cancel if there is any inflight request. on_async_fetch_cb_ = [this, token_string = std::move(token_string)](const std::string&& arg) { return this->fetchCredentialFromInstanceRoleAsync(std::move(arg), std::move(token_string)); }; @@ -325,12 +321,7 @@ void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( extractCredentials(std::move(credential_document.value())); } else { // Using Http async client to fetch and parse the AWS credentials. - if (!metadata_fetcher_) { - metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); - } else { - metadata_fetcher_->cancel(); - } - + metadata_fetcher_->cancel(); // Cancel if there is any inflight request. on_async_fetch_cb_ = [this](const std::string&& arg) { return this->extractCredentialsAsync(std::move(arg)); }; @@ -382,15 +373,17 @@ void InstanceProfileCredentialsProvider::onMetadataSuccess(const std::string&& b on_async_fetch_cb_(std::move(body)); } -void InstanceProfileCredentialsProvider::onMetadataError(Failure) { +void InstanceProfileCredentialsProvider::onMetadataError(Failure reason) { // TODO(suniltheta): increment fetch failed stats if (continue_on_async_fetch_failure_) { - ENVOY_LOG(warn, continue_on_async_fetch_failure_reason_); + ENVOY_LOG(warn, continue_on_async_fetch_failure_reason_, + metadata_fetcher_->failureToString(reason)); continue_on_async_fetch_failure_ = false; - continue_on_async_fetch_failure_reason_ = ""; + continue_on_async_fetch_failure_reason_ = "{}"; on_async_fetch_cb_(std::move("")); } else { - ENVOY_LOG(error, "AWS Instance metadata fetch failure"); + ENVOY_LOG(error, "AWS Instance metadata fetch failure: {}", + metadata_fetcher_->failureToString(reason)); handleFetchDone(); } } @@ -432,7 +425,7 @@ void TaskRoleCredentialsProvider::refresh() { if (!metadata_fetcher_) { metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); } else { - metadata_fetcher_->cancel(); + metadata_fetcher_->cancel(); // Cancel if there is any inflight request. } on_async_fetch_cb_ = [this](const std::string&& arg) { return this->extractCredentials(std::move(arg)); @@ -489,9 +482,9 @@ void TaskRoleCredentialsProvider::onMetadataSuccess(const std::string&& body) { on_async_fetch_cb_(std::move(body)); } -void TaskRoleCredentialsProvider::onMetadataError(Failure) { +void TaskRoleCredentialsProvider::onMetadataError(Failure reason) { // TODO(suniltheta): increment fetch failed stats - ENVOY_LOG(error, "AWS metadata fetch failure"); + ENVOY_LOG(error, "AWS metadata fetch failure: {}", metadata_fetcher_->failureToString(reason)); handleFetchDone(); } diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index d86eded9c207b..e137ce5787181 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -183,7 +183,7 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { OnAsyncFetchCb on_async_fetch_cb_; bool continue_on_async_fetch_failure_ = false; - std::string continue_on_async_fetch_failure_reason_ = ""; + std::string continue_on_async_fetch_failure_reason_ = "{}"; SystemTime last_updated_; Credentials cached_credentials_; Thread::MutexBasicLockable lock_; diff --git a/source/extensions/common/aws/metadata_fetcher.cc b/source/extensions/common/aws/metadata_fetcher.cc index 4a3ddc64a041a..dd5ed27a9c2ec 100644 --- a/source/extensions/common/aws/metadata_fetcher.cc +++ b/source/extensions/common/aws/metadata_fetcher.cc @@ -33,6 +33,18 @@ class MetadataFetcherImpl : public MetadataFetcher, reset(); } + absl::string_view failureToString(MetadataFetcher::MetadataReceiver::Failure reason) override { + switch (reason) { + case MetadataFetcher::MetadataReceiver::Failure::Network: + return "Network"; + case MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata: + return "InvalidMetadata"; + case MetadataFetcher::MetadataReceiver::Failure::MissingConfig: + return "MissingConfig"; + } + return ""; + } + void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, MetadataFetcher::MetadataReceiver& receiver) override { complete_ = false; @@ -112,7 +124,7 @@ class MetadataFetcherImpl : public MetadataFetcher, } else { ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: body is empty", __func__, cluster_name_); - receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); } } else { if (response->body().length() != 0) { @@ -138,6 +150,7 @@ class MetadataFetcherImpl : public MetadataFetcher, reset(); } + // TODO(suniltheta): Add metadata fetch status into the span like it is done on ext_authz filter. void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} private: diff --git a/source/extensions/common/aws/metadata_fetcher.h b/source/extensions/common/aws/metadata_fetcher.h index dca68c86bdd1d..a39d1480447c0 100644 --- a/source/extensions/common/aws/metadata_fetcher.h +++ b/source/extensions/common/aws/metadata_fetcher.h @@ -75,6 +75,13 @@ class MetadataFetcher { virtual void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, MetadataReceiver& receiver) PURE; + /** + * @brief Return MetadataReceiver Failure enum as a string. + * + * @return absl::string_view + */ + virtual absl::string_view failureToString(MetadataReceiver::Failure) PURE; + /** * @brief Factory method for creating a Metadata Fetcher. * diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index 751adbcae487e..afbaa2a221929 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -17,6 +17,7 @@ using Envoy::Extensions::Common::Aws::MetadataFetcher; using Envoy::Extensions::Common::Aws::MetadataFetcherPtr; using Envoy::Extensions::Common::Aws::MockMetadataFetcher; using testing::_; +using testing::Eq; using testing::InSequence; using testing::NiceMock; using testing::Ref; @@ -312,11 +313,7 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { [this](Http::RequestMessage& message) -> absl::optional { return this->fetch_metadata_.fetch(message); }, - [this](Upstream::ClusterManager&, absl::string_view) { - metadata_fetcher_.reset(raw_metadata_fetcher_); - return std::move(metadata_fetcher_); - }, - "credentials_provider_cluster"); + nullptr, "credentials_provider_cluster"); } void expectSessionTokenCurl(const absl::optional& token) { @@ -328,18 +325,33 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(token)); } - void expectSessionTokenHttpAsync(const std::string&& token) { + void expectSessionTokenHttpAsync(const uint64_t status_code, const std::string&& token) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, {":authority", "169.254.169.254:80"}, {":method", "PUT"}, {":scheme", "http"}, {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly( - Invoke([token = std::move(token)](Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { + .WillRepeatedly(Invoke([this, status_code, token = std::move(token)]( + Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + if (status_code == enumToInt(Http::Code::OK)) { + if (!token.empty()) { receiver.onMetadataSuccess(std::move(token)); - })); + } else { + EXPECT_CALL( + *raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) + .WillRepeatedly(testing::Return("InvalidMetadata")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + EXPECT_CALL(*raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) + .WillRepeatedly(testing::Return("Network")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } + })); } void expectCredentialListingCurl(const absl::optional& listing) { @@ -359,30 +371,62 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); } - void expectCredentialListingHttpAsync(const std::string&& instance_role) { + void expectCredentialListingHttpAsync(const uint64_t status_code, + const std::string&& instance_role) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, {":scheme", "http"}, {":method", "GET"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([instance_role = std::move(instance_role)]( + .WillRepeatedly(Invoke([this, status_code, instance_role = std::move(instance_role)]( Http::RequestMessage&, Tracing::Span&, MetadataFetcher::MetadataReceiver& receiver) { - receiver.onMetadataSuccess(std::move(instance_role)); + if (status_code == enumToInt(Http::Code::OK)) { + if (!instance_role.empty()) { + receiver.onMetadataSuccess(std::move(instance_role)); + } else { + EXPECT_CALL( + *raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) + .WillRepeatedly(testing::Return("InvalidMetadata")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + EXPECT_CALL(*raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) + .WillRepeatedly(testing::Return("Network")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } })); } - void expectCredentialListingHttpAsyncSecure(const std::string&& instance_role) { + void expectCredentialListingHttpAsyncSecure(const uint64_t status_code, + const std::string&& instance_role) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, {":scheme", "http"}, {":method", "GET"}, {"X-aws-ec2-metadata-token", "TOKEN"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([instance_role = std::move(instance_role)]( + .WillRepeatedly(Invoke([this, status_code, instance_role = std::move(instance_role)]( Http::RequestMessage&, Tracing::Span&, MetadataFetcher::MetadataReceiver& receiver) { - receiver.onMetadataSuccess(std::move(instance_role)); + if (status_code == enumToInt(Http::Code::OK)) { + if (!instance_role.empty()) { + receiver.onMetadataSuccess(std::move(instance_role)); + } else { + EXPECT_CALL( + *raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) + .WillRepeatedly(testing::Return("InvalidMetadata")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + EXPECT_CALL(*raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) + .WillRepeatedly(testing::Return("Network")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } })); } @@ -405,21 +449,39 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } - void expectDocumentHttpAsync(const std::string&& credential_document_value) { + void expectDocumentHttpAsync(const uint64_t status_code, + const std::string&& credential_document_value) { Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, {":scheme", "http"}, {":method", "GET"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([credential_document_value = std::move(credential_document_value)]( + .WillRepeatedly(Invoke([this, status_code, + credential_document_value = std::move(credential_document_value)]( Http::RequestMessage&, Tracing::Span&, MetadataFetcher::MetadataReceiver& receiver) { - receiver.onMetadataSuccess(std::move(credential_document_value)); + if (status_code == enumToInt(Http::Code::OK)) { + if (!credential_document_value.empty()) { + receiver.onMetadataSuccess(std::move(credential_document_value)); + } else { + EXPECT_CALL( + *raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) + .WillRepeatedly(testing::Return("InvalidMetadata")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + EXPECT_CALL(*raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) + .WillRepeatedly(testing::Return("Network")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } })); } - void expectDocumentHttpAsyncSecure(const std::string&& credential_document_value) { + void expectDocumentHttpAsyncSecure(const uint64_t status_code, + const std::string&& credential_document_value) { Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, @@ -427,10 +489,26 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { {":method", "GET"}, {"X-aws-ec2-metadata-token", "TOKEN"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([credential_document_value = std::move(credential_document_value)]( + .WillRepeatedly(Invoke([this, status_code, + credential_document_value = std::move(credential_document_value)]( Http::RequestMessage&, Tracing::Span&, MetadataFetcher::MetadataReceiver& receiver) { - receiver.onMetadataSuccess(std::move(credential_document_value)); + if (status_code == enumToInt(Http::Code::OK)) { + if (!credential_document_value.empty()) { + receiver.onMetadataSuccess(std::move(credential_document_value)); + } else { + EXPECT_CALL( + *raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) + .WillRepeatedly(testing::Return("InvalidMetadata")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + EXPECT_CALL(*raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) + .WillRepeatedly(testing::Return("Network")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } })); } @@ -481,11 +559,11 @@ lb_policy: ROUND_ROBIN EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) .WillOnce(Return(true)); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); // Cancel is called twice. EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - expectDocumentHttpAsyncSecure(std::move(R"EOF( + expectDocumentHttpAsyncSecure(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -496,7 +574,7 @@ lb_policy: ROUND_ROBIN setupProviderWithContext(); } -TEST_F(InstanceProfileCredentialsProviderTest, TestClusterMissingExpectEnvoyException) { +TEST_F(InstanceProfileCredentialsProviderTest, TestClusterMissing) { // Setup without thread local cluster Http::RequestMessageImpl message; @@ -512,8 +590,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, TestClusterMissingExpectEnvoyExce TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string())); + expectSessionTokenHttpAsync(403 /*Forbidden*/, std::move(std::string())); + expectCredentialListingHttpAsync(403 /*Forbidden*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -535,8 +613,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingUnsecure) TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string())); + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(401 /*Unauthorized*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -558,8 +636,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string(""))); + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string(""))); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -581,8 +659,54 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string(""))); + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string(""))); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("\n"))); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called once. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called once for fetching once again as previous attempt wasn't a success. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingSecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("\n"))); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -601,12 +725,60 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { EXPECT_FALSE(credentials.sessionToken().has_value()); } +TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentUnsecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentHttpAsync(401 /*Unauthorized*/, std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called thrice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentSecure) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentHttpAsyncSecure(401 /*Unauthorized*/, std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Cancel is called twice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); + // Expect refresh timer to be started after fetch done from init. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + // Cancel is called thrice. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentHttpAsync(std::move(std::string())); + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentHttpAsync(200, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called twice. @@ -628,9 +800,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentHttpAsyncSecure(std::move(std::string())); + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentHttpAsyncSecure(200, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called twice. @@ -652,9 +824,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string("doc1"))); - expectDocumentHttpAsync(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); + expectDocumentHttpAsync(200, std::move(R"EOF( not json )EOF")); // init_watcher ready is called. @@ -678,9 +850,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(200, std::move(R"EOF( not json )EOF")); // init_watcher ready is called. @@ -704,9 +876,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string("doc1"))); - expectDocumentHttpAsync(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -733,9 +905,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(200, std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -762,9 +934,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string("doc1"))); - expectDocumentHttpAsync(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -811,9 +983,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -860,9 +1032,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string("doc1"))); - expectDocumentHttpAsync(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -891,9 +1063,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnse EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectSessionTokenHttpAsync(std::move(std::string())); - expectCredentialListingHttpAsync(std::move(std::string("doc1"))); - expectDocumentHttpAsync(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move(std::string())); + expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -925,9 +1097,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnse TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -956,9 +1128,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecu EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectSessionTokenHttpAsync(std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(std::move(R"EOF( + expectSessionTokenHttpAsync(200, std::move("TOKEN")); + expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); + expectDocumentHttpAsyncSecure(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1020,6 +1192,26 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingCurlUnsecur } TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingCurlSecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl("TOKEN"); + expectCredentialListingCurlSecure("\n"); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingCurlUnsecure) { + setupProviderForLibcurl(); + expectSessionTokenCurl(absl::optional()); + expectCredentialListingCurl("\n"); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingCurlSecure) { setupProviderForLibcurl(); expectSessionTokenCurl("TOKEN"); expectCredentialListingCurlSecure(""); @@ -1257,11 +1449,7 @@ class TaskRoleCredentialsProviderTest : public testing::Test { [this](Http::RequestMessage& message) -> absl::optional { return this->fetch_metadata_.fetch(message); }, - [this](Upstream::ClusterManager&, absl::string_view) { - metadata_fetcher_.reset(raw_metadata_fetcher_); - return std::move(metadata_fetcher_); - }, - "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); + nullptr, "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); } void expectDocumentCurl(const absl::optional& document) { @@ -1273,18 +1461,33 @@ class TaskRoleCredentialsProviderTest : public testing::Test { EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } - void expectDocumentHttpAsync(const std::string&& document) { + void expectDocumentHttpAsync(const uint64_t status_code, const std::string&& document) { Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, {":authority", "169.254.170.2:80"}, {":scheme", "http"}, {":method", "GET"}, {"authorization", "auth_token"}}; EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly( - Invoke([document = std::move(document)](Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { + .WillRepeatedly(Invoke([this, status_code, document = std::move(document)]( + Http::RequestMessage&, Tracing::Span&, + MetadataFetcher::MetadataReceiver& receiver) { + if (status_code == enumToInt(Http::Code::OK)) { + if (!document.empty()) { receiver.onMetadataSuccess(std::move(document)); - })); + } else { + EXPECT_CALL( + *raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) + .WillRepeatedly(testing::Return("InvalidMetadata")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); + } + } else { + EXPECT_CALL(*raw_metadata_fetcher_, + failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) + .WillRepeatedly(testing::Return("Network")); + receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); + } + })); } TestScopedRuntime scoped_runtime; @@ -1334,7 +1537,7 @@ lb_policy: ROUND_ROBIN EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) .WillOnce(Return(true)); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1346,7 +1549,7 @@ lb_policy: ROUND_ROBIN setupProviderWithContext(); } -TEST_F(TaskRoleCredentialsProviderTest, TestClusterMissingExpectEnvoyException) { +TEST_F(TaskRoleCredentialsProviderTest, TestClusterMissing) { // Setup without thread local cluster Http::RequestMessageImpl message; @@ -1361,7 +1564,29 @@ TEST_F(TaskRoleCredentialsProviderTest, TestClusterMissingExpectEnvoyException) TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocument) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(std::move(std::string())); + expectDocumentHttpAsync(403 /*Forbidden*/, std::move(std::string())); + // init_watcher ready is called. + init_watcher_.expectReady(); + // Expect refresh timer to be started. + EXPECT_CALL(*timer_, enableTimer(_, nullptr)); + setupProviderWithContext(); + + // Cancel is called for fetching once again as previous attempt wasn't a success. + EXPECT_CALL(*raw_metadata_fetcher_, cancel()); + // Expect refresh timer to be stopped and started. + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); + + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + +TEST_F(TaskRoleCredentialsProviderTest, EmptyDocument) { + // Setup timer. + timer_ = new NiceMock(&context_.dispatcher_); + expectDocumentHttpAsync(200, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Expect refresh timer to be started. @@ -1384,7 +1609,7 @@ TEST_F(TaskRoleCredentialsProviderTest, MalformedDocument) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( not json )EOF")); // init_watcher ready is called. @@ -1409,7 +1634,7 @@ TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -1439,7 +1664,7 @@ TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentials) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1486,7 +1711,7 @@ TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1514,7 +1739,7 @@ TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1546,7 +1771,7 @@ TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1575,7 +1800,7 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { // Cancel is called once. EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - expectDocumentHttpAsync(std::move(R"EOF( + expectDocumentHttpAsync(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc index 75a7e39b70c14..e0a5a1664ac3d 100644 --- a/test/extensions/common/aws/metadata_fetcher_test.cc +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -120,6 +120,20 @@ TEST_F(MetadataFetcherTest, TestGet400) { fetcher_->fetch(message, parent_span_, receiver); } +TEST_F(MetadataFetcherTest, TestGet400NoBody) { + // Setup + setupFetcher(); + Http::RequestMessageImpl message; + + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "400", ""); + MockMetadataReceiver receiver; + EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + + // Act + fetcher_->fetch(message, parent_span_, receiver); +} + TEST_F(MetadataFetcherTest, TestGetNoBody) { // Setup setupFetcher(); @@ -128,7 +142,8 @@ TEST_F(MetadataFetcherTest, TestGetNoBody) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", ""); MockMetadataReceiver receiver; EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); - EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); + EXPECT_CALL(receiver, + onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata)); // Act fetcher_->fetch(message, parent_span_, receiver); diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index 92fe1cf253587..de760f5050db6 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -19,6 +19,7 @@ namespace Aws { class MockMetadataFetcher : public MetadataFetcher { public: MOCK_METHOD(void, cancel, ()); + MOCK_METHOD(absl::string_view, failureToString, (MetadataFetcher::MetadataReceiver::Failure)); MOCK_METHOD(void, fetch, (Http::RequestMessage & message, Tracing::Span& parent_span, MetadataFetcher::MetadataReceiver& receiver)); From d3732b641b41c5ce27b0ad1c25a66da8e3f0d50d Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Mon, 2 Oct 2023 19:14:31 +0000 Subject: [PATCH 06/15] fix constant expression failure Signed-off-by: Sunil Narasimhamurthy --- .../extensions/common/aws/credentials_provider_impl.cc | 7 +++---- source/extensions/common/aws/credentials_provider_impl.h | 5 ++--- .../common/aws/credentials_provider_impl_test.cc | 9 +++++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/source/extensions/common/aws/credentials_provider_impl.cc b/source/extensions/common/aws/credentials_provider_impl.cc index 01ee4bec7ba8d..64923d2196c75 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -235,8 +235,7 @@ void InstanceProfileCredentialsProvider::refresh() { return this->fetchInstanceRoleAsync(std::move(arg)); }; continue_on_async_fetch_failure_ = true; - continue_on_async_fetch_failure_reason_ = - "Failed to get token from EC2MetadataService, reason:{}. Falling back to less secure way"; + continue_on_async_fetch_failure_reason_ = "Token fetch failed so fall back to less secure way"; metadata_fetcher_->fetch(token_req_message, Tracing::NullSpan::instance(), *this); } } @@ -376,10 +375,10 @@ void InstanceProfileCredentialsProvider::onMetadataSuccess(const std::string&& b void InstanceProfileCredentialsProvider::onMetadataError(Failure reason) { // TODO(suniltheta): increment fetch failed stats if (continue_on_async_fetch_failure_) { - ENVOY_LOG(warn, continue_on_async_fetch_failure_reason_, + ENVOY_LOG(critical, "{}. Reason: {}", continue_on_async_fetch_failure_reason_, metadata_fetcher_->failureToString(reason)); continue_on_async_fetch_failure_ = false; - continue_on_async_fetch_failure_reason_ = "{}"; + continue_on_async_fetch_failure_reason_ = ""; on_async_fetch_cb_(std::move("")); } else { ENVOY_LOG(error, "AWS Instance metadata fetch failure: {}", diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index e137ce5787181..771e8f2ce77f2 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -146,8 +146,7 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { protected: struct ThreadLocalCredentialsCache : public ThreadLocal::ThreadLocalObject { ThreadLocalCredentialsCache() { - // Creating empty credentials as default. - credentials_ = std::make_shared(); + credentials_ = std::make_shared(); // Creating empty credentials as default. } // The credentials object. CredentialsConstSharedPtr credentials_; @@ -183,7 +182,7 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { OnAsyncFetchCb on_async_fetch_cb_; bool continue_on_async_fetch_failure_ = false; - std::string continue_on_async_fetch_failure_reason_ = "{}"; + std::string continue_on_async_fetch_failure_reason_ = ""; SystemTime last_updated_; Credentials cached_credentials_; Thread::MutexBasicLockable lock_; diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index afbaa2a221929..4f3dfb4535e13 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -1828,6 +1828,15 @@ TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocumentCurl) { EXPECT_FALSE(credentials.sessionToken().has_value()); } +TEST_F(TaskRoleCredentialsProviderTest, EmptyDocumentCurl) { + setupProviderForLibcurl(); + expectDocumentCurl(""); + const auto credentials = provider_->getCredentials(); + EXPECT_FALSE(credentials.accessKeyId().has_value()); + EXPECT_FALSE(credentials.secretAccessKey().has_value()); + EXPECT_FALSE(credentials.sessionToken().has_value()); +} + TEST_F(TaskRoleCredentialsProviderTest, MalformedDocumentCurl) { setupProviderForLibcurl(); expectDocumentCurl(R"EOF( From bbf86e8d6db879b9460548ec77711595726c0f67 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Wed, 11 Oct 2023 00:57:56 +0000 Subject: [PATCH 07/15] fix asan memory leak in test Signed-off-by: Sunil Narasimhamurthy --- .../aws/credentials_provider_impl_test.cc | 558 +++++++++--------- 1 file changed, 294 insertions(+), 264 deletions(-) diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index 4f3dfb4535e13..fddeed91990a0 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -276,6 +276,7 @@ messageMatches(const Http::TestRequestHeaderMapImpl& expected_headers) { return testing::MakeMatcher(new MessageMatcher(expected_headers)); } +// Begin unit test for new option via Http Async client. class InstanceProfileCredentialsProviderTest : public testing::Test { public: InstanceProfileCredentialsProviderTest() @@ -305,27 +306,7 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { init_target_handle_->initialize(init_watcher_); } - void setupProviderForLibcurl() { - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); - provider_ = std::make_shared( - *api_, absl::nullopt, - [this](Http::RequestMessage& message) -> absl::optional { - return this->fetch_metadata_.fetch(message); - }, - nullptr, "credentials_provider_cluster"); - } - - void expectSessionTokenCurl(const absl::optional& token) { - Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, - {":authority", "169.254.169.254:80"}, - {":method", "PUT"}, - {":scheme", "http"}, - {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; - EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(token)); - } - - void expectSessionTokenHttpAsync(const uint64_t status_code, const std::string&& token) { + void expectSessionToken(const uint64_t status_code, const std::string&& token) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, {":authority", "169.254.169.254:80"}, {":method", "PUT"}, @@ -354,25 +335,7 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { })); } - void expectCredentialListingCurl(const absl::optional& listing) { - Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, - {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, - {":method", "GET"}}; - EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); - } - - void expectCredentialListingCurlSecure(const absl::optional& listing) { - Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, - {":authority", "169.254.169.254:80"}, - {":method", "GET"}, - {":scheme", "http"}, - {"X-aws-ec2-metadata-token", "TOKEN"}}; - EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); - } - - void expectCredentialListingHttpAsync(const uint64_t status_code, - const std::string&& instance_role) { + void expectCredentialListing(const uint64_t status_code, const std::string&& instance_role) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, {":scheme", "http"}, @@ -400,8 +363,8 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { })); } - void expectCredentialListingHttpAsyncSecure(const uint64_t status_code, - const std::string&& instance_role) { + void expectCredentialListingSecure(const uint64_t status_code, + const std::string&& instance_role) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, {":scheme", "http"}, @@ -430,27 +393,7 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { })); } - void expectDocumentCurl(const absl::optional& document) { - Http::TestRequestHeaderMapImpl headers{ - {":path", "/latest/meta-data/iam/security-credentials/doc1"}, - {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, - {":method", "GET"}}; - EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); - } - - void expectDocumentCurlSecure(const absl::optional& document) { - Http::TestRequestHeaderMapImpl headers{ - {":path", "/latest/meta-data/iam/security-credentials/doc1"}, - {":authority", "169.254.169.254:80"}, - {":method", "GET"}, - {":scheme", "http"}, - {"X-aws-ec2-metadata-token", "TOKEN"}}; - EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); - } - - void expectDocumentHttpAsync(const uint64_t status_code, - const std::string&& credential_document_value) { + void expectDocument(const uint64_t status_code, const std::string&& credential_document_value) { Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, @@ -480,8 +423,8 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { })); } - void expectDocumentHttpAsyncSecure(const uint64_t status_code, - const std::string&& credential_document_value) { + void expectDocumentSecure(const uint64_t status_code, + const std::string&& credential_document_value) { Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, @@ -527,7 +470,6 @@ class InstanceProfileCredentialsProviderTest : public testing::Test { std::chrono::milliseconds expected_duration_; }; -// Begin unit test for new option via Http Async TEST_F(InstanceProfileCredentialsProviderTest, TestAddMissingCluster) { // Setup without thread local cluster yet envoy::config::cluster::v3::Cluster expected_cluster; @@ -559,11 +501,11 @@ lb_policy: ROUND_ROBIN EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) .WillOnce(Return(true)); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1"))); // Cancel is called twice. EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - expectDocumentHttpAsyncSecure(200, std::move(R"EOF( + expectDocumentSecure(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -585,13 +527,15 @@ TEST_F(InstanceProfileCredentialsProviderTest, TestClusterMissing) { // init_watcher ready is not called. init_watcher_.expectReady().Times(0); setupProvider(); + // Below line is not testing anything, will just avoid asan failure with memory leak. + metadata_fetcher_.reset(raw_metadata_fetcher_); } TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(403 /*Forbidden*/, std::move(std::string())); - expectCredentialListingHttpAsync(403 /*Forbidden*/, std::move(std::string())); + expectSessionToken(403 /*Forbidden*/, std::move(std::string())); + expectCredentialListing(403 /*Forbidden*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -613,8 +557,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingUnsecure) TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(401 /*Unauthorized*/, std::move(std::string())); + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(401 /*Unauthorized*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -636,8 +580,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string(""))); + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string(""))); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -659,8 +603,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string(""))); + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string(""))); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -682,8 +626,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("\n"))); + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("\n"))); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -705,8 +649,8 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingUnsecur TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("\n"))); + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("\n"))); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called once. @@ -728,9 +672,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingSecure) TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentHttpAsync(401 /*Unauthorized*/, std::move(std::string())); + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocument(401 /*Unauthorized*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called twice. @@ -752,9 +696,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentHttpAsyncSecure(401 /*Unauthorized*/, std::move(std::string())); + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentSecure(401 /*Unauthorized*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called twice. @@ -776,9 +720,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentSecure) { TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentHttpAsync(200, std::move(std::string())); + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocument(200, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called twice. @@ -800,9 +744,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentHttpAsyncSecure(200, std::move(std::string())); + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); + expectDocumentSecure(200, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Cancel is called twice. @@ -824,9 +768,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1"))); + expectDocument(200, std::move(R"EOF( not json )EOF")); // init_watcher ready is called. @@ -850,9 +794,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(200, std::move(R"EOF( + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1"))); + expectDocumentSecure(200, std::move(R"EOF( not json )EOF")); // init_watcher ready is called. @@ -876,9 +820,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1"))); + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -905,9 +849,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(200, std::move(R"EOF( + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1"))); + expectDocumentSecure(200, std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -934,9 +878,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1"))); + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -983,9 +927,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsUnsecure) { TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(200, std::move(R"EOF( + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1"))); + expectDocumentSecure(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1032,9 +976,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnsecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1"))); + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1063,9 +1007,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnse EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectSessionTokenHttpAsync(200, std::move(std::string())); - expectCredentialListingHttpAsync(200, std::move(std::string("doc1"))); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectSessionToken(200, std::move(std::string())); + expectCredentialListing(200, std::move(std::string("doc1"))); + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1097,9 +1041,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnse TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecure) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(200, std::move(R"EOF( + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1"))); + expectDocumentSecure(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1128,9 +1072,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecu EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectSessionTokenHttpAsync(200, std::move("TOKEN")); - expectCredentialListingHttpAsyncSecure(200, std::move(std::string("doc1"))); - expectDocumentHttpAsyncSecure(200, std::move(R"EOF( + expectSessionToken(200, std::move("TOKEN")); + expectCredentialListingSecure(200, std::move(std::string("doc1"))); + expectDocumentSecure(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1158,96 +1102,165 @@ TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecu EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); EXPECT_EQ("new_token1", new_credentials.sessionToken().value()); } -// End unit test for new option via Http Async +// End unit test for new option via Http Async client. + +// Begin unit test for deprecated option using Libcurl client. +// TODO(suniltheta): Remove this test class once libcurl is removed from Envoy. +class InstanceProfileCredentialsProviderUsingLibcurlTest : public testing::Test { +public: + InstanceProfileCredentialsProviderUsingLibcurlTest() + : api_(Api::createApiForTest(time_system_)) {} + + void setupProvider() { + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); + provider_ = std::make_shared( + *api_, absl::nullopt, + [this](Http::RequestMessage& message) -> absl::optional { + return this->fetch_metadata_.fetch(message); + }, + nullptr, "credentials_provider_cluster"); + } + + void expectSessionToken(const absl::optional& token) { + Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, + {":authority", "169.254.169.254:80"}, + {":method", "PUT"}, + {":scheme", "http"}, + {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; + EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(token)); + } + + void expectCredentialListing(const absl::optional& listing) { + Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, + {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, + {":method", "GET"}}; + EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); + } + + void expectCredentialListingSecure(const absl::optional& listing) { + Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, + {":authority", "169.254.169.254:80"}, + {":method", "GET"}, + {":scheme", "http"}, + {"X-aws-ec2-metadata-token", "TOKEN"}}; + EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); + } + + void expectDocument(const absl::optional& document) { + Http::TestRequestHeaderMapImpl headers{ + {":path", "/latest/meta-data/iam/security-credentials/doc1"}, + {":authority", "169.254.169.254:80"}, + {":scheme", "http"}, + {":method", "GET"}}; + EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); + } + + void expectDocumentSecure(const absl::optional& document) { + Http::TestRequestHeaderMapImpl headers{ + {":path", "/latest/meta-data/iam/security-credentials/doc1"}, + {":authority", "169.254.169.254:80"}, + {":method", "GET"}, + {":scheme", "http"}, + {"X-aws-ec2-metadata-token", "TOKEN"}}; + EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); + } -// Begin unit test for deprecated option Libcurl -TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl(absl::optional()); + TestScopedRuntime scoped_runtime; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + NiceMock fetch_metadata_; + InstanceProfileCredentialsProviderPtr provider_; +}; + +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FailedCredentialListingCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing(absl::optional()); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure(absl::optional()); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FailedCredentialListingCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure(absl::optional()); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl(""); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyCredentialListingCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing(""); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("\n"); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyCredentialListingCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure("\n"); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("\n"); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyListCredentialListingCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing("\n"); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure(""); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyListCredentialListingCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure(""); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("doc1\ndoc2\ndoc3"); - expectDocumentCurl(absl::optional()); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MissingDocumentCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing("doc1\ndoc2\ndoc3"); + expectDocument(absl::optional()); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("doc1\ndoc2\ndoc3"); - expectDocumentCurlSecure(absl::optional()); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MissingDocumentCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure("doc1\ndoc2\ndoc3"); + expectDocumentSecure(absl::optional()); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("doc1"); - expectDocumentCurl(R"EOF( +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MalformedDocumentCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing("doc1"); + expectDocument(R"EOF( not json )EOF"); const auto credentials = provider_->getCredentials(); @@ -1256,11 +1269,11 @@ TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentCurlUnsecure) { EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("doc1"); - expectDocumentCurlSecure(R"EOF( +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MalformedDocumentCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure("doc1"); + expectDocumentSecure(R"EOF( not json )EOF"); const auto credentials = provider_->getCredentials(); @@ -1269,11 +1282,11 @@ not json EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("doc1"); - expectDocumentCurl(R"EOF( +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyValuesCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing("doc1"); + expectDocument(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -1286,11 +1299,11 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesCurlUnsecure) { EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("doc1"); - expectDocumentCurlSecure(R"EOF( +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyValuesCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure("doc1"); + expectDocumentSecure(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -1303,11 +1316,11 @@ TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesCurlSecure) { EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsCurlUnsecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("doc1"); - expectDocumentCurl(R"EOF( +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurlUnsecure) { + setupProvider(); + expectSessionToken(absl::optional()); + expectCredentialListing("doc1"); + expectDocument(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1324,11 +1337,11 @@ TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsCurlUnsecure EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsCurlSecure) { - setupProviderForLibcurl(); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("doc1"); - expectDocumentCurlSecure(R"EOF( +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurlSecure) { + setupProvider(); + expectSessionToken("TOKEN"); + expectCredentialListingSecure("doc1"); + expectDocumentSecure(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1345,12 +1358,12 @@ TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsCurlSecure) EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlUnsecure) { - setupProviderForLibcurl(); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationCurlUnsecure) { + setupProvider(); InSequence sequence; - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("doc1"); - expectDocumentCurl(R"EOF( + expectSessionToken(absl::optional()); + expectCredentialListing("doc1"); + expectDocument(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1362,9 +1375,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlUnsecure) EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); time_system_.advanceTimeWait(std::chrono::hours(2)); - expectSessionTokenCurl(absl::optional()); - expectCredentialListingCurl("doc1"); - expectDocumentCurl(R"EOF( + expectSessionToken(absl::optional()); + expectCredentialListing("doc1"); + expectDocument(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1377,12 +1390,12 @@ TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlUnsecure) EXPECT_EQ("new_token", new_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlSecure) { - setupProviderForLibcurl(); +TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationCurlSecure) { + setupProvider(); InSequence sequence; - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("doc1"); - expectDocumentCurlSecure(R"EOF( + expectSessionToken("TOKEN"); + expectCredentialListingSecure("doc1"); + expectDocumentSecure(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1394,9 +1407,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlSecure) { EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); time_system_.advanceTimeWait(std::chrono::hours(2)); - expectSessionTokenCurl("TOKEN"); - expectCredentialListingCurlSecure("doc1"); - expectDocumentCurlSecure(R"EOF( + expectSessionToken("TOKEN"); + expectCredentialListingSecure("doc1"); + expectDocumentSecure(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1408,8 +1421,9 @@ TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationCurlSecure) { EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", new_credentials.sessionToken().value()); } -// End unit test for deprecated option Libcurl +// End unit test for deprecated option using Libcurl client. +// Begin unit test for new option via Http Async client. class TaskRoleCredentialsProviderTest : public testing::Test { public: TaskRoleCredentialsProviderTest() @@ -1441,27 +1455,7 @@ class TaskRoleCredentialsProviderTest : public testing::Test { init_target_handle_->initialize(init_watcher_); } - void setupProviderForLibcurl() { - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); - provider_ = std::make_shared( - *api_, absl::nullopt, - [this](Http::RequestMessage& message) -> absl::optional { - return this->fetch_metadata_.fetch(message); - }, - nullptr, "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); - } - - void expectDocumentCurl(const absl::optional& document) { - Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, - {":authority", "169.254.170.2:80"}, - {":scheme", "http"}, - {":method", "GET"}, - {"authorization", "auth_token"}}; - EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); - } - - void expectDocumentHttpAsync(const uint64_t status_code, const std::string&& document) { + void expectDocument(const uint64_t status_code, const std::string&& document) { Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, {":authority", "169.254.170.2:80"}, {":scheme", "http"}, @@ -1505,7 +1499,6 @@ class TaskRoleCredentialsProviderTest : public testing::Test { std::chrono::milliseconds expected_duration_; }; -// Begin unit test for new option via Http Async TEST_F(TaskRoleCredentialsProviderTest, TestAddMissingCluster) { // Setup without thread local cluster yet envoy::config::cluster::v3::Cluster expected_cluster; @@ -1537,7 +1530,7 @@ lb_policy: ROUND_ROBIN EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) .WillOnce(Return(true)); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1559,12 +1552,14 @@ TEST_F(TaskRoleCredentialsProviderTest, TestClusterMissing) { // init_watcher ready is not called. init_watcher_.expectReady().Times(0); setupProvider(); + // Below line is not testing anything, will just avoid asan failure with memory leak. + metadata_fetcher_.reset(raw_metadata_fetcher_); } TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocument) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(403 /*Forbidden*/, std::move(std::string())); + expectDocument(403 /*Forbidden*/, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Expect refresh timer to be started. @@ -1586,7 +1581,7 @@ TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocument) { TEST_F(TaskRoleCredentialsProviderTest, EmptyDocument) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(200, std::move(std::string())); + expectDocument(200, std::move(std::string())); // init_watcher ready is called. init_watcher_.expectReady(); // Expect refresh timer to be started. @@ -1609,7 +1604,7 @@ TEST_F(TaskRoleCredentialsProviderTest, MalformedDocument) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( not json )EOF")); // init_watcher ready is called. @@ -1634,7 +1629,7 @@ TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -1664,7 +1659,7 @@ TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentials) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1711,7 +1706,7 @@ TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1739,7 +1734,7 @@ TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1771,7 +1766,7 @@ TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { // Setup timer. timer_ = new NiceMock(&context_.dispatcher_); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1800,7 +1795,7 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { // Cancel is called once. EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - expectDocumentHttpAsync(200, std::move(R"EOF( + expectDocument(200, std::move(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1816,30 +1811,65 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } -// End unit test for new option via Http Async +// End unit test for new option via Http Async client. + +// Begin unit test for deprecated option using Libcurl client. +// TODO(suniltheta): Remove this test class once libcurl is removed from Envoy. +class TaskRoleCredentialsProviderUsingLibcurlTest : public testing::Test { +public: + TaskRoleCredentialsProviderUsingLibcurlTest() : api_(Api::createApiForTest(time_system_)) { + // Tue Jan 2 03:04:05 UTC 2018 + time_system_.setSystemTime(std::chrono::milliseconds(1514862245000)); + } + + void setupProvider() { + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); + provider_ = std::make_shared( + *api_, absl::nullopt, + [this](Http::RequestMessage& message) -> absl::optional { + return this->fetch_metadata_.fetch(message); + }, + nullptr, "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); + } -// Begin unit test for deprecated option Libcurl -TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocumentCurl) { - setupProviderForLibcurl(); - expectDocumentCurl(absl::optional()); + void expectDocument(const absl::optional& document) { + Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, + {":authority", "169.254.170.2:80"}, + {":scheme", "http"}, + {":method", "GET"}, + {"authorization", "auth_token"}}; + EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); + } + + TestScopedRuntime scoped_runtime; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + NiceMock fetch_metadata_; + TaskRoleCredentialsProviderPtr provider_; +}; + +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, FailedFetchingDocumentCurl) { + setupProvider(); + expectDocument(absl::optional()); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderTest, EmptyDocumentCurl) { - setupProviderForLibcurl(); - expectDocumentCurl(""); +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, EmptyDocumentCurl) { + setupProvider(); + expectDocument(""); const auto credentials = provider_->getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderTest, MalformedDocumentCurl) { - setupProviderForLibcurl(); - expectDocumentCurl(R"EOF( +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, MalformedDocumentCurl) { + setupProvider(); + expectDocument(R"EOF( not json )EOF"); const auto credentials = provider_->getCredentials(); @@ -1848,9 +1878,9 @@ not json EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderTest, EmptyValuesCurl) { - setupProviderForLibcurl(); - expectDocumentCurl(R"EOF( +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, EmptyValuesCurl) { + setupProvider(); + expectDocument(R"EOF( { "AccessKeyId": "", "SecretAccessKey": "", @@ -1864,9 +1894,9 @@ TEST_F(TaskRoleCredentialsProviderTest, EmptyValuesCurl) { EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentialsCurl) { - setupProviderForLibcurl(); - expectDocumentCurl(R"EOF( +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurl) { + setupProvider(); + expectDocument(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1884,10 +1914,10 @@ TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentialsCurl) { EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpirationCurl) { - setupProviderForLibcurl(); +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, NormalCredentialExpirationCurl) { + setupProvider(); InSequence sequence; - expectDocumentCurl(R"EOF( + expectDocument(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1900,7 +1930,7 @@ TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpirationCurl) { EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); time_system_.advanceTimeWait(std::chrono::hours(2)); - expectDocumentCurl(R"EOF( + expectDocument(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1914,10 +1944,10 @@ TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpirationCurl) { EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } -TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpirationCurl) { - setupProviderForLibcurl(); +TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, TimestampCredentialExpirationCurl) { + setupProvider(); InSequence sequence; - expectDocumentCurl(R"EOF( + expectDocument(R"EOF( { "AccessKeyId": "akid", "SecretAccessKey": "secret", @@ -1929,7 +1959,7 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpirationCurl) { EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - expectDocumentCurl(R"EOF( + expectDocument(R"EOF( { "AccessKeyId": "new_akid", "SecretAccessKey": "new_secret", @@ -1942,7 +1972,7 @@ TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpirationCurl) { EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } -// End unit test for deprecated option Libcurl +// End unit test for deprecated option using Libcurl client. class DefaultCredentialsProviderChainTest : public testing::Test { public: From 5ce6bf8ab25da05965dddc61f44baec09a11a5c1 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Thu, 12 Oct 2023 02:45:25 +0000 Subject: [PATCH 08/15] Honor host value from AWS_CONTAINER_CREDENTIALS_FULL_URI Signed-off-by: Sunil Narasimhamurthy --- .../common/aws/credentials_provider_impl.h | 11 +++++------ source/extensions/common/aws/utility.cc | 14 ++++++-------- test/extensions/common/aws/utility_test.cc | 8 +++++--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index 771e8f2ce77f2..1f5c065542b0e 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -96,7 +96,7 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { MetadataCredentialsProviderBase(Api::Api& api, ServerFactoryContextOptRef context, const FetchMetadataUsingCurl& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name, absl::string_view host) + absl::string_view cluster_name, absl::string_view uri) : api_(api), context_(context), fetch_metadata_using_curl_(fetch_metadata_using_curl), create_metadata_fetcher_cb_(create_metadata_fetcher_cb), cluster_name_(std::string(cluster_name)), cache_duration_(getCacheDuration()), @@ -105,12 +105,12 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials")) { if (!use_libcurl_ && context_) { - context_->mainThreadDispatcher().post([this, host]() { + context_->mainThreadDispatcher().post([this, uri]() { if (!Utility::addInternalClusterStatic(context_->clusterManager(), cluster_name_, "STATIC", - host)) { + uri)) { ENVOY_LOG(critical, "Failed to add [STATIC cluster = {} with address = {}] or cluster not found", - cluster_name_, host); + cluster_name_, uri); return; } }); @@ -250,8 +250,7 @@ class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase, absl::string_view authorization_token = {}, absl::string_view cluster_name = {}) : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, - create_metadata_fetcher_cb, cluster_name, - CONTAINER_METADATA_HOST), + create_metadata_fetcher_cb, cluster_name, credential_uri), credential_uri_(credential_uri), authorization_token_(authorization_token) {} // Following functions are for MetadataFetcher::MetadataReceiver interface diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index bb0fad253e5d5..af4457f4a726f 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -301,20 +301,18 @@ absl::optional Utility::fetchMetadata(Http::RequestMessage& message } bool Utility::addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, - absl::string_view cluster_type, absl::string_view host) { + absl::string_view cluster_type, absl::string_view uri) { // Check if local cluster exists with that name. if (cm.getThreadLocalCluster(cluster_name) == nullptr) { // Make sure we run this on main thread. TRY_ASSERT_MAIN_THREAD { envoy::config::cluster::v3::Cluster cluster; - const auto host_attributes = Http::Utility::parseAuthority(host); + absl::string_view host_port; + absl::string_view path; + Http::Utility::extractHostPathFromUri(uri, host_port, path); + const auto host_attributes = Http::Utility::parseAuthority(host_port); const auto host = host_attributes.host_; - if (!host_attributes.port_) { - ENVOY_LOG_MISC( - error, "Failed to add internal cluster with port value missing from host: {}", host); - return false; - } - const auto port = host_attributes.port_.value(); + const auto port = host_attributes.port_ ? host_attributes.port_.value() : 80; MessageUtil::loadFromYaml(fmt::format(R"EOF( name: {} type: {} diff --git a/test/extensions/common/aws/utility_test.cc b/test/extensions/common/aws/utility_test.cc index 038d8f0e3149f..ff7670ddd024e 100644 --- a/test/extensions/common/aws/utility_test.cc +++ b/test/extensions/common/aws/utility_test.cc @@ -381,11 +381,13 @@ TEST(UtilityTest, AddStaticClusterFailure) { EXPECT_FALSE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1:80")); } -// Verify that missing port value from host will return false. -TEST(UtilityTest, AddStaticClusterFailureWithMissingPort) { +// Verify that even with missing port value and any path set in host will still succeed. +TEST(UtilityTest, AddStaticClusterSuccessEvenWithMissingPort) { NiceMock cm_; EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); - EXPECT_FALSE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1")); + EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)).WillOnce(Return(true)); + EXPECT_TRUE( + Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1/something")); } } // namespace From 0284a531326af84c78606d7bd1b30133ad38be6e Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Fri, 13 Oct 2023 06:48:54 +0000 Subject: [PATCH 09/15] address review comments * move implementation from header to cc file * Don't create duplicate MockUpstream * Use named args - less error prone * Don't restrict runtime value read to be in just constructor Signed-off-by: Sunil Narasimhamurthy --- .gitignore | 1 - .../common/aws/credentials_provider_impl.cc | 98 +++++++++++++++++-- .../common/aws/credentials_provider_impl.h | 90 ++++------------- source/extensions/common/aws/utility.cc | 14 +-- test/extensions/common/aws/BUILD | 1 + .../aws/credentials_provider_impl_test.cc | 4 +- .../common/aws/metadata_fetcher_test.cc | 2 + test/extensions/common/aws/mocks.cc | 40 -------- test/extensions/common/aws/mocks.h | 26 ----- 9 files changed, 121 insertions(+), 155 deletions(-) diff --git a/.gitignore b/.gitignore index 18c3326cd76af..6aad749804db6 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,3 @@ distribution/custom examples/websocket/certs /contrib/golang/**/test_data/go.sum /contrib/golang/**/test_data/*/go.sum -env/ diff --git a/source/extensions/common/aws/credentials_provider_impl.cc b/source/extensions/common/aws/credentials_provider_impl.cc index 64923d2196c75..a4ccfe9f19ed4 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -45,6 +45,8 @@ constexpr char AWS_EC2_METADATA_DISABLED[] = "AWS_EC2_METADATA_DISABLED"; constexpr std::chrono::hours REFRESH_INTERVAL{1}; constexpr std::chrono::seconds REFRESH_GRACE_PERIOD{5}; +constexpr char EC2_METADATA_HOST[] = "169.254.169.254:80"; +constexpr char CONTAINER_METADATA_HOST[] = "169.254.170.2:80"; constexpr char EC2_IMDS_TOKEN_RESOURCE[] = "/latest/api/token"; constexpr char EC2_IMDS_TOKEN_HEADER[] = "X-aws-ec2-metadata-token"; constexpr char EC2_IMDS_TOKEN_TTL_HEADER[] = "X-aws-ec2-metadata-token-ttl-seconds"; @@ -82,9 +84,54 @@ void CachedCredentialsProviderBase::refreshIfNeeded() { } } +// TODO(suniltheta): The field context is of type ServerFactoryContextOptRef so that an +// optional empty value can be set. Especially in aws iam plugin the cluster manager +// obtained from server factory context object is not fully initialized due to the +// reasons explained in https://github.com/envoyproxy/envoy/issues/27586 which cannot +// utilize http async client here to fetch AWS credentials. For time being if context +// is empty then will use libcurl to fetch the credentials. +MetadataCredentialsProviderBase::MetadataCredentialsProviderBase( + Api::Api& api, ServerFactoryContextOptRef context, + const CurlMetadataFetcher& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, + absl::string_view uri) + : api_(api), context_(context), fetch_metadata_using_curl_(fetch_metadata_using_curl), + create_metadata_fetcher_cb_(create_metadata_fetcher_cb), + cluster_name_(std::string(cluster_name)), cache_duration_(getCacheDuration()), + debug_name_(absl::StrCat("Fetching aws credentials from cluster=", cluster_name)) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && + context_) { + context_->mainThreadDispatcher().post([this, uri]() { + if (!Utility::addInternalClusterStatic(context_->clusterManager(), cluster_name_, "STATIC", + uri)) { + ENVOY_LOG(critical, + "Failed to add [STATIC cluster = {} with address = {}] or cluster not found", + cluster_name_, uri); + return; + } + }); + + tls_ = ThreadLocal::TypedSlot::makeUnique(context_->threadLocal()); + tls_->set( + [](Envoy::Event::Dispatcher&) { return std::make_shared(); }); + + cache_duration_timer_ = context_->mainThreadDispatcher().createTimer([this]() -> void { + const Thread::LockGuard lock(lock_); + refresh(); + }); + + // Register with init_manager, force the listener to wait for fetching (refresh). + init_target_ = std::make_unique(debug_name_, [this]() -> void { refresh(); }); + context_->initManager().add(*init_target_); + } +} + Credentials MetadataCredentialsProviderBase::getCredentials() { refreshIfNeeded(); - if (!use_libcurl_ && context_ && tls_) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && + context_ && tls_) { // If server factor context was supplied then we would have thread local slot initialized. return *(*tls_)->credentials_.get(); } else { @@ -100,7 +147,9 @@ std::chrono::seconds MetadataCredentialsProviderBase::getCacheDuration() { } void MetadataCredentialsProviderBase::handleFetchDone() { - if (!use_libcurl_ && context_) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && + context_) { if (init_target_) { init_target_->ready(); init_target_.reset(); @@ -111,6 +160,16 @@ void MetadataCredentialsProviderBase::handleFetchDone() { } } +void MetadataCredentialsProviderBase::setCredentialsToAllThreads( + CredentialsConstUniquePtr&& creds) { + CredentialsConstSharedPtr shared_credentials = std::move(creds); + if (tls_) { + tls_->runOnAllThreads([shared_credentials](OptRef obj) { + obj->credentials_ = shared_credentials; + }); + } +} + bool CredentialsFileCredentialsProvider::needsRefresh() { return api_.timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; } @@ -192,6 +251,14 @@ void CredentialsFileCredentialsProvider::extractCredentials(const std::string& c last_updated_ = api_.timeSource().systemTime(); } +InstanceProfileCredentialsProvider::InstanceProfileCredentialsProvider( + Api::Api& api, ServerFactoryContextOptRef context, + const CurlMetadataFetcher& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name) + : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, + create_metadata_fetcher_cb, cluster_name, EC2_METADATA_HOST) { +} + bool InstanceProfileCredentialsProvider::needsRefresh() { return api_.timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; } @@ -209,7 +276,9 @@ void InstanceProfileCredentialsProvider::refresh() { token_req_message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_TTL_HEADER), EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE); - if (use_libcurl_ || !context_) { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") || + !context_) { // Using curl to fetch the AWS credentials where we first get the token. const auto token_string = fetch_metadata_using_curl_(token_req_message); if (token_string) { @@ -357,7 +426,9 @@ void InstanceProfileCredentialsProvider::extractCredentials( session_token.empty() ? "" : "*****"); last_updated_ = api_.timeSource().systemTime(); - if (!use_libcurl_ && context_) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && + context_) { setCredentialsToAllThreads( std::make_unique(access_key_id, secret_access_key, session_token)); } else { @@ -387,6 +458,15 @@ void InstanceProfileCredentialsProvider::onMetadataError(Failure reason) { } } +TaskRoleCredentialsProvider::TaskRoleCredentialsProvider( + Api::Api& api, ServerFactoryContextOptRef context, + const CurlMetadataFetcher& fetch_metadata_using_curl, + CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view credential_uri, + absl::string_view authorization_token = {}, absl::string_view cluster_name = {}) + : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, + create_metadata_fetcher_cb, cluster_name, credential_uri), + credential_uri_(credential_uri), authorization_token_(authorization_token) {} + bool TaskRoleCredentialsProvider::needsRefresh() { const auto now = api_.timeSource().systemTime(); bool needs_refresh = @@ -407,7 +487,9 @@ void TaskRoleCredentialsProvider::refresh() { message.headers().setHost(host); message.headers().setPath(path); message.headers().setCopy(Http::CustomHeaders::get().Authorization, authorization_token_); - if (use_libcurl_ || !context_) { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") || + !context_) { // Using curl to fetch the AWS credentials. const auto credential_document = fetch_metadata_using_curl_(message); if (!credential_document) { @@ -466,7 +548,9 @@ void TaskRoleCredentialsProvider::extractCredentials( } last_updated_ = api_.timeSource().systemTime(); - if (!use_libcurl_ && context_) { + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && + context_) { setCredentialsToAllThreads( std::make_unique(access_key_id, secret_access_key, session_token)); } else { @@ -501,7 +585,7 @@ Credentials CredentialsProviderChain::getCredentials() { DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, const CredentialsProviderChainFactories& factories) { ENVOY_LOG(debug, "Using environment credentials provider"); add(factories.createEnvironmentCredentialsProvider()); diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index 1f5c065542b0e..472c6e0f0d1a4 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -16,7 +16,6 @@ #include "source/common/init/target_impl.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" -#include "source/common/runtime/runtime_features.h" #include "source/extensions/common/aws/credentials_provider.h" #include "source/extensions/common/aws/metadata_fetcher.h" @@ -27,11 +26,6 @@ namespace Extensions { namespace Common { namespace Aws { -namespace { -constexpr char EC2_METADATA_HOST[] = "169.254.169.254:80"; -constexpr char CONTAINER_METADATA_HOST[] = "169.254.170.2:80"; -}; // namespace - /** * CreateMetadataFetcherCb is a callback interface for creating a MetadataFetcher instance. */ @@ -90,52 +84,17 @@ class CredentialsFileCredentialsProvider : public CachedCredentialsProviderBase class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { public: - using FetchMetadataUsingCurl = std::function(Http::RequestMessage&)>; + using CurlMetadataFetcher = std::function(Http::RequestMessage&)>; using OnAsyncFetchCb = std::function; MetadataCredentialsProviderBase(Api::Api& api, ServerFactoryContextOptRef context, - const FetchMetadataUsingCurl& fetch_metadata_using_curl, + const CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name, absl::string_view uri) - : api_(api), context_(context), fetch_metadata_using_curl_(fetch_metadata_using_curl), - create_metadata_fetcher_cb_(create_metadata_fetcher_cb), - cluster_name_(std::string(cluster_name)), cache_duration_(getCacheDuration()), - debug_name_(absl::StrCat("Fetching aws credentials from cluster=", cluster_name)), - use_libcurl_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials")) { - - if (!use_libcurl_ && context_) { - context_->mainThreadDispatcher().post([this, uri]() { - if (!Utility::addInternalClusterStatic(context_->clusterManager(), cluster_name_, "STATIC", - uri)) { - ENVOY_LOG(critical, - "Failed to add [STATIC cluster = {} with address = {}] or cluster not found", - cluster_name_, uri); - return; - } - }); - - tls_ = - ThreadLocal::TypedSlot::makeUnique(context_->threadLocal()); - tls_->set([](Envoy::Event::Dispatcher&) { - return std::make_shared(); - }); - - cache_duration_timer_ = context_->mainThreadDispatcher().createTimer([this]() -> void { - const Thread::LockGuard lock(lock_); - refresh(); - }); - - // Register with init_manager, force the listener to wait for fetching (refresh). - init_target_ = - std::make_unique(debug_name_, [this]() -> void { refresh(); }); - context_->initManager().add(*init_target_); - } - } + absl::string_view cluster_name, absl::string_view uri); Credentials getCredentials() override; - const std::string& clusterName() { return cluster_name_; } + const std::string& clusterName() const { return cluster_name_; } // Handle fetch done. void handleFetchDone(); @@ -153,20 +112,13 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { }; // Set Credentials shared_ptr on all threads. - void setCredentialsToAllThreads(CredentialsConstUniquePtr&& creds) { - CredentialsConstSharedPtr shared_credentials = std::move(creds); - if (tls_) { - tls_->runOnAllThreads([shared_credentials](OptRef obj) { - obj->credentials_ = shared_credentials; - }); - } - } + void setCredentialsToAllThreads(CredentialsConstUniquePtr&& creds); Api::Api& api_; // The optional server factory context. ServerFactoryContextOptRef context_; // Store the method to fetch metadata from libcurl (deprecated) - FetchMetadataUsingCurl fetch_metadata_using_curl_; + CurlMetadataFetcher fetch_metadata_using_curl_; // The callback used to create a MetadataFetcher instance. CreateMetadataFetcherCb create_metadata_fetcher_cb_; // The cluster name to use for internal static cluster pointing towards the credentials provider. @@ -192,8 +144,6 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { // Used in logs. const std::string debug_name_; - - const bool use_libcurl_; }; /** @@ -205,12 +155,9 @@ class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBas public MetadataFetcher::MetadataReceiver { public: InstanceProfileCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, - const FetchMetadataUsingCurl& fetch_metadata_using_curl, + const CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name) - : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, - create_metadata_fetcher_cb, cluster_name, - EC2_METADATA_HOST) {} + absl::string_view cluster_name); // Following functions are for MetadataFetcher::MetadataReceiver interface void onMetadataSuccess(const std::string&& body) override; @@ -244,14 +191,11 @@ class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase, public MetadataFetcher::MetadataReceiver { public: TaskRoleCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, - const FetchMetadataUsingCurl& fetch_metadata_using_curl, + const CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view credential_uri, - absl::string_view authorization_token = {}, - absl::string_view cluster_name = {}) - : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, - create_metadata_fetcher_cb, cluster_name, credential_uri), - credential_uri_(credential_uri), authorization_token_(authorization_token) {} + absl::string_view authorization_token, + absl::string_view cluster_name); // Following functions are for MetadataFetcher::MetadataReceiver interface void onMetadataSuccess(const std::string&& body) override; @@ -296,13 +240,13 @@ class CredentialsProviderChainFactories { virtual CredentialsProviderSharedPtr createTaskRoleCredentialsProvider( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, absl::string_view credential_uri, absl::string_view authorization_token = {}) const PURE; virtual CredentialsProviderSharedPtr createInstanceProfileCredentialsProvider( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name) const PURE; }; @@ -318,12 +262,12 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, public: DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl) + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl) : DefaultCredentialsProviderChain(api, context, fetch_metadata_using_curl, *this) {} DefaultCredentialsProviderChain( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, const CredentialsProviderChainFactories& factories); private: @@ -338,7 +282,7 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, CredentialsProviderSharedPtr createTaskRoleCredentialsProvider( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, absl::string_view credential_uri, absl::string_view authorization_token = {}) const override { return std::make_shared(api, context, fetch_metadata_using_curl, @@ -348,7 +292,7 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, CredentialsProviderSharedPtr createInstanceProfileCredentialsProvider( Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl& fetch_metadata_using_curl, + const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name) const override { return std::make_shared( diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index af4457f4a726f..7794b4202a9c8 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -314,19 +314,19 @@ bool Utility::addInternalClusterStatic(Upstream::ClusterManager& cm, absl::strin const auto host = host_attributes.host_; const auto port = host_attributes.port_ ? host_attributes.port_.value() : 80; MessageUtil::loadFromYaml(fmt::format(R"EOF( -name: {} -type: {} +name: {cluster_name} +type: {cluster_type} connectTimeout: 5s lb_policy: ROUND_ROBIN loadAssignment: - clusterName: {} + clusterName: {cluster_name} endpoints: - lbEndpoints: - endpoint: address: socketAddress: - address: {} - portValue: {} + address: {host} + portValue: {port} typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions @@ -334,7 +334,9 @@ lb_policy: ROUND_ROBIN http_protocol_options: accept_http_10: true )EOF", - cluster_name, cluster_type, cluster_name, host, port), + fmt::arg("cluster_name", cluster_name), + fmt::arg("cluster_type", cluster_type), + fmt::arg("host", host), fmt::arg("port", port)), cluster, ProtobufMessage::getNullValidationVisitor()); // TODO(suniltheta): use random number generator here for cluster version. diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index 493873d4ed466..2306da7203071 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -60,6 +60,7 @@ envoy_cc_test( deps = [ "//source/extensions/common/aws:metadata_fetcher_lib", "//test/extensions/common/aws:aws_mocks", + "//test/extensions/filters/http/common:mock_lib", "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/server:factory_context_mocks", diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index fddeed91990a0..515020310c759 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -1996,12 +1996,12 @@ class DefaultCredentialsProviderChainTest : public testing::Test { (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createTaskRoleCredentialsProvider, (Api::Api&, ServerFactoryContextOptRef, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl&, + const MetadataCredentialsProviderBase::CurlMetadataFetcher&, CreateMetadataFetcherCb, absl::string_view, absl::string_view, absl::string_view), (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createInstanceProfileCredentialsProvider, (Api::Api&, ServerFactoryContextOptRef, - const MetadataCredentialsProviderBase::FetchMetadataUsingCurl&, + const MetadataCredentialsProviderBase::CurlMetadataFetcher&, CreateMetadataFetcherCb, absl::string_view), (const)); }; diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc index e0a5a1664ac3d..c3f0bdbb161a5 100644 --- a/test/extensions/common/aws/metadata_fetcher_test.cc +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -9,6 +9,7 @@ #include "source/extensions/common/aws/metadata_fetcher.h" #include "test/extensions/common/aws/mocks.h" +#include "test/extensions/filters/http/common/mock.h" #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/server/factory_context.h" @@ -16,6 +17,7 @@ #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" +using Envoy::Extensions::HttpFilters::Common::MockUpstream; using testing::_; using testing::InSequence; using testing::NiceMock; diff --git a/test/extensions/common/aws/mocks.cc b/test/extensions/common/aws/mocks.cc index bc678bbd8ad2b..479ed4d44613e 100644 --- a/test/extensions/common/aws/mocks.cc +++ b/test/extensions/common/aws/mocks.cc @@ -13,46 +13,6 @@ MockSigner::MockSigner() = default; MockSigner::~MockSigner() = default; -// Mock HTTP upstream with response body. -MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, const std::string& status, - const std::string& response_body) - : request_(&mock_cm.thread_local_cluster_.async_client_), status_(status), - response_body_(response_body) { - ON_CALL(mock_cm.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillByDefault( - Invoke([this](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - Http::ResponseMessagePtr response_message( - new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", status_}}})); - response_message->body().add(response_body_); - cb.onSuccess(request_, std::move(response_message)); - called_count_++; - return &request_; - })); -} - -// Mock HTTP upstream with failure reason. -MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, - Http::AsyncClient::FailureReason reason) - : request_(&mock_cm.thread_local_cluster_.async_client_) { - ON_CALL(mock_cm.thread_local_cluster_.async_client_, send_(testing::_, testing::_, testing::_)) - .WillByDefault(testing::Invoke( - [this, reason](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& cb, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - cb.onFailure(request_, reason); - return &request_; - })); -} - -// Mock HTTP upstream with request. -MockUpstream::MockUpstream(Upstream::MockClusterManager& mock_cm, - Http::MockAsyncClientRequest* request) - : request_(&mock_cm.thread_local_cluster_.async_client_) { - ON_CALL(mock_cm.thread_local_cluster_.async_client_, send_(testing::_, testing::_, testing::_)) - .WillByDefault(testing::Return(request)); -} - } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index de760f5050db6..ebfc04bd31f64 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -62,32 +62,6 @@ class DummyFetchMetadata { absl::optional operator()(Http::RequestMessage&) { return absl::nullopt; } }; -// A mock HTTP upstream with response body. -class MockUpstream { -public: - /** - * Mock upstream which returns a given response body. - */ - MockUpstream(Upstream::MockClusterManager& mock_cm, const std::string& status, - const std::string& response_body); - /** - * Mock upstream which returns a given failure. - */ - MockUpstream(Upstream::MockClusterManager& mock_cm, Http::AsyncClient::FailureReason reason); - /** - * Mock upstream which returns the given request. - */ - MockUpstream(Upstream::MockClusterManager& mock_cm, Http::MockAsyncClientRequest* request); - - int called_count() const { return called_count_; } - -private: - Http::MockAsyncClientRequest request_; - std::string status_; - std::string response_body_; - int called_count_{}; -}; - } // namespace Aws } // namespace Common } // namespace Extensions From be5ecedcf6a88fd6557a05f57e1bf88401269f76 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Fri, 13 Oct 2023 07:25:01 +0000 Subject: [PATCH 10/15] update access modifier Signed-off-by: Sunil Narasimhamurthy --- .../common/aws/credentials_provider_impl.h | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index 472c6e0f0d1a4..7b44863dfc0c9 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -94,11 +94,6 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { Credentials getCredentials() override; - const std::string& clusterName() const { return cluster_name_; } - - // Handle fetch done. - void handleFetchDone(); - // Get the Metadata credentials cache duration. static std::chrono::seconds getCacheDuration(); @@ -111,6 +106,11 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { CredentialsConstSharedPtr credentials_; }; + const std::string& clusterName() const { return cluster_name_; } + + // Handle fetch done. + void handleFetchDone(); + // Set Credentials shared_ptr on all threads. void setCredentialsToAllThreads(CredentialsConstUniquePtr&& creds); @@ -122,7 +122,7 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { // The callback used to create a MetadataFetcher instance. CreateMetadataFetcherCb create_metadata_fetcher_cb_; // The cluster name to use for internal static cluster pointing towards the credentials provider. - std::string cluster_name_; + const std::string cluster_name_; // The cache duration of the fetched credentials. const std::chrono::seconds cache_duration_; // The thread local slot for cache. @@ -131,17 +131,20 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { Envoy::Event::TimerPtr cache_duration_timer_; // The Metadata fetcher object. MetadataFetcherPtr metadata_fetcher_; - + // Callback function to call on successful metadata fetch. OnAsyncFetchCb on_async_fetch_cb_; + // To determine if credentials fetching can continue even after metadata fetch failure. bool continue_on_async_fetch_failure_ = false; + // Reason to log on fetch failure while continue. std::string continue_on_async_fetch_failure_reason_ = ""; + // Last update time to determine expiration. SystemTime last_updated_; + // Cache credentials when using libcurl. Credentials cached_credentials_; + // Lock guard. Thread::MutexBasicLockable lock_; - // The init target. std::unique_ptr init_target_; - // Used in logs. const std::string debug_name_; }; @@ -203,8 +206,8 @@ class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase, private: SystemTime expiration_time_; - std::string credential_uri_; - std::string authorization_token_; + const std::string credential_uri_; + const std::string authorization_token_; bool needsRefresh() override; void refresh() override; From 08dc2ed947ea4621c74b1a3b47e48d9060d007ce Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Fri, 13 Oct 2023 07:49:40 +0000 Subject: [PATCH 11/15] reduce PR 29880 to include metadata fetcher changes Signed-off-by: Sunil Narasimhamurthy --- changelogs/current.yaml | 4 - .../http_filters/_include/aws_credentials.rst | 8 +- source/common/runtime/runtime_features.cc | 3 - source/extensions/common/aws/BUILD | 6 +- .../common/aws/credentials_provider.h | 2 - .../common/aws/credentials_provider_impl.cc | 327 +--- .../common/aws/credentials_provider_impl.h | 170 +- source/extensions/common/aws/utility.cc | 3 +- .../filters/http/aws_lambda/config.cc | 4 +- .../http/aws_request_signing/config.cc | 6 +- .../grpc_credentials/aws_iam/config.cc | 8 +- test/extensions/common/aws/BUILD | 2 - .../aws_metadata_fetcher_integration_test.cc | 7 +- .../aws/credentials_provider_impl_test.cc | 1534 ++--------------- test/extensions/common/aws/mocks.h | 2 +- .../aws_lambda_filter_integration_test.cc | 1 - 16 files changed, 181 insertions(+), 1906 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 715ee51834d40..964a4cd719cbf 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -50,10 +50,6 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* -- area: aws - change: | - uses http async client to fetch the credentials from EC2 instance metadata and ECS task metadata providers instead of libcurl - which is deprecated. To revert this behavior set ``envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials`` to true. - area: ext_authz change: | removing any query parameter in the presence of repeated query parameter keys no longer drops the repeats. diff --git a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst index 2b230fea09bea..83fd1aea5bd0a 100644 --- a/docs/root/configuration/http/http_filters/_include/aws_credentials.rst +++ b/docs/root/configuration/http/http_filters/_include/aws_credentials.rst @@ -13,10 +13,4 @@ secret access key (the session token is optional). 3. Either EC2 instance metadata or ECS task metadata. For EC2 instance metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and ``Token`` are used, and credentials are cached for 1 hour. For ECS task metadata, the fields ``AccessKeyId``, ``SecretAccessKey``, and - ``Token`` are used, and credentials are cached for 1 hour or until they expire (according to the field ``Expiration``). Note the latest - update on AWS credentials provider utility uses http async client functionality by default instead of libcurl to fetch the credentials. - The usage of libcurl is on the deprecation path and will be removed soon. To fetch the credentials from either EC2 instance metadata or - ECS task metadata a static cluster is required pointing towards the credentials provider. The static cluster name has to be - ``ec2_instance_metadata_server_internal`` for fetching from EC2 instance metadata or ``ecs_task_metadata_server_internal`` for fetching - from ECS task metadata. If these clusters are not provided in the bootstrap configuration then either of these will be added by default. - This behavior can be changed by setting ``envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials`` to true. + ``Token`` are used, and credentials are cached for 1 hour or until they expire (according to the field ``Expiration``). diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 1073144f20721..ceaf22acc2a47 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -120,9 +120,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); // TODO(adisuissa): enable by default once this is tested in prod. FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); -// TODO(suniltheta): Once the newly added http async technique proves effective and -// is stabilized get rid of this feature flag and code path that relies on libcurl. -FALSE_RUNTIME_GUARD(envoy_reloadable_features_use_libcurl_to_fetch_aws_credentials); // TODO(#10646) change to true when UHV is sufficiently tested // For more information about Universal Header Validation, please see // https://github.com/envoyproxy/envoy/issues/10646 diff --git a/source/extensions/common/aws/BUILD b/source/extensions/common/aws/BUILD index 39d5ff381ea89..b35e9d31bdac0 100644 --- a/source/extensions/common/aws/BUILD +++ b/source/extensions/common/aws/BUILD @@ -59,15 +59,12 @@ envoy_cc_library( external_deps = ["abseil_time"], deps = [ ":credentials_provider_interface", - ":metadata_fetcher_lib", + ":utility_lib", "//envoy/api:api_interface", "//source/common/common:logger_lib", "//source/common/common:thread_lib", "//source/common/http:utility_lib", - "//source/common/init:target_lib", "//source/common/json:json_loader_lib", - "//source/common/runtime:runtime_features_lib", - "//source/common/tracing:http_tracer_lib", ], ) @@ -84,7 +81,6 @@ envoy_cc_library( "//source/common/common:utility_lib", "//source/common/http:headers_lib", "//source/common/http:utility_lib", - "//source/common/runtime:runtime_features_lib", ], ) diff --git a/source/extensions/common/aws/credentials_provider.h b/source/extensions/common/aws/credentials_provider.h index dc06c0c779880..9de0fe8b7a4df 100644 --- a/source/extensions/common/aws/credentials_provider.h +++ b/source/extensions/common/aws/credentials_provider.h @@ -68,8 +68,6 @@ class CredentialsProvider { virtual Credentials getCredentials() PURE; }; -using CredentialsConstSharedPtr = std::shared_ptr; -using CredentialsConstUniquePtr = std::unique_ptr; using CredentialsProviderSharedPtr = std::shared_ptr; } // namespace Aws diff --git a/source/extensions/common/aws/credentials_provider_impl.cc b/source/extensions/common/aws/credentials_provider_impl.cc index a4ccfe9f19ed4..9cba9e82c265a 100644 --- a/source/extensions/common/aws/credentials_provider_impl.cc +++ b/source/extensions/common/aws/credentials_provider_impl.cc @@ -9,7 +9,6 @@ #include "source/common/http/utility.h" #include "source/common/json/json_loader.h" #include "source/common/runtime/runtime_features.h" -#include "source/common/tracing/http_tracer_impl.h" #include "source/extensions/common/aws/utility.h" #include "absl/strings/str_format.h" @@ -53,9 +52,6 @@ constexpr char EC2_IMDS_TOKEN_TTL_HEADER[] = "X-aws-ec2-metadata-token-ttl-secon constexpr char EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE[] = "21600"; constexpr char SECURITY_CREDENTIALS_PATH[] = "/latest/meta-data/iam/security-credentials"; -constexpr char EC2_METADATA_CLUSTER[] = "ec2_instance_metadata_server_internal"; -constexpr char CONTAINER_METADATA_CLUSTER[] = "ecs_task_metadata_server_internal"; - } // namespace Credentials EnvironmentCredentialsProvider::getCredentials() { @@ -84,92 +80,6 @@ void CachedCredentialsProviderBase::refreshIfNeeded() { } } -// TODO(suniltheta): The field context is of type ServerFactoryContextOptRef so that an -// optional empty value can be set. Especially in aws iam plugin the cluster manager -// obtained from server factory context object is not fully initialized due to the -// reasons explained in https://github.com/envoyproxy/envoy/issues/27586 which cannot -// utilize http async client here to fetch AWS credentials. For time being if context -// is empty then will use libcurl to fetch the credentials. -MetadataCredentialsProviderBase::MetadataCredentialsProviderBase( - Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, - absl::string_view uri) - : api_(api), context_(context), fetch_metadata_using_curl_(fetch_metadata_using_curl), - create_metadata_fetcher_cb_(create_metadata_fetcher_cb), - cluster_name_(std::string(cluster_name)), cache_duration_(getCacheDuration()), - debug_name_(absl::StrCat("Fetching aws credentials from cluster=", cluster_name)) { - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && - context_) { - context_->mainThreadDispatcher().post([this, uri]() { - if (!Utility::addInternalClusterStatic(context_->clusterManager(), cluster_name_, "STATIC", - uri)) { - ENVOY_LOG(critical, - "Failed to add [STATIC cluster = {} with address = {}] or cluster not found", - cluster_name_, uri); - return; - } - }); - - tls_ = ThreadLocal::TypedSlot::makeUnique(context_->threadLocal()); - tls_->set( - [](Envoy::Event::Dispatcher&) { return std::make_shared(); }); - - cache_duration_timer_ = context_->mainThreadDispatcher().createTimer([this]() -> void { - const Thread::LockGuard lock(lock_); - refresh(); - }); - - // Register with init_manager, force the listener to wait for fetching (refresh). - init_target_ = std::make_unique(debug_name_, [this]() -> void { refresh(); }); - context_->initManager().add(*init_target_); - } -} - -Credentials MetadataCredentialsProviderBase::getCredentials() { - refreshIfNeeded(); - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && - context_ && tls_) { - // If server factor context was supplied then we would have thread local slot initialized. - return *(*tls_)->credentials_.get(); - } else { - return cached_credentials_; - } -} - -std::chrono::seconds MetadataCredentialsProviderBase::getCacheDuration() { - // TODO(suniltheta): This value should be configurable. - return std::chrono::seconds( - REFRESH_INTERVAL * 60 * 60 - - REFRESH_GRACE_PERIOD /*TODO: Add jitter from context.api().randomGenerator()*/); -} - -void MetadataCredentialsProviderBase::handleFetchDone() { - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && - context_) { - if (init_target_) { - init_target_->ready(); - init_target_.reset(); - } - if (cache_duration_timer_ && !cache_duration_timer_->enabled()) { - cache_duration_timer_->enableTimer(cache_duration_); - } - } -} - -void MetadataCredentialsProviderBase::setCredentialsToAllThreads( - CredentialsConstUniquePtr&& creds) { - CredentialsConstSharedPtr shared_credentials = std::move(creds); - if (tls_) { - tls_->runOnAllThreads([shared_credentials](OptRef obj) { - obj->credentials_ = shared_credentials; - }); - } -} - bool CredentialsFileCredentialsProvider::needsRefresh() { return api_.timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; } @@ -251,14 +161,6 @@ void CredentialsFileCredentialsProvider::extractCredentials(const std::string& c last_updated_ = api_.timeSource().systemTime(); } -InstanceProfileCredentialsProvider::InstanceProfileCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name) - : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, - create_metadata_fetcher_cb, cluster_name, EC2_METADATA_HOST) { -} - bool InstanceProfileCredentialsProvider::needsRefresh() { return api_.timeSource().systemTime() - last_updated_ > REFRESH_INTERVAL; } @@ -269,51 +171,24 @@ void InstanceProfileCredentialsProvider::refresh() { // First request for a session TOKEN so that we can call EC2MetadataService securely. // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html Http::RequestMessageImpl token_req_message; - token_req_message.headers().setScheme(Http::Headers::get().SchemeValues.Http); token_req_message.headers().setMethod(Http::Headers::get().MethodValues.Put); token_req_message.headers().setHost(EC2_METADATA_HOST); token_req_message.headers().setPath(EC2_IMDS_TOKEN_RESOURCE); token_req_message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_TTL_HEADER), EC2_IMDS_TOKEN_TTL_DEFAULT_VALUE); - - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") || - !context_) { - // Using curl to fetch the AWS credentials where we first get the token. - const auto token_string = fetch_metadata_using_curl_(token_req_message); - if (token_string) { - ENVOY_LOG(debug, "Obtained token to make secure call to EC2MetadataService"); - fetchInstanceRole(std::move(token_string.value())); - } else { - ENVOY_LOG(warn, - "Failed to get token from EC2MetadataService, falling back to less secure way"); - fetchInstanceRole(std::move("")); - } + const auto token_string = metadata_fetcher_(token_req_message); + if (token_string) { + ENVOY_LOG(debug, "Obtained token to make secure call to EC2MetadataService"); + fetchInstanceRole(token_string.value()); } else { - // Stop any existing timer. - if (cache_duration_timer_ && cache_duration_timer_->enabled()) { - cache_duration_timer_->disableTimer(); - } - // Using Http async client to fetch the AWS credentials where we first get the token. - if (!metadata_fetcher_) { - metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); - } else { - metadata_fetcher_->cancel(); // Cancel if there is any inflight request. - } - on_async_fetch_cb_ = [this](const std::string&& arg) { - return this->fetchInstanceRoleAsync(std::move(arg)); - }; - continue_on_async_fetch_failure_ = true; - continue_on_async_fetch_failure_reason_ = "Token fetch failed so fall back to less secure way"; - metadata_fetcher_->fetch(token_req_message, Tracing::NullSpan::instance(), *this); + ENVOY_LOG(warn, "Failed to get token from EC2MetadataService, falling back to less secure way"); + fetchInstanceRole(""); } } -void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string&& token_string, - bool async /*default = false*/) { +void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string& token_string) { // Discover the Role of this instance. Http::RequestMessageImpl message; - message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost(EC2_METADATA_HOST); message.headers().setPath(SECURITY_CREDENTIALS_PATH); @@ -321,43 +196,22 @@ void InstanceProfileCredentialsProvider::fetchInstanceRole(const std::string&& t message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_HEADER), StringUtil::trim(token_string)); } - - if (!async) { - // Using curl to fetch the Instance Role. - const auto instance_role_string = fetch_metadata_using_curl_(message); - if (!instance_role_string) { - ENVOY_LOG(error, "Could not retrieve credentials listing from the EC2MetadataService"); - return; - } - fetchCredentialFromInstanceRole(std::move(instance_role_string.value()), - std::move(token_string)); - } else { - // Using Http async client to fetch the Instance Role. - metadata_fetcher_->cancel(); // Cancel if there is any inflight request. - on_async_fetch_cb_ = [this, token_string = std::move(token_string)](const std::string&& arg) { - return this->fetchCredentialFromInstanceRoleAsync(std::move(arg), std::move(token_string)); - }; - metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this); + const auto instance_role_string = metadata_fetcher_(message); + if (!instance_role_string) { + ENVOY_LOG(error, "Could not retrieve credentials listing from the EC2MetadataService"); + return; } + fetchCredentialFromInstanceRole(instance_role_string.value(), token_string); } void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( - const std::string&& instance_role, const std::string&& token_string, - bool async /*default = false*/) { - + const std::string& instance_role, const std::string& token_string) { if (instance_role.empty()) { - ENVOY_LOG(error, "No roles found to fetch AWS credentials from the EC2MetadataService"); - if (async) { - handleFetchDone(); - } return; } const auto instance_role_list = StringUtil::splitToken(StringUtil::trim(instance_role), "\n"); if (instance_role_list.empty()) { - ENVOY_LOG(error, "No roles found to fetch AWS credentials from the EC2MetadataService"); - if (async) { - handleFetchDone(); - } + ENVOY_LOG(error, "No AWS credentials were found in the EC2MetadataService"); return; } ENVOY_LOG(debug, "AWS credentials list:\n{}", instance_role); @@ -369,8 +223,8 @@ void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( std::string(instance_role_list[0].data(), instance_role_list[0].size()); ENVOY_LOG(debug, "AWS credentials path: {}", credential_path); + // Then fetch and parse the credentials Http::RequestMessageImpl message; - message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost(EC2_METADATA_HOST); message.headers().setPath(credential_path); @@ -378,40 +232,23 @@ void InstanceProfileCredentialsProvider::fetchCredentialFromInstanceRole( message.headers().setCopy(Http::LowerCaseString(EC2_IMDS_TOKEN_HEADER), StringUtil::trim(token_string)); } - - if (!async) { - // Fetch and parse the credentials. - const auto credential_document = fetch_metadata_using_curl_(message); - if (!credential_document) { - ENVOY_LOG(error, "Could not load AWS credentials document from the EC2MetadataService"); - return; - } - extractCredentials(std::move(credential_document.value())); - } else { - // Using Http async client to fetch and parse the AWS credentials. - metadata_fetcher_->cancel(); // Cancel if there is any inflight request. - on_async_fetch_cb_ = [this](const std::string&& arg) { - return this->extractCredentialsAsync(std::move(arg)); - }; - metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this); + const auto credential_document = metadata_fetcher_(message); + if (!credential_document) { + ENVOY_LOG(error, "Could not load AWS credentials document from the EC2MetadataService"); + return; } + extractCredentials(credential_document.value()); } void InstanceProfileCredentialsProvider::extractCredentials( - const std::string&& credential_document_value, bool async /*default = false*/) { + const std::string& credential_document_value) { if (credential_document_value.empty()) { - if (async) { - handleFetchDone(); - } return; } Json::ObjectSharedPtr document_json; TRY_NEEDS_AUDIT { document_json = Json::Factory::loadFromString(credential_document_value); } END_TRY catch (EnvoyException& e) { ENVOY_LOG(error, "Could not parse AWS credentials document: {}", e.what()); - if (async) { - handleFetchDone(); - } return; } @@ -425,53 +262,14 @@ void InstanceProfileCredentialsProvider::extractCredentials( secret_access_key.empty() ? "" : "*****", AWS_SESSION_TOKEN, session_token.empty() ? "" : "*****"); + cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); last_updated_ = api_.timeSource().systemTime(); - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && - context_) { - setCredentialsToAllThreads( - std::make_unique(access_key_id, secret_access_key, session_token)); - } else { - cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); - } - handleFetchDone(); } -void InstanceProfileCredentialsProvider::onMetadataSuccess(const std::string&& body) { - // TODO(suniltheta): increment fetch success stats - ENVOY_LOG(info, "AWS Instance metadata fetch success, calling callback func"); - on_async_fetch_cb_(std::move(body)); -} - -void InstanceProfileCredentialsProvider::onMetadataError(Failure reason) { - // TODO(suniltheta): increment fetch failed stats - if (continue_on_async_fetch_failure_) { - ENVOY_LOG(critical, "{}. Reason: {}", continue_on_async_fetch_failure_reason_, - metadata_fetcher_->failureToString(reason)); - continue_on_async_fetch_failure_ = false; - continue_on_async_fetch_failure_reason_ = ""; - on_async_fetch_cb_(std::move("")); - } else { - ENVOY_LOG(error, "AWS Instance metadata fetch failure: {}", - metadata_fetcher_->failureToString(reason)); - handleFetchDone(); - } -} - -TaskRoleCredentialsProvider::TaskRoleCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view credential_uri, - absl::string_view authorization_token = {}, absl::string_view cluster_name = {}) - : MetadataCredentialsProviderBase(api, context, fetch_metadata_using_curl, - create_metadata_fetcher_cb, cluster_name, credential_uri), - credential_uri_(credential_uri), authorization_token_(authorization_token) {} - bool TaskRoleCredentialsProvider::needsRefresh() { const auto now = api_.timeSource().systemTime(); - bool needs_refresh = - (now - last_updated_ > REFRESH_INTERVAL) || (expiration_time_ - now < REFRESH_GRACE_PERIOD); - return needs_refresh; + return (now - last_updated_ > REFRESH_INTERVAL) || + (expiration_time_ - now < REFRESH_GRACE_PERIOD); } void TaskRoleCredentialsProvider::refresh() { @@ -482,50 +280,26 @@ void TaskRoleCredentialsProvider::refresh() { Http::Utility::extractHostPathFromUri(credential_uri_, host, path); Http::RequestMessageImpl message; - message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost(host); message.headers().setPath(path); message.headers().setCopy(Http::CustomHeaders::get().Authorization, authorization_token_); - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") || - !context_) { - // Using curl to fetch the AWS credentials. - const auto credential_document = fetch_metadata_using_curl_(message); - if (!credential_document) { - ENVOY_LOG(error, "Could not load AWS credentials document from the task role"); - return; - } - extractCredentials(std::move(credential_document.value())); - } else { - // Stop any existing timer. - if (cache_duration_timer_ && cache_duration_timer_->enabled()) { - cache_duration_timer_->disableTimer(); - } - // Using Http async client to fetch the AWS credentials. - if (!metadata_fetcher_) { - metadata_fetcher_ = create_metadata_fetcher_cb_(context_->clusterManager(), clusterName()); - } else { - metadata_fetcher_->cancel(); // Cancel if there is any inflight request. - } - on_async_fetch_cb_ = [this](const std::string&& arg) { - return this->extractCredentials(std::move(arg)); - }; - metadata_fetcher_->fetch(message, Tracing::NullSpan::instance(), *this); + const auto credential_document = metadata_fetcher_(message); + if (!credential_document) { + ENVOY_LOG(error, "Could not load AWS credentials document from the task role"); + return; } + extractCredentials(credential_document.value()); } -void TaskRoleCredentialsProvider::extractCredentials( - const std::string&& credential_document_value) { +void TaskRoleCredentialsProvider::extractCredentials(const std::string& credential_document_value) { if (credential_document_value.empty()) { - handleFetchDone(); return; } Json::ObjectSharedPtr document_json; TRY_NEEDS_AUDIT { document_json = Json::Factory::loadFromString(credential_document_value); } END_TRY catch (EnvoyException& e) { ENVOY_LOG(error, "Could not parse AWS credentials document from the task role: {}", e.what()); - handleFetchDone(); return; } @@ -548,27 +322,7 @@ void TaskRoleCredentialsProvider::extractCredentials( } last_updated_ = api_.timeSource().systemTime(); - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials") && - context_) { - setCredentialsToAllThreads( - std::make_unique(access_key_id, secret_access_key, session_token)); - } else { - cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); - } - handleFetchDone(); -} - -void TaskRoleCredentialsProvider::onMetadataSuccess(const std::string&& body) { - // TODO(suniltheta): increment fetch success stats - ENVOY_LOG(debug, "AWS metadata fetch success, calling callback func"); - on_async_fetch_cb_(std::move(body)); -} - -void TaskRoleCredentialsProvider::onMetadataError(Failure reason) { - // TODO(suniltheta): increment fetch failed stats - ENVOY_LOG(error, "AWS metadata fetch failure: {}", metadata_fetcher_->failureToString(reason)); - handleFetchDone(); + cached_credentials_ = Credentials(access_key_id, secret_access_key, session_token); } Credentials CredentialsProviderChain::getCredentials() { @@ -584,8 +338,7 @@ Credentials CredentialsProviderChain::getCredentials() { } DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, + Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, const CredentialsProviderChainFactories& factories) { ENVOY_LOG(debug, "Using environment credentials provider"); add(factories.createEnvironmentCredentialsProvider()); @@ -605,9 +358,7 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( if (!relative_uri.empty()) { const auto uri = absl::StrCat(CONTAINER_METADATA_HOST, relative_uri); ENVOY_LOG(debug, "Using task role credentials provider with URI: {}", uri); - add(factories.createTaskRoleCredentialsProvider(api, context, fetch_metadata_using_curl, - MetadataFetcher::create, - CONTAINER_METADATA_CLUSTER, uri)); + add(factories.createTaskRoleCredentialsProvider(api, metadata_fetcher, uri)); } else if (!full_uri.empty()) { const auto authorization_token = absl::NullSafeStringView(std::getenv(AWS_CONTAINER_AUTHORIZATION_TOKEN)); @@ -616,19 +367,15 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain( "Using task role credentials provider with URI: " "{} and authorization token", full_uri); - add(factories.createTaskRoleCredentialsProvider( - api, context, fetch_metadata_using_curl, MetadataFetcher::create, - CONTAINER_METADATA_CLUSTER, full_uri, authorization_token)); + add(factories.createTaskRoleCredentialsProvider(api, metadata_fetcher, full_uri, + authorization_token)); } else { ENVOY_LOG(debug, "Using task role credentials provider with URI: {}", full_uri); - add(factories.createTaskRoleCredentialsProvider(api, context, fetch_metadata_using_curl, - MetadataFetcher::create, - CONTAINER_METADATA_CLUSTER, full_uri)); + add(factories.createTaskRoleCredentialsProvider(api, metadata_fetcher, full_uri)); } } else if (metadata_disabled != TRUE) { ENVOY_LOG(debug, "Using instance profile credentials provider"); - add(factories.createInstanceProfileCredentialsProvider( - api, context, fetch_metadata_using_curl, MetadataFetcher::create, EC2_METADATA_CLUSTER)); + add(factories.createInstanceProfileCredentialsProvider(api, metadata_fetcher)); } } diff --git a/source/extensions/common/aws/credentials_provider_impl.h b/source/extensions/common/aws/credentials_provider_impl.h index 7b44863dfc0c9..0b207620ad882 100644 --- a/source/extensions/common/aws/credentials_provider_impl.h +++ b/source/extensions/common/aws/credentials_provider_impl.h @@ -1,23 +1,14 @@ #pragma once #include -#include -#include #include "envoy/api/api.h" -#include "envoy/common/optref.h" #include "envoy/event/timer.h" #include "envoy/http/message.h" -#include "envoy/server/factory_context.h" -#include "source/common/common/lock_guard.h" #include "source/common/common/logger.h" #include "source/common/common/thread.h" -#include "source/common/init/target_impl.h" -#include "source/common/protobuf/message_validator_impl.h" -#include "source/common/protobuf/utility.h" #include "source/extensions/common/aws/credentials_provider.h" -#include "source/extensions/common/aws/metadata_fetcher.h" #include "absl/strings/string_view.h" @@ -26,13 +17,6 @@ namespace Extensions { namespace Common { namespace Aws { -/** - * CreateMetadataFetcherCb is a callback interface for creating a MetadataFetcher instance. - */ -using CreateMetadataFetcherCb = - std::function; -using ServerFactoryContextOptRef = OptRef; - /** * Retrieve AWS credentials from the environment variables. * @@ -84,69 +68,14 @@ class CredentialsFileCredentialsProvider : public CachedCredentialsProviderBase class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { public: - using CurlMetadataFetcher = std::function(Http::RequestMessage&)>; - using OnAsyncFetchCb = std::function; - - MetadataCredentialsProviderBase(Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name, absl::string_view uri); - - Credentials getCredentials() override; + using MetadataFetcher = std::function(Http::RequestMessage&)>; - // Get the Metadata credentials cache duration. - static std::chrono::seconds getCacheDuration(); + MetadataCredentialsProviderBase(Api::Api& api, const MetadataFetcher& metadata_fetcher) + : api_(api), metadata_fetcher_(metadata_fetcher) {} protected: - struct ThreadLocalCredentialsCache : public ThreadLocal::ThreadLocalObject { - ThreadLocalCredentialsCache() { - credentials_ = std::make_shared(); // Creating empty credentials as default. - } - // The credentials object. - CredentialsConstSharedPtr credentials_; - }; - - const std::string& clusterName() const { return cluster_name_; } - - // Handle fetch done. - void handleFetchDone(); - - // Set Credentials shared_ptr on all threads. - void setCredentialsToAllThreads(CredentialsConstUniquePtr&& creds); - Api::Api& api_; - // The optional server factory context. - ServerFactoryContextOptRef context_; - // Store the method to fetch metadata from libcurl (deprecated) - CurlMetadataFetcher fetch_metadata_using_curl_; - // The callback used to create a MetadataFetcher instance. - CreateMetadataFetcherCb create_metadata_fetcher_cb_; - // The cluster name to use for internal static cluster pointing towards the credentials provider. - const std::string cluster_name_; - // The cache duration of the fetched credentials. - const std::chrono::seconds cache_duration_; - // The thread local slot for cache. - ThreadLocal::TypedSlotPtr tls_; - // The timer to trigger fetch due to cache duration. - Envoy::Event::TimerPtr cache_duration_timer_; - // The Metadata fetcher object. - MetadataFetcherPtr metadata_fetcher_; - // Callback function to call on successful metadata fetch. - OnAsyncFetchCb on_async_fetch_cb_; - // To determine if credentials fetching can continue even after metadata fetch failure. - bool continue_on_async_fetch_failure_ = false; - // Reason to log on fetch failure while continue. - std::string continue_on_async_fetch_failure_reason_ = ""; - // Last update time to determine expiration. - SystemTime last_updated_; - // Cache credentials when using libcurl. - Credentials cached_credentials_; - // Lock guard. - Thread::MutexBasicLockable lock_; - // The init target. - std::unique_ptr init_target_; - // Used in logs. - const std::string debug_name_; + MetadataFetcher metadata_fetcher_; }; /** @@ -154,35 +83,17 @@ class MetadataCredentialsProviderBase : public CachedCredentialsProviderBase { * * https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials */ -class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBase, - public MetadataFetcher::MetadataReceiver { +class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBase { public: - InstanceProfileCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name); - - // Following functions are for MetadataFetcher::MetadataReceiver interface - void onMetadataSuccess(const std::string&& body) override; - void onMetadataError(Failure reason) override; + InstanceProfileCredentialsProvider(Api::Api& api, const MetadataFetcher& metadata_fetcher) + : MetadataCredentialsProviderBase(api, metadata_fetcher) {} private: bool needsRefresh() override; void refresh() override; - void fetchInstanceRole(const std::string&& token, bool async = false); - void fetchInstanceRoleAsync(const std::string&& token) { - fetchInstanceRole(std::move(token), true); - } - void fetchCredentialFromInstanceRole(const std::string&& instance_role, const std::string&& token, - bool async = false); - void fetchCredentialFromInstanceRoleAsync(const std::string&& instance_role, - const std::string&& token) { - fetchCredentialFromInstanceRole(std::move(instance_role), std::move(token), true); - } - void extractCredentials(const std::string&& credential_document_value, bool async = false); - void extractCredentialsAsync(const std::string&& credential_document_value) { - extractCredentials(std::move(credential_document_value), true); - } + void fetchInstanceRole(const std::string& token); + void fetchCredentialFromInstanceRole(const std::string& instance_role, const std::string& token); + void extractCredentials(const std::string& credential_document_value); }; /** @@ -190,28 +101,22 @@ class InstanceProfileCredentialsProvider : public MetadataCredentialsProviderBas * * https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#enable_task_iam_roles */ -class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase, - public MetadataFetcher::MetadataReceiver { +class TaskRoleCredentialsProvider : public MetadataCredentialsProviderBase { public: - TaskRoleCredentialsProvider(Api::Api& api, ServerFactoryContextOptRef context, - const CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, + TaskRoleCredentialsProvider(Api::Api& api, const MetadataFetcher& metadata_fetcher, absl::string_view credential_uri, - absl::string_view authorization_token, - absl::string_view cluster_name); - - // Following functions are for MetadataFetcher::MetadataReceiver interface - void onMetadataSuccess(const std::string&& body) override; - void onMetadataError(Failure reason) override; + absl::string_view authorization_token = {}) + : MetadataCredentialsProviderBase(api, metadata_fetcher), credential_uri_(credential_uri), + authorization_token_(authorization_token) {} private: SystemTime expiration_time_; - const std::string credential_uri_; - const std::string authorization_token_; + std::string credential_uri_; + std::string authorization_token_; bool needsRefresh() override; void refresh() override; - void extractCredentials(const std::string&& credential_document_value); + void extractCredentials(const std::string& credential_document_value); }; /** @@ -242,16 +147,12 @@ class CredentialsProviderChainFactories { createCredentialsFileCredentialsProvider(Api::Api& api) const PURE; virtual CredentialsProviderSharedPtr createTaskRoleCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, + Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, absl::string_view credential_uri, absl::string_view authorization_token = {}) const PURE; virtual CredentialsProviderSharedPtr createInstanceProfileCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name) const PURE; + Api::Api& api, + const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher) const PURE; }; /** @@ -264,13 +165,11 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, public CredentialsProviderChainFactories { public: DefaultCredentialsProviderChain( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl) - : DefaultCredentialsProviderChain(api, context, fetch_metadata_using_curl, *this) {} + Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher) + : DefaultCredentialsProviderChain(api, metadata_fetcher, *this) {} DefaultCredentialsProviderChain( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, + Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, const CredentialsProviderChainFactories& factories); private: @@ -284,28 +183,19 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain, } CredentialsProviderSharedPtr createTaskRoleCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, absl::string_view cluster_name, + Api::Api& api, const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher, absl::string_view credential_uri, absl::string_view authorization_token = {}) const override { - return std::make_shared(api, context, fetch_metadata_using_curl, - create_metadata_fetcher_cb, credential_uri, - authorization_token, cluster_name); + return std::make_shared(api, metadata_fetcher, credential_uri, + authorization_token); } CredentialsProviderSharedPtr createInstanceProfileCredentialsProvider( - Api::Api& api, ServerFactoryContextOptRef context, - const MetadataCredentialsProviderBase::CurlMetadataFetcher& fetch_metadata_using_curl, - CreateMetadataFetcherCb create_metadata_fetcher_cb, - absl::string_view cluster_name) const override { - return std::make_shared( - api, context, fetch_metadata_using_curl, create_metadata_fetcher_cb, cluster_name); + Api::Api& api, + const MetadataCredentialsProviderBase::MetadataFetcher& metadata_fetcher) const override { + return std::make_shared(api, metadata_fetcher); } }; -using InstanceProfileCredentialsProviderPtr = std::shared_ptr; -using TaskRoleCredentialsProviderPtr = std::shared_ptr; - } // namespace Aws } // namespace Common } // namespace Extensions diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index 7794b4202a9c8..cd06af9362093 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -241,9 +241,8 @@ absl::optional Utility::fetchMetadata(Http::RequestMessage& message const auto host = message.headers().getHostValue(); const auto path = message.headers().getPathValue(); const auto method = message.headers().getMethodValue(); - const auto scheme = message.headers().getSchemeValue(); - const std::string url = fmt::format("{}://{}{}", scheme, host, path); + const std::string url = fmt::format("http://{}{}", host, path); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT.count()); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); diff --git a/source/extensions/filters/http/aws_lambda/config.cc b/source/extensions/filters/http/aws_lambda/config.cc index adfbf2c277fe6..de5a1cfc0a394 100644 --- a/source/extensions/filters/http/aws_lambda/config.cc +++ b/source/extensions/filters/http/aws_lambda/config.cc @@ -1,6 +1,5 @@ #include "source/extensions/filters/http/aws_lambda/config.h" -#include "envoy/common/optref.h" #include "envoy/extensions/filters/http/aws_lambda/v3/aws_lambda.pb.validate.h" #include "envoy/registry/registry.h" #include "envoy/stats/scope.h" @@ -46,8 +45,7 @@ Http::FilterFactoryCb AwsLambdaFilterFactory::createFilterFactoryFromProtoTyped( auto credentials_provider = std::make_shared( - context.api(), makeOptRef(context.getServerFactoryContext()), - Extensions::Common::Aws::Utility::fetchMetadata); + context.api(), Extensions::Common::Aws::Utility::fetchMetadata); auto signer = std::make_shared( service_name, region, std::move(credentials_provider), diff --git a/source/extensions/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index 6ad378b41f42f..c277f8e600c64 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -1,6 +1,5 @@ #include "source/extensions/filters/http/aws_request_signing/config.h" -#include "envoy/common/optref.h" #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.validate.h" #include "envoy/registry/registry.h" @@ -21,8 +20,7 @@ Http::FilterFactoryCb AwsRequestSigningFilterFactory::createFilterFactoryFromPro auto credentials_provider = std::make_shared( - context.api(), makeOptRef(context.getServerFactoryContext()), - Extensions::Common::Aws::Utility::fetchMetadata); + context.api(), Extensions::Common::Aws::Utility::fetchMetadata); const auto matcher_config = Extensions::Common::Aws::AwsSigV4HeaderExclusionVector( config.match_excluded_headers().begin(), config.match_excluded_headers().end()); auto signer = std::make_unique( @@ -43,7 +41,7 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped( Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { auto credentials_provider = std::make_shared( - context.api(), makeOptRef(context), Extensions::Common::Aws::Utility::fetchMetadata); + context.api(), Extensions::Common::Aws::Utility::fetchMetadata); const auto matcher_config = Extensions::Common::Aws::AwsSigV4HeaderExclusionVector( per_route_config.aws_request_signing().match_excluded_headers().begin(), per_route_config.aws_request_signing().match_excluded_headers().end()); diff --git a/source/extensions/grpc_credentials/aws_iam/config.cc b/source/extensions/grpc_credentials/aws_iam/config.cc index 87fe1c408e8dc..42f7f28a9c41e 100644 --- a/source/extensions/grpc_credentials/aws_iam/config.cc +++ b/source/extensions/grpc_credentials/aws_iam/config.cc @@ -44,14 +44,8 @@ std::shared_ptr AwsIamGrpcCredentialsFactory::getChann const auto& config = Envoy::MessageUtil::downcastAndValidate< const envoy::config::grpc_credential::v3::AwsIamConfig&>( *config_message, ProtobufMessage::getNullValidationVisitor()); - // TODO(suniltheta): Due to the reasons explained in - // https://github.com/envoyproxy/envoy/issues/27586 this aws iam plugin is not able to - // utilize http async client to fetch AWS credentials. For time being this is still using - // libcurl to fetch the credentials. To fully get rid of curl, need to address the below - // usage of AWS credentials common utils. Until then we are setting nullopt for server - // factory context. auto credentials_provider = std::make_shared( - api, absl::nullopt /*Empty factory context*/, Common::Aws::Utility::fetchMetadata); + api, Common::Aws::Utility::fetchMetadata); auto signer = std::make_unique( config.service_name(), getRegion(config), credentials_provider, api.timeSource(), // TODO: extend API to allow specifying header exclusion. ref: diff --git a/test/extensions/common/aws/BUILD b/test/extensions/common/aws/BUILD index 2306da7203071..43ce091b0f550 100644 --- a/test/extensions/common/aws/BUILD +++ b/test/extensions/common/aws/BUILD @@ -75,12 +75,10 @@ envoy_cc_test( srcs = ["credentials_provider_impl_test.cc"], deps = [ "//source/extensions/common/aws:credentials_provider_impl_lib", - "//source/extensions/common/aws:metadata_fetcher_lib", "//test/extensions/common/aws:aws_mocks", "//test/mocks/api:api_mocks", "//test/mocks/event:event_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc index 89946ef92e063..6e71984ee8a7a 100644 --- a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc +++ b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc @@ -99,7 +99,7 @@ TEST_F(AwsMetadataIntegrationTestSuccess, Success) { const auto authority = fmt::format("{}:{}", Network::Test::getLoopbackAddressUrlString(version_), lookupPort("listener_0")); auto headers = Http::RequestHeaderMapPtr{new Http::TestRequestHeaderMapImpl{ - {":path", "/"}, {":authority", authority}, {":scheme", "http"}, {":method", "GET"}}}; + {":path", "/"}, {":authority", authority}, {":method", "GET"}}}; Http::RequestMessageImpl message(std::move(headers)); const auto response = Utility::fetchMetadata(message); @@ -116,7 +116,6 @@ TEST_F(AwsMetadataIntegrationTestSuccess, AuthToken) { auto headers = Http::RequestHeaderMapPtr{ new Http::TestRequestHeaderMapImpl{{":path", "/"}, {":authority", authority}, - {":scheme", "http"}, {":method", "GET"}, {"authorization", "AUTH_TOKEN"}}}; Http::RequestMessageImpl message(std::move(headers)); @@ -156,7 +155,6 @@ TEST_F(AwsMetadataIntegrationTestSuccess, Redirect) { auto headers = Http::RequestHeaderMapPtr{ new Http::TestRequestHeaderMapImpl{{":path", "/redirect"}, {":authority", authority}, - {":scheme", "http"}, {":method", "GET"}, {"authorization", "AUTH_TOKEN"}}}; Http::RequestMessageImpl message(std::move(headers)); @@ -184,7 +182,6 @@ TEST_F(AwsMetadataIntegrationTestFailure, Failure) { auto headers = Http::RequestHeaderMapPtr{ new Http::TestRequestHeaderMapImpl{{":path", "/"}, {":authority", authority}, - {":scheme", "http"}, {":method", "GET"}, {"authorization", "AUTH_TOKEN"}}}; @@ -213,7 +210,7 @@ TEST_F(AwsMetadataIntegrationTestTimeout, Timeout) { const auto authority = fmt::format("{}:{}", Network::Test::getLoopbackAddressUrlString(version_), lookupPort("listener_0")); auto headers = Http::RequestHeaderMapPtr{new Http::TestRequestHeaderMapImpl{ - {":path", "/"}, {":authority", authority}, {":scheme", "http"}, {":method", "GET"}}}; + {":path", "/"}, {":authority", authority}, {":method", "GET"}}}; Http::RequestMessageImpl message(std::move(headers)); const auto start_time = timeSystem().monotonicTime(); diff --git a/test/extensions/common/aws/credentials_provider_impl_test.cc b/test/extensions/common/aws/credentials_provider_impl_test.cc index 515020310c759..a93c9ccf770bd 100644 --- a/test/extensions/common/aws/credentials_provider_impl_test.cc +++ b/test/extensions/common/aws/credentials_provider_impl_test.cc @@ -1,28 +1,18 @@ -#include -#include - #include "source/extensions/common/aws/credentials_provider_impl.h" -#include "source/extensions/common/aws/metadata_fetcher.h" #include "test/extensions/common/aws/mocks.h" #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/server/factory_context.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" -using Envoy::Extensions::Common::Aws::MetadataFetcher; -using Envoy::Extensions::Common::Aws::MetadataFetcherPtr; -using Envoy::Extensions::Common::Aws::MockMetadataFetcher; using testing::_; -using testing::Eq; using testing::InSequence; using testing::NiceMock; using testing::Ref; using testing::Return; -using testing::Throw; namespace Envoy { namespace Extensions { @@ -58,22 +48,6 @@ aws_secret_access_key = profile4_secret aws_session_token = profile4_token )"; -MATCHER_P(WithName, expectedName, "") { return arg.name() == expectedName; } - -MATCHER_P(WithAttribute, expectedCluster, "") { - const auto argSocketAddress = - arg.load_assignment().endpoints()[0].lb_endpoints()[0].endpoint().address().socket_address(); - const auto expectedSocketAddress = expectedCluster.load_assignment() - .endpoints()[0] - .lb_endpoints()[0] - .endpoint() - .address() - .socket_address(); - return arg.name() == expectedCluster.name() && - argSocketAddress.address() == expectedSocketAddress.address() && - argSocketAddress.port_value() == expectedSocketAddress.port_value(); -} - class EvironmentCredentialsProviderTest : public testing::Test { public: ~EvironmentCredentialsProviderTest() override { @@ -276,857 +250,18 @@ messageMatches(const Http::TestRequestHeaderMapImpl& expected_headers) { return testing::MakeMatcher(new MessageMatcher(expected_headers)); } -// Begin unit test for new option via Http Async client. class InstanceProfileCredentialsProviderTest : public testing::Test { public: InstanceProfileCredentialsProviderTest() - : api_(Api::createApiForTest(time_system_)), raw_metadata_fetcher_(new MockMetadataFetcher) {} - - void setupProvider() { - ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); - provider_ = std::make_shared( - *api_, context_, - [this](Http::RequestMessage& message) -> absl::optional { + : api_(Api::createApiForTest(time_system_)), + provider_(*api_, [this](Http::RequestMessage& message) -> absl::optional { return this->fetch_metadata_.fetch(message); - }, - [this](Upstream::ClusterManager&, absl::string_view) { - metadata_fetcher_.reset(raw_metadata_fetcher_); - return std::move(metadata_fetcher_); - }, - "credentials_provider_cluster"); - } - - void setupProviderWithContext() { - EXPECT_CALL(context_.init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { - init_target_handle_ = target.createHandle("test"); - })); - - setupProvider(); - expected_duration_ = provider_->getCacheDuration(); - init_target_handle_->initialize(init_watcher_); - } - - void expectSessionToken(const uint64_t status_code, const std::string&& token) { - Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, - {":authority", "169.254.169.254:80"}, - {":method", "PUT"}, - {":scheme", "http"}, - {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; - EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([this, status_code, token = std::move(token)]( - Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { - if (status_code == enumToInt(Http::Code::OK)) { - if (!token.empty()) { - receiver.onMetadataSuccess(std::move(token)); - } else { - EXPECT_CALL( - *raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) - .WillRepeatedly(testing::Return("InvalidMetadata")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); - } - } else { - EXPECT_CALL(*raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) - .WillRepeatedly(testing::Return("Network")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); - } - })); - } - - void expectCredentialListing(const uint64_t status_code, const std::string&& instance_role) { - Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, - {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, - {":method", "GET"}}; - EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([this, status_code, instance_role = std::move(instance_role)]( - Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { - if (status_code == enumToInt(Http::Code::OK)) { - if (!instance_role.empty()) { - receiver.onMetadataSuccess(std::move(instance_role)); - } else { - EXPECT_CALL( - *raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) - .WillRepeatedly(testing::Return("InvalidMetadata")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); - } - } else { - EXPECT_CALL(*raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) - .WillRepeatedly(testing::Return("Network")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); - } - })); - } - - void expectCredentialListingSecure(const uint64_t status_code, - const std::string&& instance_role) { - Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, - {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, - {":method", "GET"}, - {"X-aws-ec2-metadata-token", "TOKEN"}}; - EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([this, status_code, instance_role = std::move(instance_role)]( - Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { - if (status_code == enumToInt(Http::Code::OK)) { - if (!instance_role.empty()) { - receiver.onMetadataSuccess(std::move(instance_role)); - } else { - EXPECT_CALL( - *raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) - .WillRepeatedly(testing::Return("InvalidMetadata")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); - } - } else { - EXPECT_CALL(*raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) - .WillRepeatedly(testing::Return("Network")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); - } - })); - } - - void expectDocument(const uint64_t status_code, const std::string&& credential_document_value) { - Http::TestRequestHeaderMapImpl headers{ - {":path", "/latest/meta-data/iam/security-credentials/doc1"}, - {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, - {":method", "GET"}}; - EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([this, status_code, - credential_document_value = std::move(credential_document_value)]( - Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { - if (status_code == enumToInt(Http::Code::OK)) { - if (!credential_document_value.empty()) { - receiver.onMetadataSuccess(std::move(credential_document_value)); - } else { - EXPECT_CALL( - *raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) - .WillRepeatedly(testing::Return("InvalidMetadata")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); - } - } else { - EXPECT_CALL(*raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) - .WillRepeatedly(testing::Return("Network")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); - } - })); - } - - void expectDocumentSecure(const uint64_t status_code, - const std::string&& credential_document_value) { - Http::TestRequestHeaderMapImpl headers{ - {":path", "/latest/meta-data/iam/security-credentials/doc1"}, - {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, - {":method", "GET"}, - {"X-aws-ec2-metadata-token", "TOKEN"}}; - EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([this, status_code, - credential_document_value = std::move(credential_document_value)]( - Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { - if (status_code == enumToInt(Http::Code::OK)) { - if (!credential_document_value.empty()) { - receiver.onMetadataSuccess(std::move(credential_document_value)); - } else { - EXPECT_CALL( - *raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) - .WillRepeatedly(testing::Return("InvalidMetadata")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); - } - } else { - EXPECT_CALL(*raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) - .WillRepeatedly(testing::Return("Network")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); - } - })); - } - - TestScopedRuntime scoped_runtime; - Event::SimulatedTimeSystem time_system_; - Api::ApiPtr api_; - NiceMock fetch_metadata_; - MockMetadataFetcher* raw_metadata_fetcher_; - MetadataFetcherPtr metadata_fetcher_; - NiceMock cluster_manager_; - NiceMock context_; - InstanceProfileCredentialsProviderPtr provider_; - Init::TargetHandlePtr init_target_handle_; - NiceMock init_watcher_; - Event::MockTimer* timer_{}; - std::chrono::milliseconds expected_duration_; -}; - -TEST_F(InstanceProfileCredentialsProviderTest, TestAddMissingCluster) { - // Setup without thread local cluster yet - envoy::config::cluster::v3::Cluster expected_cluster; - constexpr static const char* kStaticCluster = R"EOF( -name: credentials_provider_cluster -type: static -connectTimeout: 2s -lb_policy: ROUND_ROBIN -loadAssignment: - clusterName: credentials_provider_cluster - endpoints: - - lbEndpoints: - - endpoint: - address: - socketAddress: - address: "169.254.169.254" - portValue: 80 -typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions - explicit_http_config: - http_protocol_options: - accept_http_10: true - )EOF"; - MessageUtil::loadFromYaml(kStaticCluster, expected_cluster, - ProtobufMessage::getNullValidationVisitor()); - - EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); - EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) - .WillOnce(Return(true)); - - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1"))); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - expectDocumentSecure(200, std::move(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF")); - - setupProviderWithContext(); -} - -TEST_F(InstanceProfileCredentialsProviderTest, TestClusterMissing) { - // Setup without thread local cluster - Http::RequestMessageImpl message; - - EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); - EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithName("credentials_provider_cluster"), _)) - .WillOnce(Throw(EnvoyException("exeption message"))); - - // init_watcher ready is not called. - init_watcher_.expectReady().Times(0); - setupProvider(); - // Below line is not testing anything, will just avoid asan failure with memory leak. - metadata_fetcher_.reset(raw_metadata_fetcher_); -} - -TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(403 /*Forbidden*/, std::move(std::string())); - expectCredentialListing(403 /*Forbidden*/, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(401 /*Unauthorized*/, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string(""))); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called once for fetching once again as previous attempt wasn't a success. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string(""))); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("\n"))); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called once for fetching once again as previous attempt wasn't a success. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, EmptyListCredentialListingSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("\n"))); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocument(401 /*Unauthorized*/, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called thrice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, FailedDocumentSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentSecure(401 /*Unauthorized*/, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called thrice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocument(200, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called thrice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1\ndoc2\ndoc3"))); - expectDocumentSecure(200, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called thrice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1"))); - expectDocument(200, std::move(R"EOF( - not json - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called thrice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1"))); - expectDocumentSecure(200, std::move(R"EOF( - not json - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is called thrice - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1"))); - expectDocument(200, std::move(R"EOF( - { - "AccessKeyId": "", - "SecretAccessKey": "", - "Token": "" - } - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is not called again as we don't expect any more call to fetch until timeout. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1"))); - expectDocumentSecure(200, std::move(R"EOF( - { - "AccessKeyId": "", - "SecretAccessKey": "", - "Token": "" - } - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - // Cancel is not called again as we don't expect any more call to fetch until timeout. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1"))); - expectDocument(200, std::move(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Cancel is not called again as we don't expect any more call to fetch until timeout. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // We don't expect any more call to fetch again. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto cached_credentials = provider_->getCredentials(); - EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); - EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); - EXPECT_EQ("token", cached_credentials.sessionToken().value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1"))); - expectDocumentSecure(200, std::move(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started after fetch done from init. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Cancel is not called again as we don't expect any more call to fetch until timeout. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // We don't expect any more call to fetch again. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto cached_credentials = provider_->getCredentials(); - EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); - EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); - EXPECT_EQ("token", cached_credentials.sessionToken().value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationUnsecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1"))); - expectDocument(200, std::move(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Cancel is not called again as we don't expect any more call to fetch until timeout. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - expectSessionToken(200, std::move(std::string())); - expectCredentialListing(200, std::move(std::string("doc1"))); - expectDocument(200, std::move(R"EOF( - { - "AccessKeyId": "new_akid", - "SecretAccessKey": "new_secret", - "Token": "new_token1" - } - )EOF")); - - // Expect timer to have expired but we would re-start the timer eventually after refresh. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - // Cancel will be called thrice back to back to back. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - time_system_.advanceTimeWait(std::chrono::minutes(61)); - timer_->invokeCallback(); - - // We don't expect timer to be reset again for new fetch. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Similarly we won't call fetch or cancel on metadata fetcher. - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - - const auto new_credentials = provider_->getCredentials(); - EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); - EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); - EXPECT_EQ("new_token1", new_credentials.sessionToken().value()); -} - -TEST_F(InstanceProfileCredentialsProviderTest, RefreshOnCredentialExpirationSecure) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1"))); - expectDocumentSecure(200, std::move(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Cancel is called twice. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(2); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Cancel is not called again as we don't expect any more call to fetch until timeout. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - expectSessionToken(200, std::move("TOKEN")); - expectCredentialListingSecure(200, std::move(std::string("doc1"))); - expectDocumentSecure(200, std::move(R"EOF( - { - "AccessKeyId": "new_akid", - "SecretAccessKey": "new_secret", - "Token": "new_token1" - } - )EOF")); - - // Expect timer to have expired but we would re-start the timer eventually after refresh. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - // Cancel will be called thrice back to back to back. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(3); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - time_system_.advanceTimeWait(std::chrono::minutes(61)); - timer_->invokeCallback(); - - // We don't expect timer to be reset again for new fetch. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Similarly we won't call fetch or cancel on metadata fetcher. - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - - const auto new_credentials = provider_->getCredentials(); - EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); - EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); - EXPECT_EQ("new_token1", new_credentials.sessionToken().value()); -} -// End unit test for new option via Http Async client. - -// Begin unit test for deprecated option using Libcurl client. -// TODO(suniltheta): Remove this test class once libcurl is removed from Envoy. -class InstanceProfileCredentialsProviderUsingLibcurlTest : public testing::Test { -public: - InstanceProfileCredentialsProviderUsingLibcurlTest() - : api_(Api::createApiForTest(time_system_)) {} - - void setupProvider() { - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); - provider_ = std::make_shared( - *api_, absl::nullopt, - [this](Http::RequestMessage& message) -> absl::optional { - return this->fetch_metadata_.fetch(message); - }, - nullptr, "credentials_provider_cluster"); - } + }) {} void expectSessionToken(const absl::optional& token) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/api/token"}, {":authority", "169.254.169.254:80"}, {":method", "PUT"}, - {":scheme", "http"}, {"X-aws-ec2-metadata-token-ttl-seconds", "21600"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(token)); } @@ -1134,7 +269,6 @@ class InstanceProfileCredentialsProviderUsingLibcurlTest : public testing::Test void expectCredentialListing(const absl::optional& listing) { Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, {":method", "GET"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); } @@ -1143,7 +277,6 @@ class InstanceProfileCredentialsProviderUsingLibcurlTest : public testing::Test Http::TestRequestHeaderMapImpl headers{{":path", "/latest/meta-data/iam/security-credentials"}, {":authority", "169.254.169.254:80"}, {":method", "GET"}, - {":scheme", "http"}, {"X-aws-ec2-metadata-token", "TOKEN"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(listing)); } @@ -1152,7 +285,6 @@ class InstanceProfileCredentialsProviderUsingLibcurlTest : public testing::Test Http::TestRequestHeaderMapImpl headers{ {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, - {":scheme", "http"}, {":method", "GET"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } @@ -1162,145 +294,113 @@ class InstanceProfileCredentialsProviderUsingLibcurlTest : public testing::Test {":path", "/latest/meta-data/iam/security-credentials/doc1"}, {":authority", "169.254.169.254:80"}, {":method", "GET"}, - {":scheme", "http"}, {"X-aws-ec2-metadata-token", "TOKEN"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } - TestScopedRuntime scoped_runtime; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; NiceMock fetch_metadata_; - InstanceProfileCredentialsProviderPtr provider_; + InstanceProfileCredentialsProvider provider_; }; -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FailedCredentialListingCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListing) { expectSessionToken(absl::optional()); expectCredentialListing(absl::optional()); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FailedCredentialListingCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, FailedCredentialListingSecure) { expectSessionToken("TOKEN"); expectCredentialListingSecure(absl::optional()); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyCredentialListingCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListing) { expectSessionToken(absl::optional()); expectCredentialListing(""); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyCredentialListingCurlSecure) { - setupProvider(); - expectSessionToken("TOKEN"); - expectCredentialListingSecure("\n"); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyListCredentialListingCurlUnsecure) { - setupProvider(); - expectSessionToken(absl::optional()); - expectCredentialListing("\n"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyListCredentialListingCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, EmptyCredentialListingSecure) { expectSessionToken("TOKEN"); expectCredentialListingSecure(""); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MissingDocumentCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, MissingDocument) { expectSessionToken(absl::optional()); expectCredentialListing("doc1\ndoc2\ndoc3"); expectDocument(absl::optional()); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MissingDocumentCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, MissingDocumentSecure) { expectSessionToken("TOKEN"); expectCredentialListingSecure("doc1\ndoc2\ndoc3"); expectDocumentSecure(absl::optional()); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MalformedDocumentCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumenet) { expectSessionToken(absl::optional()); expectCredentialListing("doc1"); expectDocument(R"EOF( - not json - )EOF"); - const auto credentials = provider_->getCredentials(); +not json +)EOF"); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, MalformedDocumentCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, MalformedDocumentSecure) { expectSessionToken("TOKEN"); expectCredentialListingSecure("doc1"); expectDocumentSecure(R"EOF( not json )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyValuesCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, EmptyValues) { expectSessionToken(absl::optional()); expectCredentialListing("doc1"); expectDocument(R"EOF( - { - "AccessKeyId": "", - "SecretAccessKey": "", - "Token": "" - } - )EOF"); - const auto credentials = provider_->getCredentials(); +{ + "AccessKeyId": "", + "SecretAccessKey": "", + "Token": "" +} +)EOF"); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyValuesCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, EmptyValuesSecure) { expectSessionToken("TOKEN"); expectCredentialListingSecure("doc1"); expectDocumentSecure(R"EOF( @@ -1310,35 +410,33 @@ TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, EmptyValuesCurlSecure "Token": "" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentials) { expectSessionToken(absl::optional()); expectCredentialListing("doc1"); expectDocument(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF"); - const auto credentials = provider_->getCredentials(); +{ + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" +} +)EOF"); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - const auto cached_credentials = provider_->getCredentials(); + const auto cached_credentials = provider_.getCredentials(); EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, FullCachedCredentialsSecure) { expectSessionToken("TOKEN"); expectCredentialListingSecure("doc1"); expectDocumentSecure(R"EOF( @@ -1348,29 +446,28 @@ TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, FullCachedCredentials "Token": "token" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - const auto cached_credentials = provider_->getCredentials(); + const auto cached_credentials = provider_.getCredentials(); EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationCurlUnsecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpiration) { InSequence sequence; expectSessionToken(absl::optional()); expectCredentialListing("doc1"); expectDocument(R"EOF( - { - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token" - } - )EOF"); - const auto credentials = provider_->getCredentials(); +{ + "AccessKeyId": "akid", + "SecretAccessKey": "secret", + "Token": "token" +} +)EOF"); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); @@ -1378,20 +475,19 @@ TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationC expectSessionToken(absl::optional()); expectCredentialListing("doc1"); expectDocument(R"EOF( - { - "AccessKeyId": "new_akid", - "SecretAccessKey": "new_secret", - "Token": "new_token" - } - )EOF"); - const auto new_credentials = provider_->getCredentials(); +{ + "AccessKeyId": "new_akid", + "SecretAccessKey": "new_secret", + "Token": "new_token" +} +)EOF"); + const auto new_credentials = provider_.getCredentials(); EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", new_credentials.sessionToken().value()); } -TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationCurlSecure) { - setupProvider(); +TEST_F(InstanceProfileCredentialsProviderTest, CredentialExpirationSecure) { InSequence sequence; expectSessionToken("TOKEN"); expectCredentialListingSecure("doc1"); @@ -1402,7 +498,7 @@ TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationC "Token": "token" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); @@ -1416,470 +512,59 @@ TEST_F(InstanceProfileCredentialsProviderUsingLibcurlTest, CredentialExpirationC "Token": "new_token" } )EOF"); - const auto new_credentials = provider_->getCredentials(); + const auto new_credentials = provider_.getCredentials(); EXPECT_EQ("new_akid", new_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", new_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", new_credentials.sessionToken().value()); } -// End unit test for deprecated option using Libcurl client. -// Begin unit test for new option via Http Async client. class TaskRoleCredentialsProviderTest : public testing::Test { public: TaskRoleCredentialsProviderTest() - : api_(Api::createApiForTest(time_system_)), raw_metadata_fetcher_(new MockMetadataFetcher) { + : api_(Api::createApiForTest(time_system_)), + provider_( + *api_, + [this](Http::RequestMessage& message) -> absl::optional { + return this->fetch_metadata_.fetch(message); + }, + "169.254.170.2:80/path/to/doc", "auth_token") { // Tue Jan 2 03:04:05 UTC 2018 time_system_.setSystemTime(std::chrono::milliseconds(1514862245000)); } - void setupProvider() { - ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); - provider_ = std::make_shared( - *api_, context_, - [this](Http::RequestMessage& message) -> absl::optional { - return this->fetch_metadata_.fetch(message); - }, - [this](Upstream::ClusterManager&, absl::string_view) { - metadata_fetcher_.reset(raw_metadata_fetcher_); - return std::move(metadata_fetcher_); - }, - "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); - } - - void setupProviderWithContext() { - EXPECT_CALL(context_.init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { - init_target_handle_ = target.createHandle("test"); - })); - setupProvider(); - expected_duration_ = provider_->getCacheDuration(); - init_target_handle_->initialize(init_watcher_); - } - - void expectDocument(const uint64_t status_code, const std::string&& document) { - Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, - {":authority", "169.254.170.2:80"}, - {":scheme", "http"}, - {":method", "GET"}, - {"authorization", "auth_token"}}; - EXPECT_CALL(*raw_metadata_fetcher_, fetch(messageMatches(headers), _, _)) - .WillRepeatedly(Invoke([this, status_code, document = std::move(document)]( - Http::RequestMessage&, Tracing::Span&, - MetadataFetcher::MetadataReceiver& receiver) { - if (status_code == enumToInt(Http::Code::OK)) { - if (!document.empty()) { - receiver.onMetadataSuccess(std::move(document)); - } else { - EXPECT_CALL( - *raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata))) - .WillRepeatedly(testing::Return("InvalidMetadata")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata); - } - } else { - EXPECT_CALL(*raw_metadata_fetcher_, - failureToString(Eq(MetadataFetcher::MetadataReceiver::Failure::Network))) - .WillRepeatedly(testing::Return("Network")); - receiver.onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network); - } - })); - } - - TestScopedRuntime scoped_runtime; - Event::SimulatedTimeSystem time_system_; - Api::ApiPtr api_; - NiceMock fetch_metadata_; - MockMetadataFetcher* raw_metadata_fetcher_; - MetadataFetcherPtr metadata_fetcher_; - NiceMock cluster_manager_; - NiceMock context_; - TaskRoleCredentialsProviderPtr provider_; - Init::TargetHandlePtr init_target_handle_; - NiceMock init_watcher_; - Event::MockTimer* timer_{}; - std::chrono::milliseconds expected_duration_; -}; - -TEST_F(TaskRoleCredentialsProviderTest, TestAddMissingCluster) { - // Setup without thread local cluster yet - envoy::config::cluster::v3::Cluster expected_cluster; - constexpr static const char* kStaticCluster = R"EOF( -name: credentials_provider_cluster -type: static -connectTimeout: 2s -lb_policy: ROUND_ROBIN -loadAssignment: - clusterName: credentials_provider_cluster - endpoints: - - lbEndpoints: - - endpoint: - address: - socketAddress: - address: "169.254.170.2" - portValue: 80 -typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions - explicit_http_config: - http_protocol_options: - accept_http_10: true - )EOF"; - MessageUtil::loadFromYaml(kStaticCluster, expected_cluster, - ProtobufMessage::getNullValidationVisitor()); - - EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); - EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithAttribute(expected_cluster), _)) - .WillOnce(Return(true)); - - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token", - "Expiration": "2018-01-02T03:05:00Z" -} -)EOF")); - - setupProviderWithContext(); -} - -TEST_F(TaskRoleCredentialsProviderTest, TestClusterMissing) { - // Setup without thread local cluster - Http::RequestMessageImpl message; - - EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); - EXPECT_CALL(cluster_manager_, addOrUpdateCluster(WithName("credentials_provider_cluster"), _)) - .WillOnce(Throw(EnvoyException("exeption message"))); - // init_watcher ready is not called. - init_watcher_.expectReady().Times(0); - setupProvider(); - // Below line is not testing anything, will just avoid asan failure with memory leak. - metadata_fetcher_.reset(raw_metadata_fetcher_); -} - -TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocument) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectDocument(403 /*Forbidden*/, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // Cancel is called for fetching once again as previous attempt wasn't a success. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(TaskRoleCredentialsProviderTest, EmptyDocument) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectDocument(200, std::move(std::string())); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // Cancel is called for fetching once again as previous attempt wasn't a success. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(TaskRoleCredentialsProviderTest, MalformedDocument) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - - expectDocument(200, std::move(R"EOF( -not json -)EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // Cancel is called for fetching once again as previous attempt wasn't a success. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "", - "SecretAccessKey": "", - "Token": "", - "Expiration": "" -} -)EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // Cancel is called for fetching once again as previous attempt wasn't a success with updating - // expiration time. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentials) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token", - "Expiration": "2018-01-02T03:05:00Z" -} -)EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // We don't expect any more call to cancel or fetch again. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // We don't expect any more call to cancel or fetch again. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto cached_credentials = provider_->getCredentials(); - EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); - EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); - EXPECT_EQ("token", cached_credentials.sessionToken().value()); -} - -TEST_F(TaskRoleCredentialsProviderTest, RefreshOnNormalCredentialExpiration) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token", - "Expiration": "2019-01-02T03:04:05Z" -} -)EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // No need to restart timer since credentials are fetched from cache. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // We don't expect any more call to cancel or fetch again. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "new_akid", - "SecretAccessKey": "new_secret", - "Token": "new_token", - "Expiration": "2019-01-02T03:04:05Z" -} -)EOF")); - // Expect timer to have expired but we would re-start the timer eventually after refresh. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - // Cancel will be called once more. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - time_system_.advanceTimeWait(std::chrono::minutes(61)); - timer_->invokeCallback(); - - // We don't expect timer to be reset again for new fetch. - EXPECT_CALL(*timer_, disableTimer()).Times(0); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)).Times(0); - // Similarly we won't call fetch or cancel on metadata fetcher. - EXPECT_CALL(*raw_metadata_fetcher_, fetch(_, _, _)).Times(0); - EXPECT_CALL(*raw_metadata_fetcher_, cancel()).Times(0); - - const auto cached_credentials = provider_->getCredentials(); - EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); - EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); - EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); -} - -TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { - // Setup timer. - timer_ = new NiceMock(&context_.dispatcher_); - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "akid", - "SecretAccessKey": "secret", - "Token": "token", - "Expiration": "2018-01-02T03:04:05Z" -} -)EOF")); - // init_watcher ready is called. - init_watcher_.expectReady(); - // Expect refresh timer to be started. - EXPECT_CALL(*timer_, enableTimer(_, nullptr)); - setupProviderWithContext(); - - // init_watcher ready is not called again. - init_watcher_.expectReady().Times(0); - // Need to disable and restart timer since credentials are expired and fetched again - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - // We call cancel once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - - const auto credentials = provider_->getCredentials(); - EXPECT_EQ("akid", credentials.accessKeyId().value()); - EXPECT_EQ("secret", credentials.secretAccessKey().value()); - EXPECT_EQ("token", credentials.sessionToken().value()); - - // Cancel is called once. - EXPECT_CALL(*raw_metadata_fetcher_, cancel()); - expectDocument(200, std::move(R"EOF( -{ - "AccessKeyId": "new_akid", - "SecretAccessKey": "new_secret", - "Token": "new_token", - "Expiration": "2019-01-02T03:04:05Z" -} -)EOF")); - // Expect refresh timer to be stopped and started. - EXPECT_CALL(*timer_, disableTimer()); - EXPECT_CALL(*timer_, enableTimer(expected_duration_, nullptr)); - const auto cached_credentials = provider_->getCredentials(); - EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); - EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); - EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); -} -// End unit test for new option via Http Async client. - -// Begin unit test for deprecated option using Libcurl client. -// TODO(suniltheta): Remove this test class once libcurl is removed from Envoy. -class TaskRoleCredentialsProviderUsingLibcurlTest : public testing::Test { -public: - TaskRoleCredentialsProviderUsingLibcurlTest() : api_(Api::createApiForTest(time_system_)) { - // Tue Jan 2 03:04:05 UTC 2018 - time_system_.setSystemTime(std::chrono::milliseconds(1514862245000)); - } - - void setupProvider() { - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.use_libcurl_to_fetch_aws_credentials", "true"}}); - provider_ = std::make_shared( - *api_, absl::nullopt, - [this](Http::RequestMessage& message) -> absl::optional { - return this->fetch_metadata_.fetch(message); - }, - nullptr, "169.254.170.2:80/path/to/doc", "auth_token", "credentials_provider_cluster"); - } - void expectDocument(const absl::optional& document) { Http::TestRequestHeaderMapImpl headers{{":path", "/path/to/doc"}, {":authority", "169.254.170.2:80"}, - {":scheme", "http"}, {":method", "GET"}, {"authorization", "auth_token"}}; EXPECT_CALL(fetch_metadata_, fetch(messageMatches(headers))).WillOnce(Return(document)); } - TestScopedRuntime scoped_runtime; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; NiceMock fetch_metadata_; - TaskRoleCredentialsProviderPtr provider_; + TaskRoleCredentialsProvider provider_; }; -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, FailedFetchingDocumentCurl) { - setupProvider(); +TEST_F(TaskRoleCredentialsProviderTest, FailedFetchingDocument) { expectDocument(absl::optional()); - const auto credentials = provider_->getCredentials(); - EXPECT_FALSE(credentials.accessKeyId().has_value()); - EXPECT_FALSE(credentials.secretAccessKey().has_value()); - EXPECT_FALSE(credentials.sessionToken().has_value()); -} - -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, EmptyDocumentCurl) { - setupProvider(); - expectDocument(""); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, MalformedDocumentCurl) { - setupProvider(); +TEST_F(TaskRoleCredentialsProviderTest, MalformedDocumenet) { expectDocument(R"EOF( not json )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, EmptyValuesCurl) { - setupProvider(); +TEST_F(TaskRoleCredentialsProviderTest, EmptyValues) { expectDocument(R"EOF( { "AccessKeyId": "", @@ -1888,14 +573,13 @@ TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, EmptyValuesCurl) { "Expiration": "" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_FALSE(credentials.accessKeyId().has_value()); EXPECT_FALSE(credentials.secretAccessKey().has_value()); EXPECT_FALSE(credentials.sessionToken().has_value()); } -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurl) { - setupProvider(); +TEST_F(TaskRoleCredentialsProviderTest, FullCachedCredentials) { expectDocument(R"EOF( { "AccessKeyId": "akid", @@ -1904,18 +588,17 @@ TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, FullCachedCredentialsCurl) { "Expiration": "2018-01-02T03:05:00Z" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); - const auto cached_credentials = provider_->getCredentials(); + const auto cached_credentials = provider_.getCredentials(); EXPECT_EQ("akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("token", cached_credentials.sessionToken().value()); } -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, NormalCredentialExpirationCurl) { - setupProvider(); +TEST_F(TaskRoleCredentialsProviderTest, NormalCredentialExpiration) { InSequence sequence; expectDocument(R"EOF( { @@ -1925,7 +608,7 @@ TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, NormalCredentialExpirationCu "Expiration": "2019-01-02T03:04:05Z" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); @@ -1938,14 +621,13 @@ TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, NormalCredentialExpirationCu "Expiration": "2019-01-02T03:04:05Z" } )EOF"); - const auto cached_credentials = provider_->getCredentials(); + const auto cached_credentials = provider_.getCredentials(); EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } -TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, TimestampCredentialExpirationCurl) { - setupProvider(); +TEST_F(TaskRoleCredentialsProviderTest, TimestampCredentialExpiration) { InSequence sequence; expectDocument(R"EOF( { @@ -1955,7 +637,7 @@ TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, TimestampCredentialExpiratio "Expiration": "2018-01-02T03:04:05Z" } )EOF"); - const auto credentials = provider_->getCredentials(); + const auto credentials = provider_.getCredentials(); EXPECT_EQ("akid", credentials.accessKeyId().value()); EXPECT_EQ("secret", credentials.secretAccessKey().value()); EXPECT_EQ("token", credentials.sessionToken().value()); @@ -1967,18 +649,15 @@ TEST_F(TaskRoleCredentialsProviderUsingLibcurlTest, TimestampCredentialExpiratio "Expiration": "2019-01-02T03:04:05Z" } )EOF"); - const auto cached_credentials = provider_->getCredentials(); + const auto cached_credentials = provider_.getCredentials(); EXPECT_EQ("new_akid", cached_credentials.accessKeyId().value()); EXPECT_EQ("new_secret", cached_credentials.secretAccessKey().value()); EXPECT_EQ("new_token", cached_credentials.sessionToken().value()); } -// End unit test for deprecated option using Libcurl client. class DefaultCredentialsProviderChainTest : public testing::Test { public: DefaultCredentialsProviderChainTest() : api_(Api::createApiForTest(time_system_)) { - ON_CALL(context_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); - cluster_manager_.initializeThreadLocalClusters({"credentials_provider_cluster"}); EXPECT_CALL(factories_, createEnvironmentCredentialsProvider()); } @@ -1995,67 +674,62 @@ class DefaultCredentialsProviderChainTest : public testing::Test { MOCK_METHOD(CredentialsProviderSharedPtr, createCredentialsFileCredentialsProvider, (Api::Api&), (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createTaskRoleCredentialsProvider, - (Api::Api&, ServerFactoryContextOptRef, - const MetadataCredentialsProviderBase::CurlMetadataFetcher&, - CreateMetadataFetcherCb, absl::string_view, absl::string_view, absl::string_view), + (Api::Api&, const MetadataCredentialsProviderBase::MetadataFetcher&, + absl::string_view, absl::string_view), (const)); MOCK_METHOD(CredentialsProviderSharedPtr, createInstanceProfileCredentialsProvider, - (Api::Api&, ServerFactoryContextOptRef, - const MetadataCredentialsProviderBase::CurlMetadataFetcher&, - CreateMetadataFetcherCb, absl::string_view), + (Api::Api&, const MetadataCredentialsProviderBase::MetadataFetcher& fetcher), (const)); }; Event::SimulatedTimeSystem time_system_; Api::ApiPtr api_; - NiceMock cluster_manager_; - NiceMock context_; NiceMock factories_; }; TEST_F(DefaultCredentialsProviderChainTest, NoEnvironmentVars) { EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, CredentialsFileDisabled) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues({{"envoy.reloadable_features.enable_aws_credentials_file", "false"}}); + EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))).Times(0); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, MetadataDisabled) { TestEnvironment::setEnvVar("AWS_EC2_METADATA_DISABLED", "true", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)) - .Times(0); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)).Times(0); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, MetadataNotDisabled) { TestEnvironment::setEnvVar("AWS_EC2_METADATA_DISABLED", "false", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _, _, _, _)); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + EXPECT_CALL(factories_, createInstanceProfileCredentialsProvider(Ref(*api_), _)); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, RelativeUri) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/path/to/creds", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createTaskRoleCredentialsProvider(Ref(*api_), _, _, _, _, + EXPECT_CALL(factories_, createTaskRoleCredentialsProvider(Ref(*api_), _, "169.254.170.2:80/path/to/creds", "")); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, FullUriNoAuthorizationToken) { TestEnvironment::setEnvVar("AWS_CONTAINER_CREDENTIALS_FULL_URI", "http://host/path/to/creds", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); - EXPECT_CALL(factories_, createTaskRoleCredentialsProvider(Ref(*api_), _, _, _, _, - "http://host/path/to/creds", "")); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + EXPECT_CALL(factories_, + createTaskRoleCredentialsProvider(Ref(*api_), _, "http://host/path/to/creds", "")); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST_F(DefaultCredentialsProviderChainTest, FullUriWithAuthorizationToken) { @@ -2063,8 +737,8 @@ TEST_F(DefaultCredentialsProviderChainTest, FullUriWithAuthorizationToken) { TestEnvironment::setEnvVar("AWS_CONTAINER_AUTHORIZATION_TOKEN", "auth_token", 1); EXPECT_CALL(factories_, createCredentialsFileCredentialsProvider(Ref(*api_))); EXPECT_CALL(factories_, createTaskRoleCredentialsProvider( - Ref(*api_), _, _, _, _, "http://host/path/to/creds", "auth_token")); - DefaultCredentialsProviderChain chain(*api_, context_, DummyFetchMetadata(), factories_); + Ref(*api_), _, "http://host/path/to/creds", "auth_token")); + DefaultCredentialsProviderChain chain(*api_, DummyMetadataFetcher(), factories_); } TEST(CredentialsProviderChainTest, getCredentials_noCredentials) { diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index ebfc04bd31f64..6db726a8f9362 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -57,7 +57,7 @@ class MockFetchMetadata { MOCK_METHOD(absl::optional, fetch, (Http::RequestMessage&), (const)); }; -class DummyFetchMetadata { +class DummyMetadataFetcher { public: absl::optional operator()(Http::RequestMessage&) { return absl::nullopt; } }; diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc index bee8f3ad2a2ee..b42b5977b8397 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_integration_test.cc @@ -23,7 +23,6 @@ class AwsLambdaFilterIntegrationTest : public testing::TestWithParam Date: Fri, 13 Oct 2023 14:36:55 +0000 Subject: [PATCH 12/15] Add Metadatafetcher failureToString conversion coverage Signed-off-by: Sunil Narasimhamurthy --- source/extensions/common/aws/metadata_fetcher.cc | 3 ++- test/extensions/common/aws/metadata_fetcher_test.cc | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/source/extensions/common/aws/metadata_fetcher.cc b/source/extensions/common/aws/metadata_fetcher.cc index dd5ed27a9c2ec..fc9ca1aded338 100644 --- a/source/extensions/common/aws/metadata_fetcher.cc +++ b/source/extensions/common/aws/metadata_fetcher.cc @@ -41,8 +41,9 @@ class MetadataFetcherImpl : public MetadataFetcher, return "InvalidMetadata"; case MetadataFetcher::MetadataReceiver::Failure::MissingConfig: return "MissingConfig"; + default: + return ""; } - return ""; } void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc index c3f0bdbb161a5..807592e18df04 100644 --- a/test/extensions/common/aws/metadata_fetcher_test.cc +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -284,6 +284,17 @@ TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { fetcher_->fetch(message, parent_span_, receiver); } +TEST_F(MetadataFetcherTest, TestFailureToStringConversion) { + // Setup + setupFetcher(); + EXPECT_EQ(fetcher_->failureToString(MetadataFetcher::MetadataReceiver::Failure::Network), + "Network"); + EXPECT_EQ(fetcher_->failureToString(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata), + "InvalidMetadata"); + EXPECT_EQ(fetcher_->failureToString(MetadataFetcher::MetadataReceiver::Failure::MissingConfig), + "MissingConfig"); +} + } // namespace Aws } // namespace Common } // namespace Extensions From da3a2bbc4fb900546348bc4dc81470163b7cdf8b Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Thu, 19 Oct 2023 18:08:22 +0000 Subject: [PATCH 13/15] code review f/b don't use loadFromYaml Signed-off-by: Sunil Narasimhamurthy --- source/extensions/common/aws/BUILD | 2 + .../extensions/common/aws/metadata_fetcher.cc | 22 +-- source/extensions/common/aws/utility.cc | 50 +++--- source/extensions/common/aws/utility.h | 15 +- .../common/aws/metadata_fetcher_test.cc | 145 ++++++------------ test/extensions/common/aws/utility_test.cc | 22 ++- tools/spelling/spelling_dictionary.txt | 1 - 7 files changed, 113 insertions(+), 144 deletions(-) diff --git a/source/extensions/common/aws/BUILD b/source/extensions/common/aws/BUILD index b35e9d31bdac0..b5d8840695006 100644 --- a/source/extensions/common/aws/BUILD +++ b/source/extensions/common/aws/BUILD @@ -81,6 +81,8 @@ envoy_cc_library( "//source/common/common:utility_lib", "//source/common/http:headers_lib", "//source/common/http:utility_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/http/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/common/aws/metadata_fetcher.cc b/source/extensions/common/aws/metadata_fetcher.cc index fc9ca1aded338..339f75be7c2c4 100644 --- a/source/extensions/common/aws/metadata_fetcher.cc +++ b/source/extensions/common/aws/metadata_fetcher.cc @@ -48,10 +48,10 @@ class MetadataFetcherImpl : public MetadataFetcher, void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, MetadataFetcher::MetadataReceiver& receiver) override { + ASSERT(!request_); + ASSERT(!receiver_); complete_ = false; - if (!receiver_) { - receiver_ = &receiver; - } + receiver_ = makeOptRef(receiver); const auto thread_local_cluster = cm_.getThreadLocalCluster(cluster_name_); if (thread_local_cluster == nullptr) { ENVOY_LOG(error, "{} AWS Metadata failed: [cluster = {}] not found", __func__, cluster_name_); @@ -110,7 +110,8 @@ class MetadataFetcherImpl : public MetadataFetcher, options.setRetryPolicy(route_retry_policy); options.setBufferBodyForRetry(true); - request_ = thread_local_cluster->httpAsyncClient().send(std::move(messagePtr), *this, options); + request_ = makeOptRefFromPtr( + thread_local_cluster->httpAsyncClient().send(std::move(messagePtr), *this, options)); } // HTTP async receive method on success. @@ -155,13 +156,16 @@ class MetadataFetcherImpl : public MetadataFetcher, void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} private: - Upstream::ClusterManager& cm_; bool complete_{}; - MetadataFetcher::MetadataReceiver* receiver_{}; - std::string cluster_name_; - Http::AsyncClient::Request* request_{}; + Upstream::ClusterManager& cm_; + const std::string cluster_name_; + OptRef receiver_; + OptRef request_; - void reset() { request_ = nullptr; } + void reset() { + request_.reset(); + receiver_.reset(); + } }; } // namespace diff --git a/source/extensions/common/aws/utility.cc b/source/extensions/common/aws/utility.cc index cd06af9362093..1643e4068ba7d 100644 --- a/source/extensions/common/aws/utility.cc +++ b/source/extensions/common/aws/utility.cc @@ -299,8 +299,9 @@ absl::optional Utility::fetchMetadata(Http::RequestMessage& message return buffer.empty() ? absl::nullopt : absl::optional(buffer); } -bool Utility::addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, - absl::string_view cluster_type, absl::string_view uri) { +bool Utility::addInternalClusterStatic( + Upstream::ClusterManager& cm, absl::string_view cluster_name, + const envoy::config::cluster::v3::Cluster::DiscoveryType cluster_type, absl::string_view uri) { // Check if local cluster exists with that name. if (cm.getThreadLocalCluster(cluster_name) == nullptr) { // Make sure we run this on main thread. @@ -312,31 +313,26 @@ bool Utility::addInternalClusterStatic(Upstream::ClusterManager& cm, absl::strin const auto host_attributes = Http::Utility::parseAuthority(host_port); const auto host = host_attributes.host_; const auto port = host_attributes.port_ ? host_attributes.port_.value() : 80; - MessageUtil::loadFromYaml(fmt::format(R"EOF( -name: {cluster_name} -type: {cluster_type} -connectTimeout: 5s -lb_policy: ROUND_ROBIN -loadAssignment: - clusterName: {cluster_name} - endpoints: - - lbEndpoints: - - endpoint: - address: - socketAddress: - address: {host} - portValue: {port} -typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions - explicit_http_config: - http_protocol_options: - accept_http_10: true - )EOF", - fmt::arg("cluster_name", cluster_name), - fmt::arg("cluster_type", cluster_type), - fmt::arg("host", host), fmt::arg("port", port)), - cluster, ProtobufMessage::getNullValidationVisitor()); + + cluster.set_name(cluster_name); + cluster.set_type(cluster_type); + cluster.mutable_connect_timeout()->set_seconds(5); + cluster.mutable_load_assignment()->set_cluster_name(cluster_name); + auto* endpoint = cluster.mutable_load_assignment() + ->add_endpoints() + ->add_lb_endpoints() + ->mutable_endpoint(); + auto* addr = endpoint->mutable_address(); + addr->mutable_socket_address()->set_address(host); + addr->mutable_socket_address()->set_port_value(port); + cluster.set_lb_policy(envoy::config::cluster::v3::Cluster::ROUND_ROBIN); + envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options; + auto* http_protocol_options = + protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options(); + http_protocol_options->set_accept_http_10(true); + (*cluster.mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(protocol_options); // TODO(suniltheta): use random number generator here for cluster version. cm.addOrUpdateCluster(cluster, "12345"); diff --git a/source/extensions/common/aws/utility.h b/source/extensions/common/aws/utility.h index d0a38a9ecc12c..985ab0de6d9f9 100644 --- a/source/extensions/common/aws/utility.h +++ b/source/extensions/common/aws/utility.h @@ -1,5 +1,8 @@ #pragma once +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.h" +#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.validate.h" #include "envoy/http/message.h" #include "source/common/common/matchers.h" @@ -101,12 +104,16 @@ class Utility { * @param cm cluster manager * @param cluster_name a name for credentials provider cluster * @param cluster_type STATIC or STRICT_DNS or LOGICAL_DNS etc - * @param host provider's IP (STATIC cluster) or URL (STRICT_DNS) - * @return true if successfully added the cluster + * @param uri provider's IP (STATIC cluster) or URL (STRICT_DNS). Will use port 80 if the port is + * not specified in the uri or no matching cluster is found. + * @return true if successfully added the cluster or if a cluster with the cluster_name already + * exists. * @return false if failed to add the cluster */ - static bool addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, - absl::string_view cluster_type, absl::string_view host); + static bool + addInternalClusterStatic(Upstream::ClusterManager& cm, absl::string_view cluster_name, + const envoy::config::cluster::v3::Cluster::DiscoveryType cluster_type, + absl::string_view uri); }; } // namespace Aws diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc index 807592e18df04..ee9d413c8a3d5 100644 --- a/test/extensions/common/aws/metadata_fetcher_test.cc +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -20,32 +20,18 @@ using Envoy::Extensions::HttpFilters::Common::MockUpstream; using testing::_; using testing::InSequence; +using testing::Mock; using testing::NiceMock; using testing::Ref; using testing::Return; using testing::Throw; +using testing::UnorderedElementsAre; namespace Envoy { namespace Extensions { namespace Common { namespace Aws { -MATCHER_P(WithName, expectedName, "") { return arg.name() == expectedName; } - -MATCHER_P(WithAttribute, expectedCluster, "") { - const auto argSocketAddress = - arg.load_assignment().endpoints()[0].lb_endpoints()[0].endpoint().address().socket_address(); - const auto expectedSocketAddress = expectedCluster.load_assignment() - .endpoints()[0] - .lb_endpoints()[0] - .endpoint() - .address() - .socket_address(); - return arg.name() == expectedCluster.name() && - argSocketAddress.address() == expectedSocketAddress.address() && - argSocketAddress.port_value() == expectedSocketAddress.port_value(); -} - class MetadataFetcherTest : public testing::Test { public: void setupFetcher() { @@ -63,20 +49,21 @@ TEST_F(MetadataFetcherTest, TestGetSuccess) { // Setup setupFetcher(); Http::RequestMessageImpl message; - - MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); + std::string body = "not_empty"; + MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", body); MockMetadataReceiver receiver; - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)); - EXPECT_CALL(receiver, onMetadataError(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(std::move(body))); + EXPECT_CALL(receiver, onMetadataError(_)).Times(0); // Act fetcher_->fetch(message, parent_span_, receiver); } -TEST_F(MetadataFetcherTest, TestRequestMatch) { +TEST_F(MetadataFetcherTest, TestRequestMatchAndSpanPassedDown) { // Setup setupFetcher(); Http::RequestMessageImpl message; + message.headers().setScheme(Http::Headers::get().SchemeValues.Http); message.headers().setMethod(Http::Headers::get().MethodValues.Get); message.headers().setHost("169.254.170.2:80"); @@ -86,24 +73,34 @@ TEST_F(MetadataFetcherTest, TestRequestMatch) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); MockMetadataReceiver receiver; + Http::MockAsyncClientRequest httpClientRequest( + &mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_); EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce(Invoke([](Http::RequestMessagePtr& request, Http::AsyncClient::Callbacks&, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - EXPECT_EQ("169.254.170.2", request->headers().getHostValue()); - EXPECT_EQ("/v2/credentials/c68caeb5-ef71-4914-8170-111111111111", - request->headers().getPathValue()); - EXPECT_EQ(Http::Headers::get().MethodValues.Get, request->headers().getMethodValue()); - EXPECT_EQ(Http::Headers::get().SchemeValues.Http, request->headers().getSchemeValue()); - EXPECT_EQ("Token", request->headers() - .get(Http::LowerCaseString("X-aws-ec2-metadata-token"))[0] - ->value() - .getStringView()); - EXPECT_TRUE(request->headers().get(Http::LowerCaseString(":pseudo-header")).empty()); - return nullptr; - })); + .WillOnce(Invoke( + [this, &httpClientRequest]( + Http::RequestMessagePtr& request, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { + Http::TestRequestHeaderMapImpl injected_headers = { + {":method", "GET"}, + {":scheme", "http"}, + {":authority", "169.254.170.2"}, + {":path", "/v2/credentials/c68caeb5-ef71-4914-8170-111111111111"}, + {"X-aws-ec2-metadata-token", "Token"}}; + EXPECT_THAT(request->headers(), IsSupersetOfHeaders(injected_headers)); + EXPECT_TRUE(request->headers().get(Http::LowerCaseString(":pseudo-header")).empty()); + + // Verify expectations for span + EXPECT_TRUE(options.parent_span_ == &this->parent_span_); + EXPECT_TRUE(options.child_span_name_ == "AWS Metadata Fetch"); + // Let's say this ends up with a failure then verify it is handled properly by calling + // onMetadataError. + cb.onFailure(httpClientRequest, Http::AsyncClient::FailureReason::Reset); + return &httpClientRequest; + })); + EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); // Act fetcher_->fetch(message, parent_span_, receiver); } @@ -115,7 +112,7 @@ TEST_F(MetadataFetcherTest, TestGet400) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "400", "not_empty"); MockMetadataReceiver receiver; - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); // Act @@ -129,7 +126,7 @@ TEST_F(MetadataFetcherTest, TestGet400NoBody) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "400", ""); MockMetadataReceiver receiver; - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); // Act @@ -143,7 +140,7 @@ TEST_F(MetadataFetcherTest, TestGetNoBody) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", ""); MockMetadataReceiver receiver; - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata)); @@ -159,34 +156,13 @@ TEST_F(MetadataFetcherTest, TestHttpFailure) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, Http::AsyncClient::FailureReason::Reset); MockMetadataReceiver receiver; - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network)); // Act fetcher_->fetch(message, parent_span_, receiver); } -TEST_F(MetadataFetcherTest, TestWithValidCluster) { - // Setup with thread local cluster - Http::RequestMessageImpl message; - message.headers().setScheme(Http::Headers::get().SchemeValues.Http); - message.headers().setMethod(Http::Headers::get().MethodValues.Get); - message.headers().setHost("169.254.170.2:80"); - message.headers().setPath("/v2/credentials/c68caeb5-ef71-4914-8170-111111111111"); - - NiceMock cluster_; - fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); - EXPECT_CALL(mock_factory_ctx_.cluster_manager_, getThreadLocalCluster(_)) - .WillOnce(Return(&cluster_)); - - MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); - MockMetadataReceiver receiver; - EXPECT_CALL(receiver, onMetadataError(testing::_)).Times(0); - - // Act - fetcher_->fetch(message, parent_span_, receiver); -} - TEST_F(MetadataFetcherTest, TestClusterNotFound) { // Setup without thread local cluster fetcher_ = MetadataFetcher::create(mock_factory_ctx_.cluster_manager_, "cluster_name"); @@ -195,7 +171,7 @@ TEST_F(MetadataFetcherTest, TestClusterNotFound) { EXPECT_CALL(mock_factory_ctx_.cluster_manager_, getThreadLocalCluster(_)) .WillOnce(Return(nullptr)); - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); EXPECT_CALL(receiver, onMetadataError(MetadataFetcher::MetadataReceiver::Failure::MissingConfig)); // Act @@ -211,39 +187,20 @@ TEST_F(MetadataFetcherTest, TestCancel) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, &request); MockMetadataReceiver receiver; EXPECT_CALL(request, cancel()); - EXPECT_CALL(receiver, onMetadataSuccess(testing::_)).Times(0); - EXPECT_CALL(receiver, onMetadataError(testing::_)).Times(0); + EXPECT_CALL(receiver, onMetadataSuccess(_)).Times(0); + EXPECT_CALL(receiver, onMetadataError(_)).Times(0); // Act fetcher_->fetch(message, parent_span_, receiver); // Proper cancel fetcher_->cancel(); - // Re-entrant cancel + Mock::VerifyAndClearExpectations(&request); + Mock::VerifyAndClearExpectations(&receiver); + // Re-entrant cancel should do nothing. + EXPECT_CALL(request, cancel()).Times(0); fetcher_->cancel(); } -TEST_F(MetadataFetcherTest, TestSpanPassedDown) { - // Setup - setupFetcher(); - Http::RequestMessageImpl message; - MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); - MockMetadataReceiver receiver; - - // Expectations for span - EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, - send_(_, _, _)) - .WillOnce(Invoke( - [this](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, - const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { - EXPECT_TRUE(options.parent_span_ == &this->parent_span_); - EXPECT_TRUE(options.child_span_name_ == "AWS Metadata Fetch"); - return nullptr; - })); - - // Act - fetcher_->fetch(message, parent_span_, receiver); -} - TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { // Setup setupFetcher(); @@ -259,24 +216,20 @@ TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { EXPECT_TRUE(options.retry_policy.has_value()); EXPECT_TRUE(options.buffer_body_for_retry); EXPECT_TRUE(options.retry_policy.value().has_num_retries()); - EXPECT_EQ(PROTOBUF_GET_WRAPPED_REQUIRED(options.retry_policy.value(), num_retries), 3); + EXPECT_EQ(options.retry_policy.value().num_retries().value(), 3); EXPECT_TRUE(options.retry_policy.value().has_per_try_timeout()); - EXPECT_EQ(PROTOBUF_GET_MS_REQUIRED(options.retry_policy.value(), per_try_timeout), - 5000); + EXPECT_EQ(options.retry_policy.value().per_try_timeout().seconds(), 5); EXPECT_TRUE(options.retry_policy.value().has_per_try_idle_timeout()); - EXPECT_EQ(PROTOBUF_GET_MS_REQUIRED(options.retry_policy.value(), per_try_idle_timeout), - 1000); + EXPECT_EQ(options.retry_policy.value().per_try_idle_timeout().seconds(), 1); const std::string& retry_on = options.retry_policy.value().retry_on(); std::set retry_on_modes = absl::StrSplit(retry_on, ','); - EXPECT_EQ(retry_on_modes.count("5xx"), 1); - EXPECT_EQ(retry_on_modes.count("gateway-error"), 1); - EXPECT_EQ(retry_on_modes.count("connect-failure"), 1); - EXPECT_EQ(retry_on_modes.count("reset"), 1); - + EXPECT_THAT(retry_on_modes, + UnorderedElementsAre("5xx", "gateway-error", "connect-failure", + "refused-stream", "reset")); return nullptr; })); diff --git a/test/extensions/common/aws/utility_test.cc b/test/extensions/common/aws/utility_test.cc index ff7670ddd024e..221a94454d130 100644 --- a/test/extensions/common/aws/utility_test.cc +++ b/test/extensions/common/aws/utility_test.cc @@ -20,7 +20,11 @@ namespace Common { namespace Aws { namespace { -MATCHER_P(WithName, expectedName, "") { return arg.name() == expectedName; } +MATCHER_P(WithName, expectedName, "") { + *result_listener << "\nexpected { name: \"" << expectedName << "\"} but got {name: \"" + << arg.name() << "\"}\n"; + return ExplainMatchResult(expectedName, arg.name(), result_listener); +} // Headers must be in alphabetical order by virtue of std::map TEST(UtilityTest, CanonicalizeHeadersInAlphabeticalOrder) { @@ -361,7 +365,8 @@ TEST(UtilityTest, ThreadLocalClusterExistsAlready) { NiceMock cm_; EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(&cluster_)); EXPECT_CALL(cm_, addOrUpdateCluster(_, _)).Times(0); - EXPECT_TRUE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "")); + EXPECT_TRUE(Utility::addInternalClusterStatic(cm_, "cluster_name", + envoy::config::cluster::v3::Cluster::STATIC, "")); } // Verify that if thread local cluster doesn't exist we can create a new one. @@ -369,7 +374,8 @@ TEST(UtilityTest, AddStaticClusterSuccess) { NiceMock cm_; EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)).WillOnce(Return(true)); - EXPECT_TRUE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1:80")); + EXPECT_TRUE(Utility::addInternalClusterStatic( + cm_, "cluster_name", envoy::config::cluster::v3::Cluster::STATIC, "127.0.0.1:80")); } // Handle exception when adding thread local cluster fails. @@ -378,16 +384,18 @@ TEST(UtilityTest, AddStaticClusterFailure) { EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)) .WillOnce(Throw(EnvoyException("exeption message"))); - EXPECT_FALSE(Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1:80")); + EXPECT_FALSE(Utility::addInternalClusterStatic( + cm_, "cluster_name", envoy::config::cluster::v3::Cluster::STATIC, "127.0.0.1:80")); } -// Verify that even with missing port value and any path set in host will still succeed. +// Verify that for uri argument in addInternalClusterStatic port value is optional +// and can contain request path which will be ignored. TEST(UtilityTest, AddStaticClusterSuccessEvenWithMissingPort) { NiceMock cm_; EXPECT_CALL(cm_, getThreadLocalCluster(_)).WillOnce(Return(nullptr)); EXPECT_CALL(cm_, addOrUpdateCluster(WithName("cluster_name"), _)).WillOnce(Return(true)); - EXPECT_TRUE( - Utility::addInternalClusterStatic(cm_, "cluster_name", "STATIC", "127.0.0.1/something")); + EXPECT_TRUE(Utility::addInternalClusterStatic( + cm_, "cluster_name", envoy::config::cluster::v3::Cluster::STATIC, "127.0.0.1/something")); } } // namespace diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 3dbfd0194a667..ea7b558f6f5e4 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -252,7 +252,6 @@ integrations iouring jkl lang -libcurl libsxg LLVM LPT From 56ec67381885543823cb8d0482706f35a71f2713 Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Sat, 21 Oct 2023 06:22:12 +0000 Subject: [PATCH 14/15] Use MATCHER_P helpers for checking options Signed-off-by: Sunil Narasimhamurthy --- .../common/aws/metadata_fetcher_test.cc | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc index ee9d413c8a3d5..04b5f30c8ed2a 100644 --- a/test/extensions/common/aws/metadata_fetcher_test.cc +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -19,6 +19,7 @@ using Envoy::Extensions::HttpFilters::Common::MockUpstream; using testing::_; +using testing::AllOf; using testing::InSequence; using testing::Mock; using testing::NiceMock; @@ -32,6 +33,45 @@ namespace Extensions { namespace Common { namespace Aws { +MATCHER_P(OptionsHasRetryPolicy, expectedValue, "") { + *result_listener << "\nexpected is retry_policy present: \"" << expectedValue + << "\" but got is retry_policy present: \"" << arg.retry_policy.has_value() + << "\"\n"; + return ExplainMatchResult(expectedValue, arg.retry_policy.has_value(), result_listener); +} + +MATCHER_P(OptionsHasBufferBodyForRetry, expectedValue, "") { + *result_listener << "\nexpected { buffer_body_for_retry: \"" << expectedValue + << "\"} but got {buffer_body_for_retry: \"" << arg.buffer_body_for_retry + << "\"}\n"; + return ExplainMatchResult(expectedValue, arg.buffer_body_for_retry, result_listener); +} + +MATCHER_P(OptionsRetryPolicyHasNumRetries, expectedRetries, "") { + *result_listener << "\nexpected { num_retries: \"" << expectedRetries + << "\"} but got {num_retries: \"" + << arg.retry_policy.value().num_retries().value() << "\"}\n"; + return ExplainMatchResult(expectedRetries, arg.retry_policy.value().num_retries().value(), + result_listener); +} + +MATCHER_P(OptionsRetryPolicyHasPerTryTimeout, expectedTimeout, "") { + *result_listener << "\nexpected { per_try_timeout: \"" << expectedTimeout + << "\"} but got { per_try_timeout: \"" + << arg.retry_policy.value().per_try_timeout().seconds() << "\"}\n"; + return ExplainMatchResult(expectedTimeout, arg.retry_policy.value().per_try_timeout().seconds(), + result_listener); +} + +MATCHER_P(OptionsRetryPolicyHasPerTryIdleTimeout, expectedIdleTimeout, "") { + *result_listener << "\nexpected { per_try_idle_timeout: \"" << expectedIdleTimeout + << "\"} but got { per_try_idle_timeout: \"" + << arg.retry_policy.value().per_try_idle_timeout().seconds() << "\"}\n"; + return ExplainMatchResult(expectedIdleTimeout, + arg.retry_policy.value().per_try_idle_timeout().seconds(), + result_listener); +} + class MetadataFetcherTest : public testing::Test { public: void setupFetcher() { @@ -209,21 +249,13 @@ TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { MockMetadataReceiver receiver; EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, - send_(_, _, _)) + send_(_, _, + AllOf(OptionsHasRetryPolicy(true), OptionsHasBufferBodyForRetry(true), + OptionsRetryPolicyHasNumRetries(3), OptionsRetryPolicyHasPerTryTimeout(5), + OptionsRetryPolicyHasPerTryIdleTimeout(1)))) .WillOnce(Invoke( [](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { - EXPECT_TRUE(options.retry_policy.has_value()); - EXPECT_TRUE(options.buffer_body_for_retry); - EXPECT_TRUE(options.retry_policy.value().has_num_retries()); - EXPECT_EQ(options.retry_policy.value().num_retries().value(), 3); - - EXPECT_TRUE(options.retry_policy.value().has_per_try_timeout()); - EXPECT_EQ(options.retry_policy.value().per_try_timeout().seconds(), 5); - - EXPECT_TRUE(options.retry_policy.value().has_per_try_idle_timeout()); - EXPECT_EQ(options.retry_policy.value().per_try_idle_timeout().seconds(), 1); - const std::string& retry_on = options.retry_policy.value().retry_on(); std::set retry_on_modes = absl::StrSplit(retry_on, ','); From 5433e871202010b7e6e19b1038e71f9848fa0aad Mon Sep 17 00:00:00 2001 From: Sunil Narasimhamurthy Date: Tue, 24 Oct 2023 16:43:58 +0000 Subject: [PATCH 15/15] Avoid Invoke by using Matcher Signed-off-by: Sunil Narasimhamurthy --- .../common/aws/metadata_fetcher_test.cc | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/test/extensions/common/aws/metadata_fetcher_test.cc b/test/extensions/common/aws/metadata_fetcher_test.cc index 04b5f30c8ed2a..d009625e952a1 100644 --- a/test/extensions/common/aws/metadata_fetcher_test.cc +++ b/test/extensions/common/aws/metadata_fetcher_test.cc @@ -33,13 +33,6 @@ namespace Extensions { namespace Common { namespace Aws { -MATCHER_P(OptionsHasRetryPolicy, expectedValue, "") { - *result_listener << "\nexpected is retry_policy present: \"" << expectedValue - << "\" but got is retry_policy present: \"" << arg.retry_policy.has_value() - << "\"\n"; - return ExplainMatchResult(expectedValue, arg.retry_policy.has_value(), result_listener); -} - MATCHER_P(OptionsHasBufferBodyForRetry, expectedValue, "") { *result_listener << "\nexpected { buffer_body_for_retry: \"" << expectedValue << "\"} but got {buffer_body_for_retry: \"" << arg.buffer_body_for_retry @@ -47,31 +40,43 @@ MATCHER_P(OptionsHasBufferBodyForRetry, expectedValue, "") { return ExplainMatchResult(expectedValue, arg.buffer_body_for_retry, result_listener); } -MATCHER_P(OptionsRetryPolicyHasNumRetries, expectedRetries, "") { +MATCHER_P(NumRetries, expectedRetries, "") { *result_listener << "\nexpected { num_retries: \"" << expectedRetries - << "\"} but got {num_retries: \"" - << arg.retry_policy.value().num_retries().value() << "\"}\n"; - return ExplainMatchResult(expectedRetries, arg.retry_policy.value().num_retries().value(), - result_listener); + << "\"} but got {num_retries: \"" << arg.num_retries().value() << "\"}\n"; + return ExplainMatchResult(expectedRetries, arg.num_retries().value(), result_listener); } -MATCHER_P(OptionsRetryPolicyHasPerTryTimeout, expectedTimeout, "") { +MATCHER_P(PerTryTimeout, expectedTimeout, "") { *result_listener << "\nexpected { per_try_timeout: \"" << expectedTimeout - << "\"} but got { per_try_timeout: \"" - << arg.retry_policy.value().per_try_timeout().seconds() << "\"}\n"; - return ExplainMatchResult(expectedTimeout, arg.retry_policy.value().per_try_timeout().seconds(), - result_listener); + << "\"} but got { per_try_timeout: \"" << arg.per_try_timeout().seconds() + << "\"}\n"; + return ExplainMatchResult(expectedTimeout, arg.per_try_timeout().seconds(), result_listener); } -MATCHER_P(OptionsRetryPolicyHasPerTryIdleTimeout, expectedIdleTimeout, "") { +MATCHER_P(PerTryIdleTimeout, expectedIdleTimeout, "") { *result_listener << "\nexpected { per_try_idle_timeout: \"" << expectedIdleTimeout << "\"} but got { per_try_idle_timeout: \"" - << arg.retry_policy.value().per_try_idle_timeout().seconds() << "\"}\n"; - return ExplainMatchResult(expectedIdleTimeout, - arg.retry_policy.value().per_try_idle_timeout().seconds(), + << arg.per_try_idle_timeout().seconds() << "\"}\n"; + return ExplainMatchResult(expectedIdleTimeout, arg.per_try_idle_timeout().seconds(), result_listener); } +MATCHER_P(RetryOnModes, expectedModes, "") { + const std::string& retry_on = arg.retry_on(); + std::set retry_on_modes = absl::StrSplit(retry_on, ','); + *result_listener << "\nexpected retry_on modes doesn't match " + << "received { retry_on modes: \"" << retry_on << "\"}\n"; + return ExplainMatchResult(expectedModes, retry_on_modes, result_listener); +} + +MATCHER_P(OptionsHasRetryPolicy, policyMatcher, "") { + if (!arg.retry_policy.has_value()) { + *result_listener << "Expected options to have retry policy, but it was unset"; + return false; + } + return ExplainMatchResult(policyMatcher, arg.retry_policy.value(), result_listener); +} + class MetadataFetcherTest : public testing::Test { public: void setupFetcher() { @@ -248,23 +253,15 @@ TEST_F(MetadataFetcherTest, TestDefaultRetryPolicy) { MockUpstream mock_result(mock_factory_ctx_.cluster_manager_, "200", "not_empty"); MockMetadataReceiver receiver; - EXPECT_CALL(mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, - send_(_, _, - AllOf(OptionsHasRetryPolicy(true), OptionsHasBufferBodyForRetry(true), - OptionsRetryPolicyHasNumRetries(3), OptionsRetryPolicyHasPerTryTimeout(5), - OptionsRetryPolicyHasPerTryIdleTimeout(1)))) - .WillOnce(Invoke( - [](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks&, - const Http::AsyncClient::RequestOptions& options) -> Http::AsyncClient::Request* { - const std::string& retry_on = options.retry_policy.value().retry_on(); - std::set retry_on_modes = absl::StrSplit(retry_on, ','); - - EXPECT_THAT(retry_on_modes, - UnorderedElementsAre("5xx", "gateway-error", "connect-failure", - "refused-stream", "reset")); - return nullptr; - })); - + EXPECT_CALL( + mock_factory_ctx_.cluster_manager_.thread_local_cluster_.async_client_, + send_(_, _, + AllOf(OptionsHasBufferBodyForRetry(true), + OptionsHasRetryPolicy(AllOf( + NumRetries(3), PerTryTimeout(5), PerTryIdleTimeout(1), + RetryOnModes(UnorderedElementsAre("5xx", "gateway-error", "connect-failure", + "refused-stream", "reset"))))))) + .WillOnce(Return(nullptr)); // Act fetcher_->fetch(message, parent_span_, receiver); }