-
Notifications
You must be signed in to change notification settings - Fork 5.3k
aws: add metadata fetcher utility to use http async client #29880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mattklein123
merged 15 commits into
envoyproxy:main
from
suniltheta:remove_libcurl_28_only_required_changes
Oct 30, 2023
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e7e29cc
use http async client instead of libcurl
63b4d18
docs and release notes update
7ca9a64
ignore instance metadata for aws lambda integ test
3ab3360
ignore expected_duration_ when it is not available yet
773c178
improve code coverage
d3732b6
fix constant expression failure
bbf86e8
fix asan memory leak in test
5ce6bf8
Honor host value from AWS_CONTAINER_CREDENTIALS_FULL_URI
0284a53
address review comments
be5eced
update access modifier
08dc2ed
reduce PR 29880 to include metadata fetcher changes
1e5f1bf
Add Metadatafetcher failureToString conversion coverage
da3a2bb
code review f/b
56ec673
Use MATCHER_P helpers for checking options
5433e87
Avoid Invoke by using Matcher
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| #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<Logger::Id::aws>, | ||
| 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(); | ||
| } | ||
|
|
||
| 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"; | ||
| default: | ||
| return ""; | ||
| } | ||
| } | ||
|
|
||
| void fetch(Http::RequestMessage& message, Tracing::Span& parent_span, | ||
| MetadataFetcher::MetadataReceiver& receiver) override { | ||
| ASSERT(!request_); | ||
| ASSERT(!receiver_); | ||
| complete_ = false; | ||
| 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_); | ||
| 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::RequestHeaderMapImpl>( | ||
| {{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<Envoy::Http::RequestMessageImpl>(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_ = makeOptRefFromPtr( | ||
| 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::InvalidMetadata); | ||
| } | ||
| } 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(); | ||
| } | ||
|
|
||
| // 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: | ||
| bool complete_{}; | ||
|
suniltheta marked this conversation as resolved.
|
||
| Upstream::ClusterManager& cm_; | ||
| const std::string cluster_name_; | ||
| OptRef<MetadataFetcher::MetadataReceiver> receiver_; | ||
| OptRef<Http::AsyncClient::Request> request_; | ||
|
|
||
| void reset() { | ||
| request_.reset(); | ||
| receiver_.reset(); | ||
| } | ||
| }; | ||
| } // namespace | ||
|
|
||
| MetadataFetcherPtr MetadataFetcher::create(Upstream::ClusterManager& cm, | ||
| absl::string_view cluster_name) { | ||
| return std::make_unique<MetadataFetcherImpl>(cm, cluster_name); | ||
| } | ||
| } // namespace Aws | ||
| } // namespace Common | ||
| } // namespace Extensions | ||
| } // namespace Envoy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| #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>; | ||
|
|
||
| /** | ||
| * 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 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. | ||
| * | ||
| * @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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.