Line data Source code
1 : #include "source/extensions/common/aws/metadata_fetcher.h"
2 :
3 : #include "envoy/config/core/v3/base.pb.h"
4 : #include "envoy/config/core/v3/http_uri.pb.h"
5 :
6 : #include "source/common/common/enum_to_int.h"
7 : #include "source/common/http/headers.h"
8 : #include "source/common/http/utility.h"
9 : #include "source/common/protobuf/utility.h"
10 :
11 : namespace Envoy {
12 : namespace Extensions {
13 : namespace Common {
14 : namespace Aws {
15 :
16 : namespace {
17 :
18 : class MetadataFetcherImpl : public MetadataFetcher,
19 : public Logger::Loggable<Logger::Id::aws>,
20 : public Http::AsyncClient::Callbacks {
21 :
22 : public:
23 : MetadataFetcherImpl(Upstream::ClusterManager& cm, absl::string_view cluster_name)
24 0 : : cm_(cm), cluster_name_(std::string(cluster_name)) {}
25 :
26 0 : ~MetadataFetcherImpl() override { cancel(); }
27 :
28 0 : void cancel() override {
29 0 : if (request_ && !complete_) {
30 0 : request_->cancel();
31 0 : ENVOY_LOG(debug, "fetch AWS Metadata [cluster = {}]: cancelled", cluster_name_);
32 0 : }
33 0 : reset();
34 0 : }
35 :
36 0 : absl::string_view failureToString(MetadataFetcher::MetadataReceiver::Failure reason) override {
37 0 : switch (reason) {
38 0 : case MetadataFetcher::MetadataReceiver::Failure::Network:
39 0 : return "Network";
40 0 : case MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata:
41 0 : return "InvalidMetadata";
42 0 : case MetadataFetcher::MetadataReceiver::Failure::MissingConfig:
43 0 : return "MissingConfig";
44 0 : default:
45 0 : return "";
46 0 : }
47 0 : }
48 :
49 : void fetch(Http::RequestMessage& message, Tracing::Span& parent_span,
50 0 : MetadataFetcher::MetadataReceiver& receiver) override {
51 0 : ASSERT(!request_);
52 0 : complete_ = false;
53 0 : receiver_ = makeOptRef(receiver);
54 0 : const auto thread_local_cluster = cm_.getThreadLocalCluster(cluster_name_);
55 0 : if (thread_local_cluster == nullptr) {
56 0 : ENVOY_LOG(error, "{} AWS Metadata failed: [cluster = {}] not found", __func__, cluster_name_);
57 0 : complete_ = true;
58 0 : receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::MissingConfig);
59 0 : reset();
60 0 : return;
61 0 : }
62 :
63 0 : constexpr uint64_t MAX_RETRIES = 3;
64 0 : constexpr uint64_t RETRY_DELAY = 1000;
65 0 : constexpr uint64_t TIMEOUT = 5 * 1000;
66 :
67 0 : const auto host_attributes = Http::Utility::parseAuthority(message.headers().getHostValue());
68 0 : const auto host = host_attributes.host_;
69 0 : const auto path = message.headers().getPathValue();
70 0 : const auto scheme = message.headers().getSchemeValue();
71 0 : const auto method = message.headers().getMethodValue();
72 :
73 0 : const size_t query_offset = path.find('?');
74 : // Sanitize the path before logging.
75 : // However, the route debug log will still display the entire path.
76 : // So safely store the Envoy logs at debug level.
77 0 : const absl::string_view sanitized_path =
78 0 : query_offset != absl::string_view::npos ? path.substr(0, query_offset) : path;
79 0 : ENVOY_LOG(debug, "fetch AWS Metadata from the cluster {} at [uri = {}]", cluster_name_,
80 0 : fmt::format("{}://{}{}", scheme, host, sanitized_path));
81 :
82 0 : Http::RequestHeaderMapPtr headersPtr =
83 0 : Envoy::Http::createHeaderMap<Envoy::Http::RequestHeaderMapImpl>(
84 0 : {{Envoy::Http::Headers::get().Method, std::string(method)},
85 0 : {Envoy::Http::Headers::get().Host, std::string(host)},
86 0 : {Envoy::Http::Headers::get().Scheme, std::string(scheme)},
87 0 : {Envoy::Http::Headers::get().Path, std::string(path)}});
88 :
89 : // Copy the remaining headers.
90 0 : message.headers().iterate(
91 0 : [&headersPtr](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate {
92 : // Skip pseudo-headers
93 0 : if (!entry.key().getStringView().empty() && entry.key().getStringView()[0] == ':') {
94 0 : return Http::HeaderMap::Iterate::Continue;
95 0 : }
96 0 : headersPtr->addCopy(Http::LowerCaseString(entry.key().getStringView()),
97 0 : entry.value().getStringView());
98 0 : return Http::HeaderMap::Iterate::Continue;
99 0 : });
100 :
101 0 : auto messagePtr = std::make_unique<Envoy::Http::RequestMessageImpl>(std::move(headersPtr));
102 :
103 0 : auto options = Http::AsyncClient::RequestOptions()
104 0 : .setTimeout(std::chrono::milliseconds(TIMEOUT))
105 0 : .setParentSpan(parent_span)
106 0 : .setSendXff(false)
107 0 : .setChildSpanName("AWS Metadata Fetch");
108 :
109 0 : envoy::config::route::v3::RetryPolicy route_retry_policy;
110 0 : route_retry_policy.mutable_num_retries()->set_value(MAX_RETRIES);
111 0 : route_retry_policy.mutable_per_try_timeout()->CopyFrom(
112 0 : Protobuf::util::TimeUtil::MillisecondsToDuration(TIMEOUT));
113 0 : route_retry_policy.mutable_per_try_idle_timeout()->CopyFrom(
114 0 : Protobuf::util::TimeUtil::MillisecondsToDuration(RETRY_DELAY));
115 0 : route_retry_policy.set_retry_on("5xx,gateway-error,connect-failure,reset,refused-stream");
116 :
117 0 : options.setRetryPolicy(route_retry_policy);
118 0 : options.setBufferBodyForRetry(true);
119 0 : request_ = makeOptRefFromPtr(
120 0 : thread_local_cluster->httpAsyncClient().send(std::move(messagePtr), *this, options));
121 0 : }
122 :
123 : // HTTP async receive method on success.
124 0 : void onSuccess(const Http::AsyncClient::Request&, Http::ResponseMessagePtr&& response) override {
125 0 : ASSERT(receiver_);
126 0 : complete_ = true;
127 0 : const uint64_t status_code = Http::Utility::getResponseStatus(response->headers());
128 0 : if (status_code == enumToInt(Http::Code::OK)) {
129 0 : ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: success", __func__, cluster_name_);
130 0 : if (response->body().length() != 0) {
131 0 : const auto body = response->bodyAsString();
132 0 : receiver_->onMetadataSuccess(std::move(body));
133 0 : } else {
134 0 : ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: body is empty", __func__,
135 0 : cluster_name_);
136 0 : receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::InvalidMetadata);
137 0 : }
138 0 : } else {
139 0 : if (response->body().length() != 0) {
140 0 : ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: response status code {}, body: {}",
141 0 : __func__, cluster_name_, status_code, response->bodyAsString());
142 0 : } else {
143 0 : ENVOY_LOG(debug,
144 0 : "{}: fetch AWS Metadata [cluster = {}]: response status code {}, body is empty",
145 0 : __func__, cluster_name_, status_code);
146 0 : }
147 0 : receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network);
148 0 : }
149 0 : reset();
150 0 : }
151 :
152 : // HTTP async receive method on failure.
153 : void onFailure(const Http::AsyncClient::Request&,
154 0 : Http::AsyncClient::FailureReason reason) override {
155 0 : ASSERT(receiver_);
156 0 : ENVOY_LOG(debug, "{}: fetch AWS Metadata [cluster = {}]: network error {}", __func__,
157 0 : cluster_name_, enumToInt(reason));
158 0 : complete_ = true;
159 0 : receiver_->onMetadataError(MetadataFetcher::MetadataReceiver::Failure::Network);
160 0 : reset();
161 0 : }
162 :
163 : // TODO(suniltheta): Add metadata fetch status into the span like it is done on ext_authz filter.
164 0 : void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {}
165 :
166 : private:
167 : bool complete_{};
168 : Upstream::ClusterManager& cm_;
169 : const std::string cluster_name_;
170 : OptRef<MetadataFetcher::MetadataReceiver> receiver_;
171 : OptRef<Http::AsyncClient::Request> request_;
172 :
173 0 : void reset() { request_.reset(); }
174 : };
175 : } // namespace
176 :
177 : MetadataFetcherPtr MetadataFetcher::create(Upstream::ClusterManager& cm,
178 0 : absl::string_view cluster_name) {
179 0 : return std::make_unique<MetadataFetcherImpl>(cm, cluster_name);
180 0 : }
181 : } // namespace Aws
182 : } // namespace Common
183 : } // namespace Extensions
184 : } // namespace Envoy
|