-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Add AWS http request signer #5580
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
Conversation
Signed-off-by: Scott LaVigne <lavignes@amazon.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great in general, some comments on structure, implementation and Envoy style. This is definitely heading in the right direction, thanks for splitting up the PRs!
envoy_package() | ||
|
||
envoy_cc_library( | ||
name = "signer_lib", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: if this is going to only ever be a pure interface, signer_interface
would be more consistent with other conventions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know. I thought that convention was only used the public headers. e.g. include/envoy
.
|
||
const absl::optional<std::string>& secretAccessKey() const { return secret_access_key_; } | ||
|
||
void setSessionToken(const std::string& session_token) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need both setters and constructors for these fields? Could this be an immutable object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need them. I can remove them.
|
||
~Credentials() = default; | ||
|
||
Credentials(const std::string& access_key_id, const std::string& secret_access_key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we tend to use absl::string_view
rather than const std::string&
in new code, as it's strictly more general. You can still turn this into a string copy below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy will suggest pass by value here and std::move below for constructors like this.
https://clang.llvm.org/extra/clang-tidy/checks/modernize-pass-by-value.html
By doing this you may save one copy if caller does move too.
public: | ||
Credentials() = default; | ||
|
||
~Credentials() = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: probably don't need this if we're not doing polymorphic inheritance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the destructor, but I do use the no-args constructor.
namespace Common { | ||
namespace Aws { | ||
|
||
class RegionProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a look at how this works in #5546. This allows both static and environment to specify region. I guess my main question is "do we really need this?". Specifically, Envoy's Node
ID includes locality information (and a variety of ways to specify, from bootstrap config to CLI). Can we pull region from that in the interest of economy of configuration mechanism and making this more consistent with the rest of Envoy config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was to eventually add a region provider impl that pulls the region from an AWS-endpoint, much like we pull the credentials.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the larger point is "shouldn't we be synthesizing the Node locality in the bootstrap or CLI for Envoys based on the AWS endpoint data source?" If you do that, there is no need to manage data sources for this information in the extension.
Credentials can also be placed in Node metadata, but the limitation there is that if they change dynamically during the lifetime of the Envoy, they can become stale. For region, this should be static for the lifetime of an Envoy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not addressing that larger point. I think it makes sense. I'll pipe the node metadata through in the later PR. I'll eliminate this class, especially since it will probably just provide static data for the foreseeable future.
// Path | ||
const auto* path_header = headers.Path(); | ||
if (path_header == nullptr || path_header->value().empty()) { | ||
throw EnvoyException("Message is missing :path header"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think either the method or path should be missing by construction, so you can probably omit these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually there doesn't seem to be anything preventing someone from not setting the path or method. Due to the way I plan to use this in the gRPC credentials plugin, I'd like to ensure these are set instead of seg-faulting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was thinking on the request receive path, but here you are creating a new request.
} | ||
out << "\n" << signing_headers << "\n"; | ||
// Content Hash | ||
out << content_hash; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be slightly more readable to use fmt::format
here instead of streams, but up to you if you prefer streams.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, IIRC fmt::format
is much faster than streams, if this is potentially being used in data path, format is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is mostly just "\n"
joining a bunch of strings. Though it is clearly not evident :). I'm updating it to use absl::StrJoin
instead.
} | ||
|
||
std::map<std::string, std::string> | ||
SignerImpl::canonicalizeHeaders(const Http::HeaderMap& headers) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic in here is quite arcane; do you think we could split this out of a private method into a standalone utility and write some extensive tests for this? It seems a possible source of compliance bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. It is one of the trickier parts of the code. And even the public documentation of the canonicalization process glosses over some details.
RELEASE_ASSERT(len <= EVP_MAX_MD_SIZE, "Hmac length too large"); | ||
HMAC_CTX_cleanup(&ctx); | ||
mac.resize(len); | ||
return mac; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think hmac and hash are general enough to put in source/common/ssl/utility.cc
or somewhere like that, this seems like something other folks might want to reuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats a good idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message_->headers().Authorization()->value().c_str()); | ||
} | ||
|
||
TEST_F(SignerImplTest, ContentHeader) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add short //
explanations of what each of these tests does above each TEST_F
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
|
||
#include "extensions/filters/http/common/aws/credentials_provider.h" | ||
#include "extensions/filters/http/common/aws/region_provider.h" | ||
#include "extensions/filters/http/common/aws/signer.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to have an abstract signing API in include/envoy somewhere, and have AwsSigner implement that as an extension?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense if we want a generic request signing extension. If @lavignes wants to take that on in this PR series, that's fine, otherwise we could leave a TODO and do the refactor when we have a second signing extension that wants to do something similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opted for the TODO. It'd be trivial to expose it in a public header in the future.
* Use absl::string_view more aggresively * Removing Aws::RegionProvider * Move SHA-256 Digest/HMAC utils to common/ssl/utility * Removed setters from Aws::Credentials * Remove usage of stringstream * Use ConstSingleton for string constants * Add test comments Signed-off-by: Scott LaVigne <lavignes@amazon.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great, just some minor stuff and would like @PiotrSikora to take a quick pass on the BoringSSL side.
// Path | ||
const auto* path_header = headers.Path(); | ||
if (path_header == nullptr || path_header->value().empty()) { | ||
throw EnvoyException("Message is missing :path header"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was thinking on the request receive path, but here you are creating a new request.
source/common/ssl/utility.cc
Outdated
@@ -101,5 +105,43 @@ SystemTime Utility::getExpirationTime(const X509& cert) { | |||
return std::chrono::system_clock::from_time_t(days * 24 * 60 * 60 + seconds); | |||
} | |||
|
|||
std::vector<uint8_t> Utility::getSha256Digest(const Buffer::Instance& buffer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PiotrSikora can you do a review pass on these two functions and their tests from a BoringSSL perspective? Thanks.
source/common/ssl/utility.cc
Outdated
unsigned int len; | ||
rc = HMAC_Final(&ctx, mac.data(), &len); | ||
RELEASE_ASSERT(rc == 1, "Failed to finalize HMAC"); | ||
RELEASE_ASSERT(len <= EVP_MAX_MD_SIZE, "HMAC length too large"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this best an ASSERT
? I.e. it can never happen? Same with the other length check above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed the conventions of the other parts of envoy that used these types of SSL apis. I think these, for all intents and purposes, don't happen.
parts.emplace_back(signing_headers); | ||
parts.emplace_back(content_hash); | ||
return absl::StrJoin(parts, "\n"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one might also belong in the utility like canonical headers creation, it has some edge cases that would benefit from unit tests unless already well covered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do have tests in the signer_tests specifically for this function but I can pull it out into a utility. I'm not sure what the convention is on util creation in envoy. Is it for any one function that is sufficiently complex on its own? Or is it just about reuse? I don't really see the request or header canonicalization being used outside of signing AWS requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's kind of up to you largely, as long as you can provide adequate test coverage. The philosophy is at a bare minimum we have code coverage based on lines, but for complex/tricky code, it's often useful to ensure we can do behavioral/branch/functional coverage via unit tests. Some folks use testing peers, others code structure to do this. I personally don't like moving private to public methods in a class just for test, but I do like adding a logically separate utility class that has the methods as public where it makes sense.
DateFormatter long_date_formatter_; | ||
DateFormatter short_date_formatter_; | ||
|
||
std::string createContentHash(Http::Message& message) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: generally we prefer methods before data members in each access scoped section, e.g. private here.
class Utility { | ||
public: | ||
/** | ||
* Creates a canonicalized header map according to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe explain why we are doing this; it's just for signing, right? We never put these on the wire..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole canonicalization process is at the end of the day about consistently hashing HTTP requests. We do the exact same canonicalization upstream as part of our AuthN. This map is really just part of the input string to the hash we put in the Authorization header.
I'll explicitly say this is to be used as an input to the request canonicalization function. (Which I'll also move into this util)
[](const Http::HeaderEntry& entry, void* context) -> Http::HeaderMap::Iterate { | ||
auto* map = static_cast<std::map<std::string, std::string>*>(context); | ||
// Pseudo-headers should not be canonicalized | ||
if (entry.key().c_str()[0] == ':') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe an empty header in there somehow as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
} | ||
return Http::HeaderMap::Iterate::Continue; | ||
}, | ||
&out); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a feeling this is more generally useful (without the quirks below), but it's fine to leave here, just thinking out loud.
} else { | ||
out.emplace(Http::Headers::get().HostLegacy.get(), std::string(value)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to completely trust you on the business logic here, code looks good.
source/common/common/logger.h
Outdated
@@ -22,6 +22,7 @@ namespace Logger { | |||
// clang-format off | |||
#define ALL_LOGGER_IDS(FUNCTION) \ | |||
FUNCTION(admin) \ | |||
FUNCTION(aws) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason not to use http
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given where the code is now, it could just be classified as http. I'm fine doing that. :)
namespace Common { | ||
namespace Aws { | ||
|
||
void SignerImpl::sign(Http::Message& message) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole function assumes that the complete request body is available at this point. I'm not sure how are you going to use this, but Envoy doesn't buffer requests before forwarding them, so this won't work for proxied requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good point. In the sigv4 algorithm, the request body signing is technically optional (for this very reason), but adds an extra layer of security for certain types of requests. I'll add a parameter, much like we do in the official SDK to control signing the body. The caveat being, that the body must be fully available at that point.
source/common/ssl/utility.cc
Outdated
unsigned int digest_length; | ||
rc = EVP_DigestFinal(&ctx, digest.data(), &digest_length); | ||
RELEASE_ASSERT(rc == 1, "Failed to finalize digest"); | ||
RELEASE_ASSERT(digest_length == SHA256_DIGEST_LENGTH, "Digest length mismatch"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we check this in a few other places, but it turns out this isn't necessary.
source/common/ssl/utility.cc
Outdated
} | ||
|
||
std::vector<uint8_t> Utility::getSha256Hmac(const std::vector<uint8_t>& key, | ||
absl::string_view string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: string
isn't very descriptive. Could you use message
or data
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no problem
source/common/ssl/utility.cc
Outdated
} | ||
|
||
std::vector<uint8_t> Utility::getSha256Hmac(const std::vector<uint8_t>& key, | ||
absl::string_view string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you can replace this whole function with:
std::vector<uint8_t> hmac(SHA256_DIGEST_LENGTH);
unsigned int len;
rc = HMAC(EVP_sha256(), key.data(), key.size(), message.data(), message.size(), hmac.data(), &len);
RELEASE_ASSERT(rc != nullptr, "Failed to create HMAC");
return hmac;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure thing
source/common/ssl/utility.cc
Outdated
@@ -1,8 +1,12 @@ | |||
#include "common/ssl/utility.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: This file doesn't exist anymore. @bdecoste moved TLS socket to extensions, so that it can be compiled out (and another TLS library can be used in it's place), it's new home is in source/extensions/transport_sockets/tls
.
Having said that, I'd prefer if you created another target for this (e.g. source/extensions/common/crypto
?), since those functions have nothing to do with SSL/TLS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was actually thinking about making a "crypto" util but just ran with the suggestion to put it in common/ssl/utillty. Easy change :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be in core actually, since even core code might want to compute HMAC and SHAs. How about source/common/crypto
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I don't leverage any code that is different between Open/BoringSSL, but it would require some compile-time plumbing, which is not as clean as swapping in/out an extension, if neither lib is used or some other crypto impl was used instead.
How about I move this to source/common/crypto
for now. And make an issue to propose adding a crypto impl extension api?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that sounds good. Would like to merge this PR soon, so whatever is reasonable here and we can then look at the next one, thanks.
* Remove Logging::Id::aws * Move crypto utils to Extensions::Common::Crypto * Make request body signing optional * Move request canonicalization method to Aws::Utility * Explicity check for empty headers when canonicalizing Signed-off-by: Scott LaVigne <lavignes@amazon.com>
Signed-off-by: Scott LaVigne <lavignes@amazon.com>
absl::string_view message) { | ||
std::vector<uint8_t> hmac(SHA256_DIGEST_LENGTH); | ||
unsigned int len; | ||
auto ret = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: const
parts.emplace_back(signing_headers); | ||
parts.emplace_back(content_hash); | ||
return absl::StrJoin(parts, "\n"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's kind of up to you largely, as long as you can provide adequate test coverage. The philosophy is at a bare minimum we have code coverage based on lines, but for complex/tricky code, it's often useful to ensure we can do behavioral/branch/functional coverage via unit tests. Some folks use testing peers, others code structure to do this. I personally don't like moving private to public methods in a class just for test, but I do like adding a logically separate utility class that has the methods as public where it makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo remaining comments, thanks!
Signed-off-by: Scott LaVigne <lavignes@amazon.com>
LGTM, @PiotrSikora WDYT? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, sans the typo.
test/common/crypto/utility_test.cc
Outdated
namespace Common { | ||
namespace Crypto { | ||
|
||
TEST(UtilityTest, TestSha256Disgest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: s/Disgest/Digest/g
(here and in the 2 tests below).
* github/master: (78 commits) transport capture: rename to tap (#5666) Misc additions for stubbing out QUICHE platform impls. (#5684) router: fix http/2 shadow request with body. (#5674) jwt_authn: cleanup, separate verifier from the matcher (#5689) redis: add latency stats for commands (#5593) config: invoke initialization callbacks on remote close (#5671) ext_authz: v2Alpha migration and documentation improvements (#5672) Update Datadog tracer version to v0.4.1 (#5691) envoy: basic spellcheck (#5688) fix a google string vs std::string issue for easier import (#5685) tools: tagging deprecation issues as no stalebot (#5680) health filter: cache degraded health checks (#5659) upstream: fix degraded health check and thread posting (#5662) tracing: inject tracing context in router (#5661) mysql_utils.cc: access EnvoyException by reference. (#5669) tap: introduce HTTP tap filter (#5515) fix a comment error (#5664) docs: add relase note for degraded health (#5653) Add yes flag for gcc-7 apt-repository (#5657) test: fix ssl_integration_test flake. (#5650) ... Signed-off-by: Scott LaVigne <lavignes@amazon.com>
Signed-off-by: Scott LaVigne <lavignes@amazon.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
As part of leveraging Envoy in AWS App Mesh, we implemented a gRPC credentials extension that uses AWS IAM auth. To be able to use authenticate gRPC requests with AWS IAM credentials, we needed to add a code to Envoy to sign HTTP requests. More high-level details in envoyproxy#5215. I opened up a prior PR: envoyproxy#5546 which was admittedly too large and unwieldy. This is part 1 of a series of smaller patches needed to add our gRPC authentication extension to Envoy. Risk Level: Low Testing: Unit tests for this API. This has also been verified to work when communicating with App Mesh. Signed-off-by: Scott LaVigne <lavignes@amazon.com> Signed-off-by: Dan Zhang <danzh@google.com>
As part of leveraging Envoy in AWS App Mesh, we implemented a gRPC credentials extension that uses AWS IAM auth. To be able to use authenticate gRPC requests with AWS IAM credentials, we needed to add a code to Envoy to sign HTTP requests. More high-level details in envoyproxy#5215. I opened up a prior PR: envoyproxy#5546 which was admittedly too large and unwieldy. This is part 1 of a series of smaller patches needed to add our gRPC authentication extension to Envoy. Risk Level: Low Testing: Unit tests for this API. This has also been verified to work when communicating with App Mesh. Signed-off-by: Scott LaVigne <lavignes@amazon.com> Signed-off-by: Scott LaVigne <1406859+lavignes@users.noreply.github.com>
As part of leveraging Envoy in AWS App Mesh, we implemented a gRPC credentials extension that uses AWS IAM auth. To be able to use authenticate gRPC requests with AWS IAM credentials, we needed to add a code to Envoy to sign HTTP requests. More high-level details in envoyproxy#5215. I opened up a prior PR: envoyproxy#5546 which was admittedly too large and unwieldy. This is part 1 of a series of smaller patches needed to add our gRPC authentication extension to Envoy. Risk Level: Low Testing: Unit tests for this API. This has also been verified to work when communicating with App Mesh. Signed-off-by: Scott LaVigne <lavignes@amazon.com> Signed-off-by: Fred Douglas <fredlas@google.com>
Description: As part of leveraging Envoy in AWS App Mesh, we implemented a gRPC credentials extension that uses AWS IAM auth. To be able to use authenticate gRPC requests with AWS IAM credentials, we needed to add a code to Envoy to sign HTTP requests. More high-level details in #5215.
I opened up a prior PR: #5546 which was admittedly too large and unwieldy. This is part 1 of a series of smaller patches needed to add our gRPC authentication extension to Envoy.
Risk Level: Low
Testing: Unit tests for this API. This has also been verified to work when communicating with App Mesh.
Docs Changes: N/A
Release Notes: N/A