1
#include "source/extensions/common/aws/utility.h"
2

            
3
#include "envoy/extensions/upstreams/http/v3/http_protocol_options.pb.h"
4

            
5
#include "source/common/common/empty_string.h"
6
#include "source/common/http/headers.h"
7
#include "source/common/http/utility.h"
8
#include "source/extensions/common/aws/signer_base_impl.h"
9

            
10
#include "openssl/crypto.h"
11

            
12
namespace Envoy {
13
namespace Extensions {
14
namespace Common {
15
namespace Aws {
16

            
17
constexpr absl::string_view PATH_SPLITTER = "/";
18
constexpr absl::string_view QUERY_PARAM_SEPERATOR = "=";
19
constexpr absl::string_view QUERY_SEPERATOR = "&";
20
constexpr absl::string_view QUERY_SPLITTER = "?";
21
constexpr absl::string_view RESERVED_CHARS = "-._~";
22
constexpr absl::string_view URI_ENCODE = "%{:02X}";
23

            
24
constexpr char AWS_SHARED_CREDENTIALS_FILE[] = "AWS_SHARED_CREDENTIALS_FILE";
25
constexpr char AWS_PROFILE[] = "AWS_PROFILE";
26
constexpr char DEFAULT_AWS_SHARED_CREDENTIALS_FILE[] = "/.aws/credentials";
27
constexpr char DEFAULT_AWS_PROFILE[] = "default";
28
constexpr char AWS_CONFIG_FILE[] = "AWS_CONFIG_FILE";
29
constexpr char DEFAULT_AWS_CONFIG_FILE[] = "/.aws/config";
30

            
31
std::map<std::string, std::string>
32
Utility::canonicalizeHeaders(const Http::RequestHeaderMap& headers,
33
                             const std::vector<Matchers::StringMatcherPtr>& excluded_headers,
34
259
                             const std::vector<Matchers::StringMatcherPtr>& included_headers) {
35
259
  std::map<std::string, std::string> out;
36

            
37
259
  headers.iterate([&out, &excluded_headers,
38
1352
                   &included_headers](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate {
39
    // Skip empty headers
40
1352
    if (entry.key().empty() || entry.value().empty()) {
41
      return Http::HeaderMap::Iterate::Continue;
42
    }
43
1352
    const auto key = entry.key().getStringView();
44

            
45
    // Pseudo-headers should not be canonicalized
46
1352
    if (!key.empty() && key[0] == ':') {
47
787
      return Http::HeaderMap::Iterate::Continue;
48
787
    }
49

            
50
    // Always include x-amz-* and content-type headers per AWS signing requirements
51
565
    const auto key_lower = absl::AsciiStrToLower(key);
52
565
    const bool is_required_header =
53
565
        absl::StartsWith(key_lower, "x-amz-") || key_lower == "content-type";
54

            
55
565
    if (!is_required_header) {
56
151
      if (!included_headers.empty()) {
57
        // If included_headers is set, only include headers that match
58
13
        if (!std::any_of(included_headers.begin(), included_headers.end(),
59
17
                         [&key](const Matchers::StringMatcherPtr& matcher) {
60
17
                           return matcher->match(key);
61
17
                         })) {
62
5
          return Http::HeaderMap::Iterate::Continue;
63
5
        }
64
138
      } else {
65
        // Otherwise use excluded_headers
66
138
        if (std::any_of(excluded_headers.begin(), excluded_headers.end(),
67
276
                        [&key](const Matchers::StringMatcherPtr& matcher) {
68
221
                          return matcher->match(key);
69
221
                        })) {
70
16
          return Http::HeaderMap::Iterate::Continue;
71
16
        }
72
138
      }
73
151
    }
74

            
75
544
    std::string value(entry.value().getStringView());
76

            
77
    // Remove leading, trailing, and deduplicate repeated ascii spaces
78
544
    absl::RemoveExtraAsciiWhitespace(&value);
79
544
    const std::string key_str(key);
80
544
    const auto iter = out.find(key_str);
81
    // If the entry already exists, append the new value to the end
82
544
    if (iter != out.end()) {
83
22
      iter->second += fmt::format(",{}", value);
84
522
    } else {
85
522
      out.emplace(std::move(key_str), std::move(value));
86
522
    }
87
544
    return Http::HeaderMap::Iterate::Continue;
88
565
  });
89
  // The AWS SDK has a quirk where it removes "default ports" (80, 443) from the host headers
90
  // Additionally, we canonicalize the :authority header as "host"
91
  // TODO(suniltheta): This may need to be tweaked to canonicalize :authority for HTTP/2 requests
92
259
  const absl::string_view authority_header = headers.getHostValue();
93
259
  if (!authority_header.empty()) {
94
240
    const auto parts = StringUtil::splitToken(authority_header, ":");
95
240
    if (parts.size() > 1 && (parts[1] == "80" || parts[1] == "443")) {
96
      // Has default port, so use only the host part
97
2
      out.emplace(Http::Headers::get().HostLegacy.get(), std::string(parts[0]));
98
238
    } else {
99
238
      out.emplace(Http::Headers::get().HostLegacy.get(), std::string(authority_header));
100
238
    }
101
240
  }
102
259
  return out;
103
259
}
104

            
105
std::string
106
Utility::createCanonicalRequest(absl::string_view method, absl::string_view path,
107
                                const std::map<std::string, std::string>& canonical_headers,
108
                                absl::string_view content_hash, bool should_normalize_uri_path,
109
254
                                bool use_double_uri_uncode) {
110

            
111
254
  std::string canonical_request;
112

            
113
  // Add the method
114
254
  canonical_request = absl::StrCat(method, "\n");
115

            
116
  // don't include the query part of the path
117
254
  const auto path_part = StringUtil::cropRight(path, QUERY_SPLITTER);
118

            
119
254
  std::string new_path;
120
254
  if (use_double_uri_uncode) {
121
248
    if (should_normalize_uri_path) {
122
220
      new_path = normalizePath(path_part);
123
220
    } else {
124
28
      new_path = path_part;
125
28
    }
126
248
    new_path = uriEncodePath(new_path);
127
248
  } else {
128
    // Special case for S3 and its variants when path is not pre-encoded at all
129
6
    auto encoded_path =
130
6
        isUriPathEncoded(path_part) ? std::string(path_part) : uriEncodePath(path_part);
131
6
    if (should_normalize_uri_path) {
132
1
      new_path = normalizePath(encoded_path);
133
5
    } else {
134
5
      new_path = encoded_path;
135
5
    }
136
6
  }
137

            
138
  // No path present, so add /
139
254
  if (new_path.empty()) {
140
1
    absl::StrAppend(&canonical_request, PATH_SPLITTER, "\n");
141
253
  } else {
142
    // Append path verbatim
143
253
    absl::StrAppend(&canonical_request, new_path, "\n");
144
253
  }
145

            
146
254
  const auto query_part = StringUtil::cropLeft(path, QUERY_SPLITTER);
147

            
148
  // If query_part == path_part, then we have no query string. Otherwise canonicalize it.
149
254
  if (query_part == path_part) {
150
125
    absl::StrAppend(&canonical_request, "\n");
151
181
  } else {
152
129
    absl::StrAppend(&canonical_request, canonicalizeQueryString(query_part), "\n");
153
129
  }
154

            
155
  // Add headers
156
254
  std::vector<std::string> formatted_headers;
157
254
  formatted_headers.reserve(canonical_headers.size());
158
733
  for (const auto& header : canonical_headers) {
159
727
    formatted_headers.emplace_back(fmt::format("{}:{}", header.first, header.second));
160
727
    absl::StrAppend(&canonical_request, formatted_headers.back(), "\n");
161
727
  }
162

            
163
  // Add blank space after the canonical headers
164
254
  absl::StrAppend(&canonical_request, "\n");
165

            
166
  // Add the list of signed headers
167
254
  absl::StrAppend(&canonical_request, joinCanonicalHeaderNames(canonical_headers), "\n");
168

            
169
  // Add the content hash
170
254
  absl::StrAppend(&canonical_request, content_hash);
171

            
172
254
  return canonical_request;
173
254
}
174

            
175
/**
176
 * Normalizes the path string based on AWS requirements.
177
 * See step 2 in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
178
 */
179
857606
std::string Utility::normalizePath(absl::string_view original_path) {
180

            
181
857606
  const auto path_segments = StringUtil::splitToken(original_path, std::string{PATH_SPLITTER});
182
857606
  std::vector<std::string> path_list;
183
857606
  path_list.reserve(path_segments.size());
184

            
185
  /* Loop through path segment.
186
   *
187
   * If segment is blank or . then skip it
188
   * If segment is .. then remove the previous path segment
189
   * Otherwise append the path segment as normal
190
   */
191
866453
  for (const auto& path_segment : path_segments) {
192
866325
    if (path_segment.empty() || path_segment == ".") {
193
199
      continue;
194
866126
    } else if (path_segment == "..") {
195
16
      if (path_list.empty()) {
196
3
        path_list.emplace_back(PATH_SPLITTER);
197
15
      } else {
198
13
        path_list.pop_back();
199
13
      }
200
866110
    } else {
201
866110
      path_list.emplace_back(path_segment);
202
866110
    }
203
866325
  }
204

            
205
857606
  auto canonical_path_string =
206
857606
      fmt::format("{}{}", PATH_SPLITTER, absl::StrJoin(path_list, PATH_SPLITTER));
207
  // Handle corner case when path ends with '/'
208
857606
  if (absl::EndsWith(original_path, PATH_SPLITTER) && canonical_path_string.size() > 1) {
209
9034
    canonical_path_string.push_back(PATH_SPLITTER[0]);
210
9034
  }
211
857606
  return canonical_path_string;
212
857606
}
213

            
214
7499985
bool isReservedChar(const char c) {
215
7499985
  return std::isalnum(c) || RESERVED_CHARS.find(c) != std::string::npos;
216
7499985
}
217

            
218
7499804
void Utility::encodeCharacter(unsigned char c, std::string& result) {
219
7499804
  if (isReservedChar(c)) {
220
5347471
    result.push_back(c);
221
5347471
  } else {
222
2152333
    absl::StrAppend(&result, fmt::format(URI_ENCODE, c));
223
2152333
  }
224
7499804
}
225

            
226
857628
std::string Utility::uriEncodePath(absl::string_view original_path) {
227

            
228
857628
  const absl::string_view::size_type query_start = original_path.find_first_of("?#");
229
857628
  const absl::string_view path = original_path.substr(0, query_start);
230
857628
  const absl::string_view query = absl::ClippedSubstr(original_path, query_start);
231

            
232
857628
  std::string encoded;
233

            
234
2466904
  for (unsigned char c : path) {
235
    // Do not encode slashes or unreserved chars from RFC 3986
236
2466904
    if (c == PATH_SPLITTER[0]) {
237
26836
      encoded.push_back(c);
238
2440096
    } else {
239
2440068
      encodeCharacter(c, encoded);
240
2440068
    }
241
2466904
  }
242

            
243
857628
  absl::StrAppend(&encoded, query);
244

            
245
857628
  return encoded;
246
857628
}
247

            
248
/**
249
 * Normalizes the query string based on AWS requirements.
250
 * See step 3 in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
251
 */
252
857511
std::string Utility::canonicalizeQueryString(absl::string_view query_string) {
253
  // Sort query string based on param name and append "=" if value is missing
254
857511
  const auto query_fragments = StringUtil::splitToken(query_string, QUERY_SEPERATOR);
255
857511
  std::vector<std::pair<std::string, std::string>> query_list;
256
866885
  for (const auto& query_fragment : query_fragments) {
257
    // Only split at the first "=" and encode the rest
258
866885
    const std::vector<std::string> query =
259
866885
        absl::StrSplit(query_fragment, absl::MaxSplits(QUERY_PARAM_SEPERATOR, 1));
260
866885
    if (!query.empty()) {
261
866885
      const absl::string_view param = query[0];
262
866885
      const absl::string_view value = query.size() > 1 ? query[1] : EMPTY_STRING;
263
866885
      query_list.emplace_back(std::make_pair(param, value));
264
866885
    }
265
866885
  }
266

            
267
  // Encode query params name and value separately
268
866885
  for (auto& query : query_list) {
269
866885
    if (query.first != SignatureQueryParameterValues::AmzSecurityToken) {
270
866875
      query.first = Utility::encodeQueryComponentPreservingPlus(query.first);
271
866875
      query.second = Utility::encodeQueryComponentPreservingPlus(query.second);
272
866875
    }
273
866885
  }
274

            
275
  // Sort query params by name and value after encoding
276
857511
  std::sort(query_list.begin(), query_list.end());
277

            
278
857511
  return absl::StrJoin(query_list, QUERY_SEPERATOR, absl::PairFormatter(QUERY_PARAM_SEPERATOR));
279
857511
}
280

            
281
// Encode query component while preserving original %2B semantics
282
// %2B stays as %2B, raw + becomes %20 (space)
283
2591353
std::string Utility::encodeQueryComponentPreservingPlus(absl::string_view original) {
284
2591353
  std::string result;
285

            
286
7705245
  for (size_t i = 0; i < original.size(); ++i) {
287
5113892
    if (i + 2 < original.size() && absl::EqualsIgnoreCase(original.substr(i, 3), "%2B")) {
288
      // %2B stays as %2B (preserve original encoding)
289
5
      absl::StrAppend(&result, "%2B");
290
5
      i += 2; // Skip the "2B" part
291
5113887
    } else if (original[i] == '+') {
292
      // Raw + becomes %20 (space)
293
54151
      absl::StrAppend(&result, "%20");
294
5059736
    } else if (original[i] == '%' && i + 2 < original.size()) {
295
18049
      std::string decoded_seq =
296
18049
          Envoy::Http::Utility::PercentEncoding::decode(original.substr(i, 3));
297
18049
      if (decoded_seq.size() == 1) {
298
        // Valid percent encoding - encode the decoded character
299
1343
        encodeCharacter(decoded_seq[0], result);
300
1343
        i += 2;
301
17084
      } else {
302
        // Invalid percent encoding - treat as regular character
303
16706
        encodeCharacter(original[i], result);
304
16706
      }
305
5041687
    } else {
306
      // Regular character
307
5041687
      encodeCharacter(original[i], result);
308
5041687
    }
309
5113892
  }
310
2591353
  return result;
311
2591353
}
312

            
313
std::string
314
425
Utility::joinCanonicalHeaderNames(const std::map<std::string, std::string>& canonical_headers) {
315
1236
  return absl::StrJoin(canonical_headers, ";", [](auto* out, const auto& pair) {
316
1231
    return absl::StrAppend(out, pair.first);
317
1231
  });
318
425
}
319

            
320
/**
321
 * This function generates an STS Endpoint from a region string.
322
 * If a SigV4A region set has been provided, it will use the first region in region set, and if
323
 * that region still contains a wildcard, the STS Endpoint will be set to us-east-1 global endpoint
324
 * (or FIPS if compiled for FIPS support)
325
 */
326
84
std::string Utility::getSTSEndpoint(absl::string_view region) {
327
84
  std::string single_region;
328
84
  const bool fips_mode = FIPS_mode();
329

            
330
  // If we contain a comma or asterisk it looks like a region set.
331
84
  if (absl::StrContains(region, ",") || (absl::StrContains(region, "*"))) {
332
    // Use the first element from a region set if we have multiple regions specified.
333
9
    const std::vector<std::string> region_v = absl::StrSplit(region, ',');
334
    // If we still have a * in the first element, then send them to us-east-1 fips or global
335
    // endpoint.
336
9
    if (absl::StrContains(region_v[0], '*')) {
337
7
      if (fips_mode) {
338
        return "sts-fips.us-east-1.amazonaws.com";
339
7
      } else {
340
7
        return "sts.amazonaws.com";
341
7
      }
342
7
    }
343
2
    single_region = region_v[0];
344
75
  } else {
345
    // Otherwise it's a standard region, so use that.
346
75
    single_region = region;
347
75
  }
348

            
349
77
  if (single_region == "cn-northwest-1" || single_region == "cn-north-1") {
350
2
    return fmt::format("sts.{}.amazonaws.com.cn", single_region);
351
2
  }
352
75
  if (fips_mode) {
353
    // Use AWS STS FIPS endpoints in FIPS mode
354
    // https://docs.aws.amazon.com/general/latest/gr/sts.html. Note: AWS GovCloud doesn't have
355
    // separate fips endpoints.
356
    // TODO(suniltheta): Include `ca-central-1` when sts supports a dedicated FIPS endpoint.
357
    if (single_region == "us-east-1" || single_region == "us-east-2" ||
358
        single_region == "us-west-1" || single_region == "us-west-2") {
359
      return fmt::format("sts-fips.{}.amazonaws.com", single_region);
360
    }
361
    ENVOY_LOG(
362
        warn,
363
        "FIPS Support is enabled, but an STS FIPS endpoint is not available in the configured "
364
        "region ({})",
365
        region);
366
  }
367
75
  return fmt::format("sts.{}.amazonaws.com", single_region);
368
75
}
369

            
370
/**
371
 * This function generates an RolesAnywhere Endpoint from a region string.
372
 */
373
5
std::string Utility::getRolesAnywhereEndpoint(const std::string& trust_anchor_arn) {
374
5
  std::string region;
375
5
  const std::vector<std::string> arn_split = absl::StrSplit(trust_anchor_arn, ':');
376
5
  if (arn_split.size() < 3) {
377
1
    region = "us-east-1";
378
4
  } else {
379
4
    region = arn_split[3];
380
4
  }
381

            
382
5
  if (FIPS_mode() == 1) {
383
    if (region == "us-east-1" || region == "us-east-2" || region == "us-west-1" ||
384
        region == "us-west-2" || region == "us-gov-east-1" || region == "us-gov-west-1") {
385
      return fmt::format("rolesanywhere-fips.{}.amazonaws.com", region);
386
    } else {
387
      ENVOY_LOG(
388
          warn,
389
          "FIPS Support is enabled, but a rolesanywhere FIPS endpoint is not available in the "
390
          "configured region ({})",
391
          region);
392
      return fmt::format("rolesanywhere.{}.amazonaws.com", region);
393
    }
394
5
  } else {
395
5
    return fmt::format("rolesanywhere.{}.amazonaws.com", region);
396
5
  }
397
5
}
398

            
399
envoy::config::cluster::v3::Cluster Utility::createInternalClusterStatic(
400
    absl::string_view cluster_name,
401
139
    const envoy::config::cluster::v3::Cluster::DiscoveryType cluster_type, absl::string_view uri) {
402
  // Check if local cluster exists with that name.
403

            
404
139
  envoy::config::cluster::v3::Cluster cluster;
405
139
  absl::string_view host_port;
406
139
  absl::string_view path;
407
139
  Http::Utility::extractHostPathFromUri(uri, host_port, path);
408
139
  const auto host_attributes = Http::Utility::parseAuthority(host_port);
409
139
  const auto host = host_attributes.host_;
410
139
  const auto port = host_attributes.port_ ? host_attributes.port_.value() : 80;
411

            
412
139
  cluster.set_name(cluster_name);
413
139
  cluster.set_type(cluster_type);
414
139
  cluster.mutable_connect_timeout()->set_seconds(5);
415
139
  cluster.mutable_load_assignment()->set_cluster_name(cluster_name);
416
139
  auto* endpoint =
417
139
      cluster.mutable_load_assignment()->add_endpoints()->add_lb_endpoints()->mutable_endpoint();
418
139
  auto* addr = endpoint->mutable_address();
419
139
  addr->mutable_socket_address()->set_address(host);
420
139
  addr->mutable_socket_address()->set_port_value(port);
421
139
  cluster.set_lb_policy(envoy::config::cluster::v3::Cluster::ROUND_ROBIN);
422
139
  envoy::extensions::upstreams::http::v3::HttpProtocolOptions protocol_options;
423
139
  auto* http_protocol_options =
424
139
      protocol_options.mutable_explicit_http_config()->mutable_http_protocol_options();
425
139
  http_protocol_options->set_accept_http_10(true);
426
139
  (*cluster.mutable_typed_extension_protocol_options())
427
139
      ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"]
428
139
          .PackFrom(protocol_options);
429

            
430
  // Add tls transport socket if cluster supports https over port 443.
431
139
  if (port == 443) {
432
49
    auto* socket = cluster.mutable_transport_socket();
433
49
    envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_socket;
434
49
    socket->set_name("envoy.transport_sockets.tls");
435
49
    socket->mutable_typed_config()->PackFrom(tls_socket);
436
49
  }
437

            
438
139
  return cluster;
439
139
}
440

            
441
std::string Utility::getEnvironmentVariableOrDefault(const std::string& variable_name,
442
150
                                                     const std::string& default_value) {
443
150
  const char* value = getenv(variable_name.c_str());
444
150
  return (value != nullptr) && (value[0] != '\0') ? value : default_value;
445
150
}
446

            
447
bool Utility::resolveProfileElementsFromString(
448
    const std::string& string_data, const std::string& profile_name,
449
16
    absl::flat_hash_map<std::string, std::string>& elements) {
450
16
  std::unique_ptr<std::istream> stream;
451

            
452
16
  stream = std::make_unique<std::istringstream>(std::istringstream{string_data});
453
16
  return resolveProfileElementsFromStream(*stream, profile_name, elements);
454
16
}
455

            
456
bool Utility::resolveProfileElementsFromFile(
457
    const std::string& profile_file, const std::string& profile_name,
458
30
    absl::flat_hash_map<std::string, std::string>& elements) {
459
30
  std::ifstream file(profile_file);
460
30
  if (!file.is_open()) {
461
11
    ENVOY_LOG(debug, "Error opening credentials file {}", profile_file);
462
11
    return false;
463
11
  }
464
19
  std::unique_ptr<std::istream> stream;
465
19
  stream = std::make_unique<std::ifstream>(std::move(file));
466
19
  return resolveProfileElementsFromStream(*stream, profile_name, elements);
467
30
}
468

            
469
bool Utility::resolveProfileElementsFromStream(
470
    std::istream& stream, const std::string& profile_name,
471
35
    absl::flat_hash_map<std::string, std::string>& elements) {
472

            
473
35
  const auto profile_start = absl::StrFormat("[%s]", profile_name);
474

            
475
35
  bool found_profile = false;
476
35
  std::string line;
477

            
478
427
  while (std::getline(stream, line)) {
479
411
    line = std::string(StringUtil::trim(line));
480
411
    if (line.empty()) {
481
97
      continue;
482
97
    }
483

            
484
314
    if (line == profile_start) {
485
33
      found_profile = true;
486
33
      continue;
487
33
    }
488

            
489
281
    if (found_profile) {
490
      // Stop reading once we find the start of the next profile.
491
115
      if (absl::StartsWith(line, "[")) {
492
19
        break;
493
19
      }
494

            
495
96
      std::vector<std::string> parts = absl::StrSplit(line, absl::MaxSplits('=', 1));
496
96
      if (parts.size() == 2) {
497

            
498
91
        const auto key = StringUtil::toUpper(StringUtil::trim(parts[0]));
499
91
        const auto val = StringUtil::trim(parts[1]);
500
91
        auto found = elements.find(key);
501
91
        if (found != elements.end()) {
502
55
          found->second = val;
503
55
        }
504
91
      }
505
96
    }
506
281
  }
507
35
  return true;
508
35
}
509

            
510
31
std::string Utility::getCredentialFilePath() {
511

            
512
  // Default credential file path plus current home directory. Will fall back to / if HOME
513
  // environment variable does
514
  // not exist
515

            
516
31
  const auto home = getEnvironmentVariableOrDefault("HOME", "");
517
31
  const auto default_credentials_file_path =
518
31
      absl::StrCat(home, DEFAULT_AWS_SHARED_CREDENTIALS_FILE);
519

            
520
31
  return getEnvironmentVariableOrDefault(AWS_SHARED_CREDENTIALS_FILE,
521
31
                                         default_credentials_file_path);
522
31
}
523

            
524
12
std::string Utility::getConfigFilePath() {
525

            
526
  // Default config file path plus current home directory. Will fall back to / if HOME environment
527
  // variable does
528
  // not exist
529

            
530
12
  const auto home = Utility::getEnvironmentVariableOrDefault("HOME", "");
531
12
  const auto default_credentials_file_path = absl::StrCat(home, DEFAULT_AWS_CONFIG_FILE);
532

            
533
12
  return getEnvironmentVariableOrDefault(AWS_CONFIG_FILE, default_credentials_file_path);
534
12
}
535

            
536
32
std::string Utility::getCredentialProfileName() {
537
32
  return getEnvironmentVariableOrDefault(AWS_PROFILE, DEFAULT_AWS_PROFILE);
538
32
}
539

            
540
12
std::string Utility::getConfigProfileName() {
541
12
  auto profile_name = getEnvironmentVariableOrDefault(AWS_PROFILE, DEFAULT_AWS_PROFILE);
542
12
  if (profile_name == DEFAULT_AWS_PROFILE) {
543
10
    return profile_name;
544
10
  } else {
545
2
    return "profile " + profile_name;
546
2
  }
547
12
}
548

            
549
std::string Utility::getStringFromJsonOrDefault(Json::ObjectSharedPtr json_object,
550
                                                const std::string& string_value,
551
111
                                                const std::string& string_default) {
552
111
  absl::StatusOr<Envoy::Json::ValueType> value_or_error;
553
111
  value_or_error = json_object->getValue(string_value);
554
111
  if ((!value_or_error.ok()) || (!absl::holds_alternative<std::string>(value_or_error.value()))) {
555

            
556
8
    ENVOY_LOG(error, "Unable to retrieve string value from json: {}", string_value);
557
8
    return string_default;
558
8
  }
559
103
  return absl::get<std::string>(value_or_error.value());
560
111
}
561

            
562
int64_t Utility::getIntegerFromJsonOrDefault(Json::ObjectSharedPtr json_object,
563
                                             const std::string& integer_value,
564
25
                                             const int64_t integer_default) {
565
25
  absl::StatusOr<Envoy::Json::ValueType> value_or_error;
566
25
  value_or_error = json_object->getValue(integer_value);
567
25
  if (!value_or_error.ok() || ((!absl::holds_alternative<double>(value_or_error.value())) &&
568
21
                               (!absl::holds_alternative<int64_t>(value_or_error.value())))) {
569
12
    ENVOY_LOG(error, "Unable to retrieve integer value from json: {}", integer_value);
570
12
    return integer_default;
571
12
  }
572
13
  auto json_integer = value_or_error.value();
573
  // Handle double formatted integers IE exponent format such as 1.714449238E9
574
13
  if (auto* double_integer = absl::get_if<double>(&json_integer)) {
575
6
    if (*double_integer < 0) {
576
1
      ENVOY_LOG(error, "Integer {} less than 0: {}", integer_value, *double_integer);
577
1
      return integer_default;
578
5
    } else {
579
5
      return int64_t(*double_integer);
580
5
    }
581
8
  } else {
582
    // Standard integer
583
7
    return absl::get<int64_t>(json_integer);
584
7
  }
585
13
}
586

            
587
213
bool Utility::useDoubleUriEncode(const std::string service_name) {
588
  // These services require unmodified (not normalized) URL paths
589
  // https://github.com/boto/botocore/blob/66d047b5cdb033e4406e306afc5ab1c3e4785f16/botocore/data/s3control/2018-08-20/endpoint-rule-set-1.json#L368
590
  // https://github.com/boto/botocore/blob/66d047b5cdb033e4406e306afc5ab1c3e4785f16/botocore/data/s3/2006-03-01/endpoint-rule-set-1.json#L4347
591
  // https://github.com/boto/botocore/blob/66d047b5cdb033e4406e306afc5ab1c3e4785f16/botocore/data/s3/2006-03-01/endpoint-rule-set-1.json#L390
592

            
593
213
  constexpr absl::string_view S3_SERVICE_NAME = "s3";
594
213
  constexpr absl::string_view S3_OUTPOSTS_SERVICE_NAME = "s3-outposts";
595
213
  constexpr absl::string_view S3_EXPRESS_SERVICE_NAME = "s3-express";
596

            
597
213
  const std::vector<absl::string_view> do_not_normalize = {
598
213
      S3_SERVICE_NAME, S3_OUTPOSTS_SERVICE_NAME, S3_EXPRESS_SERVICE_NAME};
599

            
600
620
  for (auto& it : do_not_normalize) {
601
620
    if (absl::EqualsIgnoreCase(service_name, it)) {
602
11
      return false;
603
11
    }
604
620
  }
605

            
606
202
  return true;
607
213
}
608

            
609
// Even though these are two separate flags in the AWS SDKs, they are used in conjunction with each
610
// other
611
109
bool Utility::shouldNormalizeUriPath(const std::string service) {
612
109
  return Utility::useDoubleUriEncode(service);
613
109
}
614

            
615
15
bool Utility::isUriPathEncoded(absl::string_view path) {
616
181
  for (char ch : path) {
617
181
    if (!isReservedChar(ch) && ch != '%' && ch != '/') {
618
4
      return false;
619
4
    }
620
181
  }
621
11
  return true;
622
15
}
623

            
624
} // namespace Aws
625
} // namespace Common
626
} // namespace Extensions
627
} // namespace Envoy