Coverage Report

Created: 2024-09-19 09:45

/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