Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
acc3696
aws_request_signing_filter hash payload by default
jstewmon Apr 6, 2021
267c7f3
document aws_request_signing buffering behavior change
jstewmon Apr 7, 2021
0685707
get aws_request_signing unsigned_payload option from config
jstewmon Apr 7, 2021
72e4b82
apply formatting
jstewmon Apr 7, 2021
0ab2d44
update aws mocks with new sign signature
jstewmon Apr 7, 2021
59f1a03
aws filter tests compat with overloaded sign method
jstewmon Apr 7, 2021
07fd41e
test aws request signing from either decodeHeaders or decodeData
jstewmon Apr 8, 2021
e062333
address PR feedback
jstewmon Apr 8, 2021
c33e053
address PR feedback
jstewmon Apr 8, 2021
1d0039b
change sha comments to appease spell check
jstewmon Apr 9, 2021
1543a00
Merge branch 'main' into aws-request-signing-payload-hash
jstewmon Apr 16, 2021
6fe9ced
fix version history docs after merging main
jstewmon Apr 16, 2021
04d81aa
try using Matcher to disambiguate sign call
jstewmon Apr 16, 2021
0b32662
use Matcher<bool> in aws request signing tests
jstewmon Apr 16, 2021
7912133
Merge branch 'main' into aws-request-signing-payload-hash
jstewmon Apr 21, 2021
dc2303d
document new aws request signing filter stats
jstewmon Apr 22, 2021
054f829
prefer discretely named methods in Signer interface
jstewmon Apr 23, 2021
1186e2d
remove unused using decl Matcher in aws filter tests
jstewmon Apr 27, 2021
c8c83e6
Merge branch 'main' into aws-request-signing-payload-hash
jstewmon May 7, 2021
7a964a5
fix version history docs after merging main
jstewmon May 7, 2021
12e1e0c
Merge branch 'main' into aws-request-signing-payload-hash
jstewmon May 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ message AwsRequestSigning {
// value set here would be used for signing whereas the value set in the HCM would be used
// for host header forwarding which is not the desired outcome.
string host_rewrite = 3;

// Instead of buffering the request to calculate the payload hash, use the literal string ``UNSIGNED-PAYLOAD``
// to calculate the payload hash. Not all services support this option. See the `S3
// <https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html>`_ policy for details.
bool use_unsigned_payload = 4;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ The HTTP AWS request signing filter is used to access authenticated AWS services
existing AWS Credential Provider to get the secrets used for generating the required
headers.


The :ref:`use_unsigned_payload <envoy_v3_api_field_extensions.filters.http.aws_request_signing.v3.AwsRequestSigning.use_unsigned_payload>`
option determines whether or not requests are buffered so the request body can be used to compute the payload hash. Some
services, such as S3, allow requests with unsigned payloads. Consult the AWS documentation and your service's resource
policies to determine if this option is appropriate.

When :ref:`use_unsigned_payload <envoy_v3_api_field_extensions.filters.http.aws_request_signing.v3.AwsRequestSigning.use_unsigned_payload>`
is false (the default), requests which exceed the configured buffer limit will receive a 413 response. See the
ref:`flow control docs <faq_flow_control>` for details.

Example configuration
---------------------

Expand All @@ -27,6 +37,7 @@ Example filter configuration:
"@type": type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning
service_name: s3
region: us-west-2
use_unsigned_payload: true


Statistics
Expand All @@ -40,5 +51,7 @@ comes from the owning HTTP connection manager.
:header: Name, Type, Description
:widths: 1, 1, 2

signing_added, Counter, Total authentication headers added to requests
signing_failed, Counter, Total requests for which a signature was not added
signing_added, Counter, Total requests for which signing succeeded (includes payload_signing_added)
signing_failed, Counter, Total requests for which signing failed (includes payload_signing_failed)
payload_signing_added, Counter, Total requests for which the payload was buffered signing succeeded
payload_signing_failed, Counter, Total requests for which the payload was buffered but signing failed
6 changes: 6 additions & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Minor Behavior Changes
----------------------
*Changes that may cause incompatibilities for some users, but should not for most*

* aws_request_signing: requests are now buffered by default to compute signatures which include the
payload hash, making the filter compatible with most AWS services. Previously, requests were
never buffered, which only produced correct signatures for requests without a body, or for
requests to S3, ES or Glacier, which used the literal string ``UNSIGNED-PAYLOAD``. Buffering can
be now be disabled in favor of using unsigned payloads with compatible services via the new
`use_unsigned_payload` filter option (default false).
* http: replaced setting `envoy.reloadable_features.strict_1xx_and_204_response_headers` with settings
`envoy.reloadable_features.require_strict_1xx_and_204_response_headers`
(require upstream 1xx or 204 responses to not have Transfer-Encoding or non-zero Content-Length headers) and
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions source/extensions/common/aws/signer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ class Signer {
virtual void sign(Http::RequestMessage& message, bool sign_body) PURE;

/**
* Sign an AWS request.
* Sign an AWS request without a payload (empty string used as content hash).
* @param headers AWS API request headers.
* @throws EnvoyException if the request cannot be signed.
*/
virtual void signEmptyPayload(Http::RequestHeaderMap& headers) PURE;

/**
* Sign an AWS request using the literal string UNSIGNED-PAYLOAD in the canonical request.
* @param headers AWS API request headers.
* @throws EnvoyException if the request cannot be signed.
*/
virtual void sign(Http::RequestHeaderMap& headers) PURE;
virtual void signUnsignedPayload(Http::RequestHeaderMap& headers) PURE;

/**
* Sign an AWS request.
Expand Down
20 changes: 10 additions & 10 deletions source/extensions/common/aws/signer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ void SignerImpl::sign(Http::RequestMessage& message, bool sign_body) {
sign(headers, content_hash);
}

void SignerImpl::sign(Http::RequestHeaderMap& headers) {
if (require_content_hash_) {
headers.setReference(SignatureHeaders::get().ContentSha256,
SignatureConstants::get().UnsignedPayload);
sign(headers, SignatureConstants::get().UnsignedPayload);
} else {
headers.setReference(SignatureHeaders::get().ContentSha256,
SignatureConstants::get().HashedEmptyString);
sign(headers, SignatureConstants::get().HashedEmptyString);
}
void SignerImpl::signEmptyPayload(Http::RequestHeaderMap& headers) {
headers.setReference(SignatureHeaders::get().ContentSha256,
SignatureConstants::get().HashedEmptyString);
sign(headers, SignatureConstants::get().HashedEmptyString);
}

void SignerImpl::signUnsignedPayload(Http::RequestHeaderMap& headers) {
headers.setReference(SignatureHeaders::get().ContentSha256,
SignatureConstants::get().UnsignedPayload);
sign(headers, SignatureConstants::get().UnsignedPayload);
}

void SignerImpl::sign(Http::RequestHeaderMap& headers, const std::string& content_hash) {
Expand Down
22 changes: 5 additions & 17 deletions source/extensions/common/aws/signer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,14 @@ class SignerImpl : public Signer, public Logger::Loggable<Logger::Id::http> {
public:
SignerImpl(absl::string_view service_name, absl::string_view region,
const CredentialsProviderSharedPtr& credentials_provider, TimeSource& time_source)
: service_name_(service_name), region_(region),

// S3, Glacier, ES payloads require special treatment.
// S3:
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html.
// ES:
// https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html.
// Glacier:
// https://docs.aws.amazon.com/amazonglacier/latest/dev/amazon-glacier-signing-requests.html.
require_content_hash_{service_name_ == "s3" || service_name_ == "glacier" ||
service_name_ == "es"},
credentials_provider_(credentials_provider), time_source_(time_source),
long_date_formatter_(SignatureConstants::get().LongDateFormat),
: service_name_(service_name), region_(region), credentials_provider_(credentials_provider),
time_source_(time_source), long_date_formatter_(SignatureConstants::get().LongDateFormat),
short_date_formatter_(SignatureConstants::get().ShortDateFormat) {}

void sign(Http::RequestMessage& message, bool sign_body = false) override;
void sign(Http::RequestHeaderMap& headers) override;
void sign(Http::RequestHeaderMap& headers, const std::string& content_hash) override;
void signEmptyPayload(Http::RequestHeaderMap& headers) override;
void signUnsignedPayload(Http::RequestHeaderMap& headers) override;

private:
std::string createContentHash(Http::RequestMessage& message, bool sign_body) const;
Expand All @@ -81,12 +72,9 @@ class SignerImpl : public Signer, public Logger::Loggable<Logger::Id::http> {
const std::map<std::string, std::string>& canonical_headers,
absl::string_view signature) const;

void sign(Http::RequestHeaderMap& headers, const std::string& content_hash) override;

const std::string service_name_;
const std::string region_;

const bool require_content_hash_;
CredentialsProviderSharedPtr credentials_provider_;
TimeSource& time_source_;
DateFormatter long_date_formatter_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers,

if (payload_passthrough_) {
setLambdaHeaders(headers, arn_->functionName(), invocation_mode_);
sigv4_signer_->sign(headers);
sigv4_signer_->signEmptyPayload(headers);
return Http::FilterHeadersStatus::Continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

#include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h"

#include "common/common/hex.h"
#include "common/crypto/utility.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace AwsRequestSigningFilter {

FilterConfigImpl::FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer,
const std::string& stats_prefix, Stats::Scope& scope,
const std::string& host_rewrite)
const std::string& host_rewrite, bool use_unsigned_payload)
: signer_(std::move(signer)), stats_(Filter::generateStats(stats_prefix, scope)),
host_rewrite_(host_rewrite) {}
host_rewrite_(host_rewrite), use_unsigned_payload_{use_unsigned_payload} {}

Filter::Filter(const std::shared_ptr<FilterConfig>& config) : config_(config) {}

Expand All @@ -20,29 +23,76 @@ Extensions::Common::Aws::Signer& FilterConfigImpl::signer() { return *signer_; }
FilterStats& FilterConfigImpl::stats() { return stats_; }

const std::string& FilterConfigImpl::hostRewrite() const { return host_rewrite_; }
bool FilterConfigImpl::useUnsignedPayload() const { return use_unsigned_payload_; }

FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope) {
const std::string final_prefix = prefix + "aws_request_signing.";
return {ALL_AWS_REQUEST_SIGNING_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))};
}

Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool) {
Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) {
const auto& host_rewrite = config_->hostRewrite();
const bool use_unsigned_payload = config_->useUnsignedPayload();

if (!host_rewrite.empty()) {
headers.setHost(host_rewrite);
}

if (!use_unsigned_payload && !end_stream) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the default behavior of the filter need to change to buffer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I touched on this in the original commit message:

canonical must include the hashed payload for most services. The prior
behavior of using UNSIGNED-PAYLOAD is an exception to the rule, which
select services like s3 support, since hashing the payload may be
impractical if the payload is very large.

The existing behavior of the filter is that the signature is based on '' unless the service name is s3, es, or glacier in which case the literal UNSIGNED-PAYLOAD is used. This behavior is not configurable, so I think we should frame the rest of the discussion as whether the existing behavior should be the default going forward. I argue that the default behavior should not be the existing behavior...

The current behavior is incompatible with nearly all AWS services. I can't find any evidence that the changes made in #12020 actually made any cases change from failing to passing, since it only added es and glacier to the list of services which use UNSIGNED-PAYLOAD to calculate the content hash, and neither of those services document that they support that signature variant.

In the version history I worded the notes giving the benefit of doubt to what the code expressed, despite not believing this to be true:

Previously, requests were never buffered, which only produced correct signatures for requests without a body, or for requests to S3, ES or Glacier, which used the literal string UNSIGNED-PAYLOAD

We can actually search the AWS SDK models to see which services can use UNSIGNED-PAYLOAD:

find aws-sdk-cpp/code-generation/api-descriptions -name '*.json' -exec grep -lF v4-unsigned-body "{}" \+
aws-sdk-cpp/code-generation/api-descriptions/runtime.lex.v2-2020-08-07.normal.json
aws-sdk-cpp/code-generation/api-descriptions/runtime.lex-2016-11-28.normal.json
aws-sdk-cpp/code-generation/api-descriptions/s3-2006-03-01.normal.json
aws-sdk-cpp/code-generation/api-descriptions/ebs-2019-11-02.normal.json
aws-sdk-cpp/code-generation/api-descriptions/mediastore-data-2017-09-01.normal.json

Of those 4, only s3 is included in the filter's list to use UNSIGNED-PAYLOAD, so the other 3 are in the same boat as all other services - the filter is only compatible with requests without a body.

I've used the new stats to verify that the new behavior still signs requests without a body in decodeHeaders. So, the only type of requests which are known to have worked with the existing behavior and would be treated differently by the new default behavior are requests to S3 with a body (uploads).

For requests to S3 with a body, the new filter will buffer and sign the payload by default. The consequence of that change depends on the size of uploads being handled and the buffer configuration - impact could range from negligible, to higher memory usage, to 413 responses being returned. Of course, the old behavior is available for this use case via the new use_unsigned_payload config setting.

I did consider retaining the old behavior for s3, but the filter needs to support signing payloads for s3 because s3 bucket policies can be used to deny requests with unsigned payloads. If the default s3 behavior is to use unsigned payloads, there would need to be a separate config setting like sign_s3_payloads, which would greatly increase the complexity of evaluating the filter config.

It is impossible to know how prevalently the filter may be used to facilitate uploads to s3 and whether the buffer limit would affect those usages. But, I argue that this one case cannot be assumed to be significant enough to justify the default filter behavior being incompatible with all other AWS services; nor could it justify the more confusing config interface and more complex config evaluation. The version history notes clearly state the change and provide instructions on how to enable the old behavior.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks for the really thorough answer-- made it really clear.

request_headers_ = &headers;
return Http::FilterHeadersStatus::StopIteration;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we stop iterating to avoid tampering the signature? Probably a comment could be helpful here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I inferred this as necessary because it's what the buffer filter and the aws lambda filter do. I supposed it was necessary to preclude the headers from being sent upstream before the stream ends.

I don't mind adding a comment, but the best I can honestly write with my current understanding is "stop iteration is what other filters do when the request is to be buffered."

Did you have something more concrete in mind?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My experience with filter flows is limited, but I think you are right: StopIteration is needed to buffer the request.

I don't have strong opinion if a comment is needed. Let's leave it as it is.

}

try {
config_->signer().sign(headers);
ENVOY_LOG(debug, "aws request signing from decodeHeaders use_unsigned_payload: {}",
use_unsigned_payload);
if (use_unsigned_payload) {
config_->signer().signUnsignedPayload(headers);
} else {
config_->signer().signEmptyPayload(headers);
}
config_->stats().signing_added_.inc();
} catch (const EnvoyException& e) {
// TODO: sign should not throw to avoid exceptions in the request path
ENVOY_LOG(debug, "signing failed: {}", e.what());
config_->stats().signing_failed_.inc();
}

return Http::FilterHeadersStatus::Continue;
}

Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) {
if (config_->useUnsignedPayload()) {
return Http::FilterDataStatus::Continue;
}

if (!end_stream) {
return Http::FilterDataStatus::StopIterationAndBuffer;
}

decoder_callbacks_->addDecodedData(data, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm , I think this should happen before returning StopIterationAndBuffer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the docstrings (item 2, reference below) and implementation in other filters, I think I've used this correctly (this line is only reachable when end_stream is true.

/**
* Add buffered body data. This method is used in advanced cases where returning
* StopIterationAndBuffer from decodeData() is not sufficient.
*
* 1) If a headers only request needs to be turned into a request with a body, this method can
* be called to add body in the decodeHeaders() callback. Subsequent filters will receive
* decodeHeaders(..., false) followed by decodeData(..., true). This works both in the direct
* iteration as well as the continuation case.
*
* 2) If a filter is going to look at all buffered data from within a data callback with end
* stream set, this method can be called to immediately buffer the data. This avoids having
* to deal with the existing buffered data and the data from the current callback.
*
* 3) If additional buffered body data needs to be added by a filter before continuation of data
* to further filters (outside of callback context).
*
* 4) If additional data needs to be added in the decodeTrailers() callback, this method can be
* called in the context of the callback. All further filters will receive decodeData(..., false)
* followed by decodeTrailers(). However if the iteration is stopped, the added data will
* buffered, so that the further filters will not receive decodeData() before decodeHeaders().
*
* It is an error to call this method in any other case.
*
* See also injectDecodedDataToFilterChain() for a different way of passing data to further
* filters and also how the two methods are different.
*
* @param data Buffer::Instance supplies the data to be decoded.
* @param streaming_filter boolean supplies if this filter streams data or buffers the full body.
*/
virtual void addDecodedData(Buffer::Instance& data, bool streaming_filter) PURE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you are right, I got confused with the case where the data will be changed/replaced (e.g.: compression).


const Buffer::Instance& decoding_buffer = *decoder_callbacks_->decodingBuffer();

auto& hashing_util = Envoy::Common::Crypto::UtilitySingleton::get();
const std::string hash = Hex::encode(hashing_util.getSha256Digest(decoding_buffer));

try {
ENVOY_LOG(debug, "aws request signing from decodeData");
ASSERT(request_headers_ != nullptr);
config_->signer().sign(*request_headers_, hash);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASSERT(request_headers_ != nullptr)

config_->stats().signing_added_.inc();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm should we add a new stat signing_body_added or similar? To distinguish from the existing signing code path...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having separate counters could add fidelity when unsigned_payload is false. I don't actually know under what circumstances end_stream is true in decodeHeaders, so I suppose having discrete counters could be useful in diagnosing any unexpected behavior (like if there's some way end_stream is true when there was a request body).

config_->stats().payload_signing_added_.inc();
} catch (const EnvoyException& e) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are moving away from exceptions in the serving path, so maybe add a TODO about making sign() unexceptional

// TODO: sign should not throw to avoid exceptions in the request path
ENVOY_LOG(debug, "signing failed: {}", e.what());
config_->stats().signing_failed_.inc();
config_->stats().payload_signing_failed_.inc();
}

return Http::FilterDataStatus::Continue;
}

} // namespace AwsRequestSigningFilter
} // namespace HttpFilters
} // namespace Extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ namespace AwsRequestSigningFilter {
* All stats for the AWS request signing filter. @see stats_macros.h
*/
// clang-format off
#define ALL_AWS_REQUEST_SIGNING_FILTER_STATS(COUNTER) \
COUNTER(signing_added) \
COUNTER(signing_failed)
#define ALL_AWS_REQUEST_SIGNING_FILTER_STATS(COUNTER) \
COUNTER(signing_added) \
COUNTER(signing_failed) \
COUNTER(payload_signing_added) \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to add docs for these new stats over here:

signing_added, Counter, Total authentication headers added to requests

COUNTER(payload_signing_failed)
// clang-format on

/**
Expand Down Expand Up @@ -50,6 +52,11 @@ class FilterConfig {
* @return the host rewrite value.
*/
virtual const std::string& hostRewrite() const PURE;

/**
* @return whether or not to buffer and sign the payload.
*/
virtual bool useUnsignedPayload() const PURE;
};

using FilterConfigSharedPtr = std::shared_ptr<FilterConfig>;
Expand All @@ -60,16 +67,18 @@ using FilterConfigSharedPtr = std::shared_ptr<FilterConfig>;
class FilterConfigImpl : public FilterConfig {
public:
FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer, const std::string& stats_prefix,
Stats::Scope& scope, const std::string& host_rewrite);
Stats::Scope& scope, const std::string& host_rewrite, bool use_unsigned_payload);

Extensions::Common::Aws::Signer& signer() override;
FilterStats& stats() override;
const std::string& hostRewrite() const override;
bool useUnsignedPayload() const override;

private:
Extensions::Common::Aws::SignerPtr signer_;
FilterStats stats_;
std::string host_rewrite_;
const bool use_unsigned_payload_;
};

/**
Expand All @@ -83,9 +92,11 @@ class Filter : public Http::PassThroughDecoderFilter, Logger::Loggable<Logger::I

Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers,
bool end_stream) override;
Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override;

private:
std::shared_ptr<FilterConfig> config_;
Http::RequestHeaderMap* request_headers_{};
};

} // namespace AwsRequestSigningFilter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ Http::FilterFactoryCb AwsRequestSigningFilterFactory::createFilterFactoryFromPro
config.service_name(), config.region(), credentials_provider,
context.dispatcher().timeSource());

auto filter_config = std::make_shared<FilterConfigImpl>(std::move(signer), stats_prefix,
context.scope(), config.host_rewrite());
auto filter_config =
std::make_shared<FilterConfigImpl>(std::move(signer), stats_prefix, context.scope(),
config.host_rewrite(), config.use_unsigned_payload());
return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
auto filter = std::make_shared<Filter>(filter_config);
callbacks.addStreamDecoderFilter(filter);
Expand Down
3 changes: 2 additions & 1 deletion test/extensions/common/aws/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ class MockSigner : public Signer {
~MockSigner() override;

MOCK_METHOD(void, sign, (Http::RequestMessage&, bool));
MOCK_METHOD(void, sign, (Http::RequestHeaderMap&));
MOCK_METHOD(void, sign, (Http::RequestHeaderMap&, const std::string&));
MOCK_METHOD(void, signEmptyPayload, (Http::RequestHeaderMap&));
MOCK_METHOD(void, signUnsignedPayload, (Http::RequestHeaderMap&));
};

class MockMetadataFetcher {
Expand Down
16 changes: 10 additions & 6 deletions test/extensions/common/aws/signer_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SignerImplTest : public testing::Test {
void setBody(const std::string& body) { message_->body().add(body); }

void expectSignHeaders(absl::string_view service_name, absl::string_view signature,
absl::string_view payload) {
absl::string_view payload, bool use_unsigned_payload) {
auto* credentials_provider = new NiceMock<MockCredentialsProvider>();
EXPECT_CALL(*credentials_provider, getCredentials()).WillOnce(Return(credentials_));
Http::TestRequestHeaderMapImpl headers{};
Expand All @@ -50,7 +50,11 @@ class SignerImplTest : public testing::Test {

SignerImpl signer(service_name, "region", CredentialsProviderSharedPtr{credentials_provider},
time_system_);
signer.sign(headers);
if (use_unsigned_payload) {
signer.signUnsignedPayload(headers);
} else {
signer.signEmptyPayload(headers);
}

EXPECT_EQ(fmt::format("AWS4-HMAC-SHA256 Credential=akid/20180102/region/{}/aws4_request, "
"SignedHeaders=host;x-amz-content-sha256;x-amz-date, "
Expand Down Expand Up @@ -204,13 +208,13 @@ TEST_F(SignerImplTest, SignHostHeader) {
// Verify signing headers for services.
TEST_F(SignerImplTest, SignHeadersByService) {
expectSignHeaders("s3", "d97cae067345792b78d2bad746f25c729b9eb4701127e13a7c80398f8216a167",
SignatureConstants::get().UnsignedPayload);
SignatureConstants::get().UnsignedPayload, true);
expectSignHeaders("service", "d9fd9be575a254c924d843964b063d770181d938ae818f5b603ef0575a5ce2cd",
SignatureConstants::get().HashedEmptyString);
SignatureConstants::get().HashedEmptyString, false);
expectSignHeaders("es", "0fd9c974bb2ad16c8d8a314dca4f6db151d32cbd04748d9c018afee2a685a02e",
SignatureConstants::get().UnsignedPayload);
SignatureConstants::get().UnsignedPayload, true);
expectSignHeaders("glacier", "8d1f241d77c64cda57b042cd312180f16e98dbd7a96e5545681430f8dbde45a0",
SignatureConstants::get().UnsignedPayload);
SignatureConstants::get().UnsignedPayload, true);
}

} // namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ TEST_F(AwsLambdaFilterTest, DecodingHeaderStopIteration) {
*/
TEST_F(AwsLambdaFilterTest, HeaderOnlyShouldContinue) {
setupFilter({arn_, InvocationMode::Synchronous, true /*passthrough*/});
EXPECT_CALL(*signer_, sign(_));
EXPECT_CALL(*signer_, signEmptyPayload(An<Http::RequestHeaderMap&>()));
Http::TestRequestHeaderMapImpl input_headers;
const auto result = filter_->decodeHeaders(input_headers, true /*end_stream*/);
EXPECT_EQ(Http::FilterHeadersStatus::Continue, result);
Expand Down
Loading