/proc/self/cwd/source/extensions/common/aws/utility.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/extensions/common/aws/utility.h" |
2 | | |
3 | | #include <cstdint> |
4 | | #include <limits> |
5 | | |
6 | | #include "envoy/upstream/cluster_manager.h" |
7 | | |
8 | | #include "source/common/common/empty_string.h" |
9 | | #include "source/common/common/fmt.h" |
10 | | #include "source/common/common/utility.h" |
11 | | #include "source/common/json/json_loader.h" |
12 | | #include "source/common/protobuf/message_validator_impl.h" |
13 | | #include "source/common/protobuf/utility.h" |
14 | | |
15 | | #include "absl/strings/match.h" |
16 | | #include "absl/strings/str_join.h" |
17 | | #include "absl/strings/str_split.h" |
18 | | #include "curl/curl.h" |
19 | | #include "fmt/printf.h" |
20 | | |
21 | | namespace Envoy { |
22 | | namespace Extensions { |
23 | | namespace Common { |
24 | | namespace Aws { |
25 | | |
26 | | constexpr absl::string_view PATH_SPLITTER = "/"; |
27 | | constexpr absl::string_view QUERY_PARAM_SEPERATOR = "="; |
28 | | constexpr absl::string_view QUERY_SEPERATOR = "&"; |
29 | | constexpr absl::string_view QUERY_SPLITTER = "?"; |
30 | | constexpr absl::string_view RESERVED_CHARS = "-._~"; |
31 | | constexpr absl::string_view S3_SERVICE_NAME = "s3"; |
32 | | constexpr absl::string_view S3_OUTPOSTS_SERVICE_NAME = "s3-outposts"; |
33 | | constexpr absl::string_view URI_ENCODE = "%{:02X}"; |
34 | | constexpr absl::string_view URI_DOUBLE_ENCODE = "%25{:02X}"; |
35 | | |
36 | | constexpr char AWS_SHARED_CREDENTIALS_FILE[] = "AWS_SHARED_CREDENTIALS_FILE"; |
37 | | constexpr char AWS_PROFILE[] = "AWS_PROFILE"; |
38 | | constexpr char DEFAULT_AWS_SHARED_CREDENTIALS_FILE[] = "/.aws/credentials"; |
39 | | constexpr char DEFAULT_AWS_PROFILE[] = "default"; |
40 | | constexpr char AWS_CONFIG_FILE[] = "AWS_CONFIG_FILE"; |
41 | | constexpr char DEFAULT_AWS_CONFIG_FILE[] = "/.aws/config"; |
42 | | |
43 | | std::map<std::string, std::string> |
44 | | Utility::canonicalizeHeaders(const Http::RequestHeaderMap& headers, |
45 | 0 | const std::vector<Matchers::StringMatcherPtr>& excluded_headers) { |
46 | 0 | std::map<std::string, std::string> out; |
47 | 0 | headers.iterate( |
48 | 0 | [&out, &excluded_headers](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate { |
49 | | // Skip empty headers |
50 | 0 | if (entry.key().empty() || entry.value().empty()) { |
51 | 0 | return Http::HeaderMap::Iterate::Continue; |
52 | 0 | } |
53 | | // Pseudo-headers should not be canonicalized |
54 | 0 | if (!entry.key().getStringView().empty() && entry.key().getStringView()[0] == ':') { |
55 | 0 | return Http::HeaderMap::Iterate::Continue; |
56 | 0 | } |
57 | 0 | const auto key = entry.key().getStringView(); |
58 | 0 | if (std::any_of(excluded_headers.begin(), excluded_headers.end(), |
59 | 0 | [&key](const Matchers::StringMatcherPtr& matcher) { |
60 | 0 | return matcher->match(key); |
61 | 0 | })) { |
62 | 0 | return Http::HeaderMap::Iterate::Continue; |
63 | 0 | } |
64 | | |
65 | 0 | std::string value(entry.value().getStringView()); |
66 | | // Remove leading, trailing, and deduplicate repeated ascii spaces |
67 | 0 | absl::RemoveExtraAsciiWhitespace(&value); |
68 | 0 | const auto iter = out.find(std::string(entry.key().getStringView())); |
69 | | // If the entry already exists, append the new value to the end |
70 | 0 | if (iter != out.end()) { |
71 | 0 | iter->second += fmt::format(",{}", value); |
72 | 0 | } else { |
73 | 0 | out.emplace(std::string(entry.key().getStringView()), value); |
74 | 0 | } |
75 | 0 | return Http::HeaderMap::Iterate::Continue; |
76 | 0 | }); |
77 | | // The AWS SDK has a quirk where it removes "default ports" (80, 443) from the host headers |
78 | | // Additionally, we canonicalize the :authority header as "host" |
79 | | // TODO(suniltheta): This may need to be tweaked to canonicalize :authority for HTTP/2 requests |
80 | 0 | const absl::string_view authority_header = headers.getHostValue(); |
81 | 0 | if (!authority_header.empty()) { |
82 | 0 | const auto parts = StringUtil::splitToken(authority_header, ":"); |
83 | 0 | if (parts.size() > 1 && (parts[1] == "80" || parts[1] == "443")) { |
84 | | // Has default port, so use only the host part |
85 | 0 | out.emplace(Http::Headers::get().HostLegacy.get(), std::string(parts[0])); |
86 | 0 | } else { |
87 | 0 | out.emplace(Http::Headers::get().HostLegacy.get(), std::string(authority_header)); |
88 | 0 | } |
89 | 0 | } |
90 | 0 | return out; |
91 | 0 | } |
92 | | |
93 | | std::string Utility::createCanonicalRequest( |
94 | | absl::string_view service_name, absl::string_view method, absl::string_view path, |
95 | 0 | const std::map<std::string, std::string>& canonical_headers, absl::string_view content_hash) { |
96 | 0 | std::vector<absl::string_view> parts; |
97 | 0 | parts.emplace_back(method); |
98 | | // don't include the query part of the path |
99 | 0 | const auto path_part = StringUtil::cropRight(path, QUERY_SPLITTER); |
100 | 0 | const auto canonicalized_path = path_part.empty() |
101 | 0 | ? std::string{PATH_SPLITTER} |
102 | 0 | : canonicalizePathString(path_part, service_name); |
103 | 0 | parts.emplace_back(canonicalized_path); |
104 | 0 | const auto query_part = StringUtil::cropLeft(path, QUERY_SPLITTER); |
105 | | // if query_part == path_part, then there is no query |
106 | 0 | const auto canonicalized_query = |
107 | 0 | query_part == path_part ? EMPTY_STRING : Utility::canonicalizeQueryString(query_part); |
108 | 0 | parts.emplace_back(absl::string_view(canonicalized_query)); |
109 | 0 | std::vector<std::string> formatted_headers; |
110 | 0 | formatted_headers.reserve(canonical_headers.size()); |
111 | 0 | for (const auto& header : canonical_headers) { |
112 | 0 | formatted_headers.emplace_back(fmt::format("{}:{}", header.first, header.second)); |
113 | 0 | parts.emplace_back(formatted_headers.back()); |
114 | 0 | } |
115 | | // need an extra blank space after the canonical headers |
116 | 0 | parts.emplace_back(EMPTY_STRING); |
117 | 0 | const auto signed_headers = Utility::joinCanonicalHeaderNames(canonical_headers); |
118 | 0 | parts.emplace_back(signed_headers); |
119 | 0 | parts.emplace_back(content_hash); |
120 | 0 | return absl::StrJoin(parts, "\n"); |
121 | 0 | } |
122 | | |
123 | | /** |
124 | | * Normalizes the path string based on AWS requirements. |
125 | | * See step 2 in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html |
126 | | */ |
127 | | std::string Utility::canonicalizePathString(absl::string_view path_string, |
128 | 0 | absl::string_view service_name) { |
129 | | // If service is S3 or outposts, do not normalize but only encode the path |
130 | 0 | if (absl::EqualsIgnoreCase(service_name, S3_SERVICE_NAME) || |
131 | 0 | absl::EqualsIgnoreCase(service_name, S3_OUTPOSTS_SERVICE_NAME)) { |
132 | 0 | return encodePathSegment(path_string, service_name); |
133 | 0 | } |
134 | | // If service is not S3, normalize and encode the path |
135 | 0 | const auto path_segments = StringUtil::splitToken(path_string, std::string{PATH_SPLITTER}); |
136 | 0 | std::vector<std::string> path_list; |
137 | 0 | path_list.reserve(path_segments.size()); |
138 | 0 | for (const auto& path_segment : path_segments) { |
139 | 0 | if (path_segment.empty()) { |
140 | 0 | continue; |
141 | 0 | } |
142 | 0 | path_list.emplace_back(encodePathSegment(path_segment, service_name)); |
143 | 0 | } |
144 | 0 | auto canonical_path_string = |
145 | 0 | fmt::format("{}{}", PATH_SPLITTER, absl::StrJoin(path_list, PATH_SPLITTER)); |
146 | | // Handle corner case when path ends with '/' |
147 | 0 | if (absl::EndsWith(path_string, PATH_SPLITTER) && canonical_path_string.size() > 1) { |
148 | 0 | canonical_path_string.push_back(PATH_SPLITTER[0]); |
149 | 0 | } |
150 | 0 | return canonical_path_string; |
151 | 0 | } |
152 | | |
153 | 0 | bool isReservedChar(const char c) { |
154 | 0 | return std::isalnum(c) || RESERVED_CHARS.find(c) != std::string::npos; |
155 | 0 | } |
156 | | |
157 | 0 | void encodeS3Path(std::string& encoded, const char& c) { |
158 | | // Do not encode '/' for S3 and do not double encode |
159 | 0 | if ((c == PATH_SPLITTER[0]) || (c == '%')) { |
160 | 0 | encoded.push_back(c); |
161 | 0 | } else { |
162 | 0 | absl::StrAppend(&encoded, fmt::format(URI_ENCODE, c)); |
163 | 0 | } |
164 | 0 | } |
165 | | |
166 | 0 | std::string Utility::encodePathSegment(absl::string_view decoded, absl::string_view service_name) { |
167 | 0 | std::string encoded; |
168 | |
|
169 | 0 | for (char c : decoded) { |
170 | 0 | if (isReservedChar(c)) { |
171 | | // Escape unreserved chars from RFC 3986 |
172 | 0 | encoded.push_back(c); |
173 | 0 | } else if (absl::EqualsIgnoreCase(service_name, S3_SERVICE_NAME) || |
174 | 0 | absl::EqualsIgnoreCase(service_name, S3_OUTPOSTS_SERVICE_NAME)) { |
175 | 0 | encodeS3Path(encoded, c); |
176 | 0 | } else { |
177 | | // TODO: @aws, There is some inconsistency between AWS services if this should be double |
178 | | // encoded or not. We need to parameterize this and expose this in the config. Ref: |
179 | | // https://github.com/aws/aws-sdk-cpp/blob/main/aws-cpp-sdk-core/source/auth/AWSAuthSigner.cpp#L79-L93 |
180 | 0 | absl::StrAppend(&encoded, fmt::format(URI_ENCODE, c)); |
181 | 0 | } |
182 | 0 | } |
183 | 0 | return encoded; |
184 | 0 | } |
185 | | |
186 | | /** |
187 | | * Normalizes the query string based on AWS requirements. |
188 | | * See step 3 in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html |
189 | | */ |
190 | 0 | std::string Utility::canonicalizeQueryString(absl::string_view query_string) { |
191 | | // Sort query string based on param name and append "=" if value is missing |
192 | 0 | const auto query_fragments = StringUtil::splitToken(query_string, QUERY_SEPERATOR); |
193 | 0 | std::vector<std::pair<std::string, std::string>> query_list; |
194 | 0 | for (const auto& query_fragment : query_fragments) { |
195 | | // Only split at the first "=" and encode the rest |
196 | 0 | const std::vector<std::string> query = |
197 | 0 | absl::StrSplit(query_fragment, absl::MaxSplits(QUERY_PARAM_SEPERATOR, 1)); |
198 | 0 | if (!query.empty()) { |
199 | 0 | const absl::string_view param = query[0]; |
200 | 0 | const absl::string_view value = query.size() > 1 ? query[1] : EMPTY_STRING; |
201 | 0 | query_list.emplace_back(std::make_pair(param, value)); |
202 | 0 | } |
203 | 0 | } |
204 | | // Sort query params by name and value |
205 | 0 | std::sort(query_list.begin(), query_list.end()); |
206 | | // Encode query params name and value separately |
207 | 0 | for (auto& query : query_list) { |
208 | 0 | query = std::make_pair(Utility::encodeQueryParam(query.first), |
209 | 0 | Utility::encodeQueryParam(query.second)); |
210 | 0 | } |
211 | 0 | return absl::StrJoin(query_list, QUERY_SEPERATOR, absl::PairFormatter(QUERY_PARAM_SEPERATOR)); |
212 | 0 | } |
213 | | |
214 | 0 | std::string Utility::encodeQueryParam(absl::string_view decoded) { |
215 | 0 | std::string encoded; |
216 | 0 | for (char c : decoded) { |
217 | 0 | if (isReservedChar(c) || c == '%') { |
218 | | // Escape unreserved chars from RFC 3986 |
219 | 0 | encoded.push_back(c); |
220 | 0 | } else if (c == '+') { |
221 | | // Encode '+' as space |
222 | 0 | absl::StrAppend(&encoded, "%20"); |
223 | 0 | } else if (c == QUERY_PARAM_SEPERATOR[0]) { |
224 | | // Double encode '=' |
225 | 0 | absl::StrAppend(&encoded, fmt::format(URI_DOUBLE_ENCODE, c)); |
226 | 0 | } else { |
227 | 0 | absl::StrAppend(&encoded, fmt::format(URI_ENCODE, c)); |
228 | 0 | } |
229 | 0 | } |
230 | 0 | return encoded; |
231 | 0 | } |
232 | | |
233 | | std::string |
234 | 0 | Utility::joinCanonicalHeaderNames(const std::map<std::string, std::string>& canonical_headers) { |
235 | 0 | return absl::StrJoin(canonical_headers, ";", [](auto* out, const auto& pair) { |
236 | 0 | return absl::StrAppend(out, pair.first); |
237 | 0 | }); |
238 | 0 | } |
239 | | |
240 | | /** |
241 | | * This function generates an STS Endpoint from a region string. |
242 | | * If a SigV4A region set has been provided, it will use the first region in region set, and if |
243 | | * that region still contains a wildcard, the STS Endpoint will be set to us-east-1 global endpoint |
244 | | * (or FIPS if compiled for FIPS support) |
245 | | */ |
246 | 0 | std::string Utility::getSTSEndpoint(absl::string_view region) { |
247 | 0 | std::string single_region; |
248 | | |
249 | | // If we contain a comma or asterisk it looks like a region set. |
250 | 0 | if (absl::StrContains(region, ",") || (absl::StrContains(region, "*"))) { |
251 | | // Use the first element from a region set if we have multiple regions specified. |
252 | 0 | const std::vector<std::string> region_v = absl::StrSplit(region, ','); |
253 | | // If we still have a * in the first element, then send them to us-east-1 fips or global |
254 | | // endpoint. |
255 | 0 | if (absl::StrContains(region_v[0], '*')) { |
256 | | #ifdef ENVOY_SSL_FIPS |
257 | | return "sts-fips.us-east-1.amazonaws.com"; |
258 | | #else |
259 | 0 | return "sts.amazonaws.com"; |
260 | 0 | #endif |
261 | 0 | } |
262 | 0 | single_region = region_v[0]; |
263 | 0 | } else { |
264 | | // Otherwise it's a standard region, so use that. |
265 | 0 | single_region = region; |
266 | 0 | } |
267 | | |
268 | 0 | if (single_region == "cn-northwest-1" || single_region == "cn-north-1") { |
269 | 0 | return fmt::format("sts.{}.amazonaws.com.cn", single_region); |
270 | 0 | } |
271 | | #ifdef ENVOY_SSL_FIPS |
272 | | // Use AWS STS FIPS endpoints in FIPS mode https://docs.aws.amazon.com/general/latest/gr/sts.html. |
273 | | // Note: AWS GovCloud doesn't have separate fips endpoints. |
274 | | // TODO(suniltheta): Include `ca-central-1` when sts supports a dedicated FIPS endpoint. |
275 | | if (single_region == "us-east-1" || single_region == "us-east-2" || |
276 | | single_region == "us-west-1" || single_region == "us-west-2") { |
277 | | return fmt::format("sts-fips.{}.amazonaws.com", single_region); |
278 | | } |
279 | | #endif |
280 | 0 | return fmt::format("sts.{}.amazonaws.com", single_region); |
281 | 0 | } |
282 | | |
283 | 0 | static size_t curlCallback(char* ptr, size_t, size_t nmemb, void* data) { |
284 | 0 | auto buf = static_cast<std::string*>(data); |
285 | 0 | buf->append(ptr, nmemb); |
286 | 0 | return nmemb; |
287 | 0 | } |
288 | | |
289 | 0 | absl::optional<std::string> Utility::fetchMetadata(Http::RequestMessage& message) { |
290 | 0 | static const size_t MAX_RETRIES = 4; |
291 | 0 | static const std::chrono::milliseconds RETRY_DELAY{1000}; |
292 | 0 | static const std::chrono::seconds TIMEOUT{5}; |
293 | |
|
294 | 0 | CURL* const curl = curl_easy_init(); |
295 | 0 | if (!curl) { |
296 | 0 | return absl::nullopt; |
297 | 0 | }; |
298 | |
|
299 | 0 | const auto host = message.headers().getHostValue(); |
300 | 0 | const auto path = message.headers().getPathValue(); |
301 | 0 | const auto method = message.headers().getMethodValue(); |
302 | 0 | const auto scheme = message.headers().getSchemeValue(); |
303 | |
|
304 | 0 | const std::string url = fmt::format("{}://{}{}", scheme, host, path); |
305 | 0 | curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); |
306 | 0 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT.count()); |
307 | 0 | curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); |
308 | 0 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); |
309 | |
|
310 | 0 | std::string buffer; |
311 | 0 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); |
312 | 0 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallback); |
313 | |
|
314 | 0 | struct curl_slist* headers = nullptr; |
315 | 0 | message.headers().iterate([&headers](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate { |
316 | | // Skip pseudo-headers |
317 | 0 | if (!entry.key().getStringView().empty() && entry.key().getStringView()[0] == ':') { |
318 | 0 | return Http::HeaderMap::Iterate::Continue; |
319 | 0 | } |
320 | 0 | const std::string header = |
321 | 0 | fmt::format("{}: {}", entry.key().getStringView(), entry.value().getStringView()); |
322 | 0 | headers = curl_slist_append(headers, header.c_str()); |
323 | 0 | return Http::HeaderMap::Iterate::Continue; |
324 | 0 | }); |
325 | | |
326 | | // This function only support doing PUT(UPLOAD) other than GET(_default_) operation. |
327 | 0 | if (Http::Headers::get().MethodValues.Put == method) { |
328 | | // https://curl.se/libcurl/c/CURLOPT_PUT.html is deprecated |
329 | | // so using https://curl.se/libcurl/c/CURLOPT_UPLOAD.html. |
330 | 0 | curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); |
331 | | // To call PUT on HTTP 1.0 we must specify a value for the upload size |
332 | | // since some old EC2's metadata service will be serving on HTTP 1.0. |
333 | | // https://curl.se/libcurl/c/CURLOPT_INFILESIZE.html |
334 | 0 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0); |
335 | | // Disabling `Expect: 100-continue` header to get a response |
336 | | // in the first attempt as the put size is zero. |
337 | | // https://everything.curl.dev/http/post/expect100 |
338 | 0 | headers = curl_slist_append(headers, "Expect:"); |
339 | 0 | } |
340 | |
|
341 | 0 | if (headers != nullptr) { |
342 | 0 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); |
343 | 0 | } |
344 | |
|
345 | 0 | for (size_t retry = 0; retry < MAX_RETRIES; retry++) { |
346 | 0 | const CURLcode res = curl_easy_perform(curl); |
347 | 0 | if (res == CURLE_OK) { |
348 | 0 | break; |
349 | 0 | } |
350 | 0 | ENVOY_LOG_MISC(debug, "Could not fetch AWS metadata: {}", curl_easy_strerror(res)); |
351 | 0 | buffer.clear(); |
352 | 0 | std::this_thread::sleep_for(RETRY_DELAY); |
353 | 0 | } |
354 | |
|
355 | 0 | curl_easy_cleanup(curl); |
356 | 0 | curl_slist_free_all(headers); |
357 | |
|
358 | 0 | return buffer.empty() ? absl::nullopt : absl::optional<std::string>(buffer); |
359 | 0 | } |
360 | | |
361 | | envoy::config::cluster::v3::Cluster Utility::createInternalClusterStatic( |
362 | | absl::string_view cluster_name, |
363 | 0 | const envoy::config::cluster::v3::Cluster::DiscoveryType cluster_type, absl::string_view uri) { |
364 | | // Check if local cluster exists with that name. |
365 | |
|
366 | 0 | envoy::config::cluster::v3::Cluster cluster; |
367 | 0 | absl::string_view host_port; |
368 | 0 | absl::string_view path; |
369 | 0 | Http::Utility::extractHostPathFromUri(uri, host_port, path); |
370 | 0 | const auto host_attributes = Http::Utility::parseAuthority(host_port); |
371 | 0 | const auto host = host_attributes.host_; |
372 | 0 | const auto port = host_attributes.port_ ? host_attributes.port_.value() : 80; |
373 | |
|
374 | 0 | cluster.set_name(cluster_name); |
375 | 0 | cluster.set_type(cluster_type); |
376 | 0 | cluster.mutable_connect_timeout()->set_seconds(5); |
377 | 0 | cluster.mutable_load_assignment()->set_cluster_name(cluster_name); |
378 | 0 | auto* endpoint = |
379 | 0 | cluster.mutable_load_assignment()->add_endpoints()->add_lb_endpoints()->mutable_endpoint(); |
380 | 0 | auto* addr = endpoint->mutable_address(); |
381 | 0 | addr->mutable_socket_address()->set_address(host); |
382 | 0 | addr->mutable_socket_address()->set_port_value(port); |
383 | 0 | cluster.set_lb_policy(envoy::config::cluster::v3::Cluster::ROUND_ROBIN); |
384 | 0 | envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options; |
385 | 0 | auto* http_protocol_options = |
386 | 0 | protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options(); |
387 | 0 | http_protocol_options->set_accept_http_10(true); |
388 | 0 | (*cluster.mutable_typed_extension_protocol_options()) |
389 | 0 | ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] |
390 | 0 | .PackFrom(protocol_options); |
391 | | |
392 | | // Add tls transport socket if cluster supports https over port 443. |
393 | 0 | if (port == 443) { |
394 | 0 | auto* socket = cluster.mutable_transport_socket(); |
395 | 0 | envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_socket; |
396 | 0 | socket->set_name("envoy.transport_sockets.tls"); |
397 | 0 | socket->mutable_typed_config()->PackFrom(tls_socket); |
398 | 0 | } |
399 | |
|
400 | 0 | return cluster; |
401 | 0 | } |
402 | | |
403 | | std::string Utility::getEnvironmentVariableOrDefault(const std::string& variable_name, |
404 | 0 | const std::string& default_value) { |
405 | 0 | const char* value = getenv(variable_name.c_str()); |
406 | 0 | return (value != nullptr) && (value[0] != '\0') ? value : default_value; |
407 | 0 | } |
408 | | |
409 | | bool Utility::resolveProfileElements(const std::string& profile_file, |
410 | | const std::string& profile_name, |
411 | 0 | absl::flat_hash_map<std::string, std::string>& elements) { |
412 | 0 | std::ifstream file(profile_file); |
413 | 0 | if (!file.is_open()) { |
414 | 0 | ENVOY_LOG_MISC(debug, "Error opening credentials file {}", profile_file); |
415 | 0 | return false; |
416 | 0 | } |
417 | 0 | const auto profile_start = absl::StrFormat("[%s]", profile_name); |
418 | |
|
419 | 0 | bool found_profile = false; |
420 | 0 | std::string line; |
421 | 0 | while (std::getline(file, line)) { |
422 | 0 | line = std::string(StringUtil::trim(line)); |
423 | 0 | if (line.empty()) { |
424 | 0 | continue; |
425 | 0 | } |
426 | | |
427 | 0 | if (line == profile_start) { |
428 | 0 | found_profile = true; |
429 | 0 | continue; |
430 | 0 | } |
431 | | |
432 | 0 | if (found_profile) { |
433 | | // Stop reading once we find the start of the next profile. |
434 | 0 | if (absl::StartsWith(line, "[")) { |
435 | 0 | break; |
436 | 0 | } |
437 | | |
438 | 0 | std::vector<std::string> parts = absl::StrSplit(line, absl::MaxSplits('=', 1)); |
439 | 0 | if (parts.size() == 2) { |
440 | |
|
441 | 0 | const auto key = StringUtil::toUpper(StringUtil::trim(parts[0])); |
442 | 0 | const auto val = StringUtil::trim(parts[1]); |
443 | 0 | auto found = elements.find(key); |
444 | 0 | if (found != elements.end()) { |
445 | 0 | found->second = val; |
446 | 0 | } |
447 | 0 | } |
448 | 0 | } |
449 | 0 | } |
450 | 0 | return true; |
451 | 0 | } |
452 | | |
453 | 0 | std::string Utility::getCredentialFilePath() { |
454 | | |
455 | | // Default credential file path plus current home directory. Will fall back to / if HOME |
456 | | // environment variable does |
457 | | // not exist |
458 | |
|
459 | 0 | const auto home = Utility::getEnvironmentVariableOrDefault("HOME", ""); |
460 | 0 | const auto default_credentials_file_path = |
461 | 0 | absl::StrCat(home, DEFAULT_AWS_SHARED_CREDENTIALS_FILE); |
462 | |
|
463 | 0 | return Utility::getEnvironmentVariableOrDefault(AWS_SHARED_CREDENTIALS_FILE, |
464 | 0 | default_credentials_file_path); |
465 | 0 | } |
466 | | |
467 | 0 | std::string Utility::getConfigFilePath() { |
468 | | |
469 | | // Default config file path plus current home directory. Will fall back to / if HOME environment |
470 | | // variable does |
471 | | // not exist |
472 | |
|
473 | 0 | const auto home = Utility::getEnvironmentVariableOrDefault("HOME", ""); |
474 | 0 | const auto default_credentials_file_path = absl::StrCat(home, DEFAULT_AWS_CONFIG_FILE); |
475 | |
|
476 | 0 | return Utility::getEnvironmentVariableOrDefault(AWS_CONFIG_FILE, default_credentials_file_path); |
477 | 0 | } |
478 | | |
479 | 0 | std::string Utility::getCredentialProfileName() { |
480 | 0 | return Utility::getEnvironmentVariableOrDefault(AWS_PROFILE, DEFAULT_AWS_PROFILE); |
481 | 0 | } |
482 | | |
483 | 0 | std::string Utility::getConfigProfileName() { |
484 | 0 | auto profile_name = Utility::getEnvironmentVariableOrDefault(AWS_PROFILE, DEFAULT_AWS_PROFILE); |
485 | 0 | if (profile_name == DEFAULT_AWS_PROFILE) { |
486 | 0 | return profile_name; |
487 | 0 | } else { |
488 | 0 | return "profile " + profile_name; |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | | std::string Utility::getStringFromJsonOrDefault(Json::ObjectSharedPtr json_object, |
493 | | const std::string& string_value, |
494 | 0 | const std::string& string_default) { |
495 | 0 | absl::StatusOr<Envoy::Json::ValueType> value_or_error; |
496 | 0 | value_or_error = json_object->getValue(string_value); |
497 | 0 | if ((!value_or_error.ok()) || (!absl::holds_alternative<std::string>(value_or_error.value()))) { |
498 | |
|
499 | 0 | ENVOY_LOG_MISC(error, "Unable to retrieve string value from json: {}", string_value); |
500 | 0 | return string_default; |
501 | 0 | } |
502 | 0 | return absl::get<std::string>(value_or_error.value()); |
503 | 0 | } |
504 | | |
505 | | int64_t Utility::getIntegerFromJsonOrDefault(Json::ObjectSharedPtr json_object, |
506 | | const std::string& integer_value, |
507 | 0 | const int64_t integer_default) { |
508 | 0 | absl::StatusOr<Envoy::Json::ValueType> value_or_error; |
509 | 0 | value_or_error = json_object->getValue(integer_value); |
510 | 0 | if (!value_or_error.ok() || ((!absl::holds_alternative<double>(value_or_error.value())) && |
511 | 0 | (!absl::holds_alternative<int64_t>(value_or_error.value())))) { |
512 | 0 | ENVOY_LOG_MISC(error, "Unable to retrieve integer value from json: {}", integer_value); |
513 | 0 | return integer_default; |
514 | 0 | } |
515 | 0 | auto json_integer = value_or_error.value(); |
516 | | // Handle double formatted integers IE exponent format such as 1.714449238E9 |
517 | 0 | if (auto* double_integer = absl::get_if<double>(&json_integer)) { |
518 | 0 | if (*double_integer < 0) { |
519 | 0 | ENVOY_LOG_MISC(error, "Integer {} less than 0: {}", integer_value, *double_integer); |
520 | 0 | return integer_default; |
521 | 0 | } else { |
522 | 0 | return int64_t(*double_integer); |
523 | 0 | } |
524 | 0 | } else { |
525 | | // Standard integer |
526 | 0 | return absl::get<int64_t>(json_integer); |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | } // namespace Aws |
531 | | } // namespace Common |
532 | | } // namespace Extensions |
533 | | } // namespace Envoy |