From acc369670373ce5f1d28f79e82d68eb815666f28 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Mon, 5 Apr 2021 22:30:21 -0500 Subject: [PATCH 01/17] aws_request_signing_filter hash payload by default 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. A new filter option is introduced, so that the filter may be explicitly configured to use the UNSIGNED-PAYLOAD string literal as specified in the S3 signing docs: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html fixes #13904 Signed-off-by: Jonathan Stewmon --- .../v3/aws_request_signing.proto | 5 ++ .../v3/aws_request_signing.proto | 5 ++ source/extensions/common/aws/signer.h | 2 +- source/extensions/common/aws/signer_impl.cc | 4 +- source/extensions/common/aws/signer_impl.h | 13 +---- .../aws_request_signing_filter.cc | 48 +++++++++++++++++-- .../aws_request_signing_filter.h | 9 ++-- .../http/aws_request_signing/config.cc | 2 +- .../extensions/common/aws/signer_impl_test.cc | 12 ++--- 9 files changed, 71 insertions(+), 29 deletions(-) diff --git a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 6a516b4300283..f8e38b3b61165 100644 --- a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -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 + // `_ policy for details. + bool unsigned_payload = 4; } diff --git a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index 6a516b4300283..f8e38b3b61165 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -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 + // `_ policy for details. + bool unsigned_payload = 4; } diff --git a/source/extensions/common/aws/signer.h b/source/extensions/common/aws/signer.h index 34a892c9efb2f..7ee645a7285cb 100644 --- a/source/extensions/common/aws/signer.h +++ b/source/extensions/common/aws/signer.h @@ -25,7 +25,7 @@ class Signer { * @param headers AWS API request headers. * @throws EnvoyException if the request cannot be signed. */ - virtual void sign(Http::RequestHeaderMap& headers) PURE; + virtual void sign(Http::RequestHeaderMap& headers, bool unsigned_payload = false) PURE; /** * Sign an AWS request. diff --git a/source/extensions/common/aws/signer_impl.cc b/source/extensions/common/aws/signer_impl.cc index ec0ae8d4b2f95..913fb25b8f06d 100644 --- a/source/extensions/common/aws/signer_impl.cc +++ b/source/extensions/common/aws/signer_impl.cc @@ -23,8 +23,8 @@ void SignerImpl::sign(Http::RequestMessage& message, bool sign_body) { sign(headers, content_hash); } -void SignerImpl::sign(Http::RequestHeaderMap& headers) { - if (require_content_hash_) { +void SignerImpl::sign(Http::RequestHeaderMap& headers, bool unsigned_payload) { + if (unsigned_payload) { headers.setReference(SignatureHeaders::get().ContentSha256, SignatureConstants::get().UnsignedPayload); sign(headers, SignatureConstants::get().UnsignedPayload); diff --git a/source/extensions/common/aws/signer_impl.h b/source/extensions/common/aws/signer_impl.h index 78908874e042a..cbb9bf8218185 100644 --- a/source/extensions/common/aws/signer_impl.h +++ b/source/extensions/common/aws/signer_impl.h @@ -48,22 +48,12 @@ class SignerImpl : public Signer, public Logger::Loggable { 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), 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, bool unsigned_payload = false) override; private: std::string createContentHash(Http::RequestMessage& message, bool sign_body) const; @@ -86,7 +76,6 @@ class SignerImpl : public Signer, public Logger::Loggable { const std::string service_name_; const std::string region_; - const bool require_content_hash_; CredentialsProviderSharedPtr credentials_provider_; TimeSource& time_source_; DateFormatter long_date_formatter_; diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index 5c4ff37272bf2..c4c2e9ef5d63f 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -1,5 +1,8 @@ #include "extensions/filters/http/aws_request_signing/aws_request_signing_filter.h" +#include "common/common/hex.h" +#include "common/crypto/utility.h" + #include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" namespace Envoy { @@ -9,9 +12,12 @@ 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 unsigned_payload) : signer_(std::move(signer)), stats_(Filter::generateStats(stats_prefix, scope)), - host_rewrite_(host_rewrite) {} + host_rewrite_(host_rewrite){ + unsigned_payload_ = unsigned_payload; + } Filter::Filter(const std::shared_ptr& config) : config_(config) {} @@ -26,14 +32,20 @@ FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope 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(); if (!host_rewrite.empty()) { headers.setHost(host_rewrite); } + if (!unsigned_payload_ && !end_stream) { + request_headers_ = &headers; + return Http::FilterHeadersStatus::StopIteration; + } + try { - config_->signer().sign(headers); + ENVOY_LOG(debug, "aws request signing from decodeHeaders"); + config_->signer().sign(headers, unsigned_payload_); config_->stats().signing_added_.inc(); } catch (const EnvoyException& e) { ENVOY_LOG(debug, "signing failed: {}", e.what()); @@ -43,6 +55,34 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, return Http::FilterHeadersStatus::Continue; } +Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { + if (unsigned_payload_) { + return Http::FilterDataStatus::Continue; + } + + if (!end_stream) { + return Http::FilterDataStatus::StopIterationAndBuffer; + } + + decoder_callbacks_->addDecodedData(data, false); + + const Buffer::Instance& decoding_buffer = *decoder_callbacks_->decodingBuffer(); + + auto& hashing_util = Envoy::Common::Crypto::UtilitySingleton::get(); + const auto hash = Hex::encode(hashing_util.getSha256Digest(decoding_buffer)); + + try { + ENVOY_LOG(debug, "aws request signing from decodeData"); + config_->signer().sign(*request_headers_, hash); + config_->stats().signing_added_.inc(); + } catch (const EnvoyException& e) { + ENVOY_LOG(debug, "signing failed: {}", e.what()); + config_->stats().signing_failed_.inc(); + } + + return Http::FilterDataStatus::Continue; +} + } // namespace AwsRequestSigningFilter } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h index ecd22a7b0b4da..c91b060a2eb2f 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h @@ -60,7 +60,7 @@ using FilterConfigSharedPtr = std::shared_ptr; 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 unsigned_payload); Extensions::Common::Aws::Signer& signer() override; FilterStats& stats() override; @@ -70,6 +70,7 @@ class FilterConfigImpl : public FilterConfig { Extensions::Common::Aws::SignerPtr signer_; FilterStats stats_; std::string host_rewrite_; + bool unsigned_payload_; }; /** @@ -81,11 +82,13 @@ class Filter : public Http::PassThroughDecoderFilter, Logger::Loggable config_; + Http::RequestHeaderMap* request_headers_ = nullptr; + bool unsigned_payload_; }; } // namespace AwsRequestSigningFilter diff --git a/source/extensions/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index b637f3bfd1781..57c063c6c4074 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -26,7 +26,7 @@ Http::FilterFactoryCb AwsRequestSigningFilterFactory::createFilterFactoryFromPro context.dispatcher().timeSource()); auto filter_config = std::make_shared(std::move(signer), stats_prefix, - context.scope(), config.host_rewrite()); + context.scope(), config.host_rewrite(), config.unsigned_payload()); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); callbacks.addStreamDecoderFilter(filter); diff --git a/test/extensions/common/aws/signer_impl_test.cc b/test/extensions/common/aws/signer_impl_test.cc index cc4fb856b5de1..f62fa40080fc2 100644 --- a/test/extensions/common/aws/signer_impl_test.cc +++ b/test/extensions/common/aws/signer_impl_test.cc @@ -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 unsigned_payload) { auto* credentials_provider = new NiceMock(); EXPECT_CALL(*credentials_provider, getCredentials()).WillOnce(Return(credentials_)); Http::TestRequestHeaderMapImpl headers{}; @@ -50,7 +50,7 @@ class SignerImplTest : public testing::Test { SignerImpl signer(service_name, "region", CredentialsProviderSharedPtr{credentials_provider}, time_system_); - signer.sign(headers); + signer.sign(headers, unsigned_payload); EXPECT_EQ(fmt::format("AWS4-HMAC-SHA256 Credential=akid/20180102/region/{}/aws4_request, " "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " @@ -204,13 +204,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 From 267c7f343023ff6111b9d3dff128dfba52d07196 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Tue, 6 Apr 2021 21:55:55 -0500 Subject: [PATCH 02/17] document aws_request_signing buffering behavior change Signed-off-by: Jonathan Stewmon --- .../http/http_filters/aws_request_signing_filter.rst | 9 +++++++++ docs/root/version_history/current.rst | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst index 0280a012a05dc..02aac403dd5f9 100644 --- a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst @@ -15,6 +15,14 @@ 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 ``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 ``unsigned_payload`` is ``false`` (the default), requests which exceed the configured buffer +limit will receive a 413 response. See the ref:`flow control docs ` for details. + Example configuration --------------------- @@ -27,6 +35,7 @@ Example filter configuration: "@type": type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning service_name: s3 region: us-west-2 + unsigned_payload: true Statistics diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 1bb975d173d8c..eaacb34e40f57 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -20,6 +20,12 @@ Minor Behavior Changes * access_logs: fix substition formatter to recognize commands ending with an integer such as DOWNSTREAM_PEER_FINGERPRINT_256. * access_logs: set the error flag `NC` for `no cluster found` instead of `NR` if the route is found but the corresponding cluster is not available. * admin: added :ref:`observability_name ` information to GET /clusters?format=json :ref:`cluster status `. +* 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 + ``unsigned_payload`` filter option (default ``false``). * dns: both the :ref:`strict DNS ` and :ref:`logical DNS ` cluster types now honor the :ref:`hostname ` field if not empty. From 0685707f8e087ac7f28c41cd4ec9b0972d4fbe29 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Tue, 6 Apr 2021 21:58:19 -0500 Subject: [PATCH 03/17] get aws_request_signing unsigned_payload option from config Signed-off-by: Jonathan Stewmon --- .../aws_request_signing_filter.cc | 16 ++++++++-------- .../aws_request_signing_filter.h | 7 ++++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index c4c2e9ef5d63f..01ce5d8d864d8 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -12,12 +12,9 @@ namespace AwsRequestSigningFilter { FilterConfigImpl::FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer, const std::string& stats_prefix, Stats::Scope& scope, - const std::string& host_rewrite, - bool unsigned_payload) + const std::string& host_rewrite, bool unsigned_payload) : signer_(std::move(signer)), stats_(Filter::generateStats(stats_prefix, scope)), - host_rewrite_(host_rewrite){ - unsigned_payload_ = unsigned_payload; - } + host_rewrite_(host_rewrite), unsigned_payload_{unsigned_payload} {} Filter::Filter(const std::shared_ptr& config) : config_(config) {} @@ -26,6 +23,7 @@ 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() { return unsigned_payload_; } FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = prefix + "aws_request_signing."; @@ -34,18 +32,20 @@ FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { const auto& host_rewrite = config_->hostRewrite(); + bool unsigned_payload = config_->useUnsignedPayload(); + if (!host_rewrite.empty()) { headers.setHost(host_rewrite); } - if (!unsigned_payload_ && !end_stream) { + if (!unsigned_payload && !end_stream) { request_headers_ = &headers; return Http::FilterHeadersStatus::StopIteration; } try { ENVOY_LOG(debug, "aws request signing from decodeHeaders"); - config_->signer().sign(headers, unsigned_payload_); + config_->signer().sign(headers, unsigned_payload); config_->stats().signing_added_.inc(); } catch (const EnvoyException& e) { ENVOY_LOG(debug, "signing failed: {}", e.what()); @@ -56,7 +56,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, } Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { - if (unsigned_payload_) { + if (config_->useUnsignedPayload()) { return Http::FilterDataStatus::Continue; } diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h index c91b060a2eb2f..f14913d6d0dc0 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h @@ -50,6 +50,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() PURE; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -65,6 +70,7 @@ class FilterConfigImpl : public FilterConfig { Extensions::Common::Aws::Signer& signer() override; FilterStats& stats() override; const std::string& hostRewrite() const override; + bool useUnsignedPayload() override; private: Extensions::Common::Aws::SignerPtr signer_; @@ -88,7 +94,6 @@ class Filter : public Http::PassThroughDecoderFilter, Logger::Loggable config_; Http::RequestHeaderMap* request_headers_ = nullptr; - bool unsigned_payload_; }; } // namespace AwsRequestSigningFilter From 72e4b82fc2b5509fce269d17c6c48817781e3d46 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Tue, 6 Apr 2021 21:59:36 -0500 Subject: [PATCH 04/17] apply formatting Signed-off-by: Jonathan Stewmon --- source/extensions/common/aws/signer_impl.h | 5 ++--- .../http/aws_request_signing/aws_request_signing_filter.cc | 4 ++-- .../http/aws_request_signing/aws_request_signing_filter.h | 3 ++- source/extensions/filters/http/aws_request_signing/config.cc | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/source/extensions/common/aws/signer_impl.h b/source/extensions/common/aws/signer_impl.h index cbb9bf8218185..7690943c8b543 100644 --- a/source/extensions/common/aws/signer_impl.h +++ b/source/extensions/common/aws/signer_impl.h @@ -47,9 +47,8 @@ class SignerImpl : public Signer, public Logger::Loggable { public: SignerImpl(absl::string_view service_name, absl::string_view region, const CredentialsProviderSharedPtr& credentials_provider, TimeSource& time_source) - : service_name_(service_name), region_(region), - 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; diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index 01ce5d8d864d8..2a369873ca9b6 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -1,10 +1,10 @@ #include "extensions/filters/http/aws_request_signing/aws_request_signing_filter.h" +#include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" + #include "common/common/hex.h" #include "common/crypto/utility.h" -#include "envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.pb.h" - namespace Envoy { namespace Extensions { namespace HttpFilters { diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h index f14913d6d0dc0..f851c5edd93bc 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h @@ -88,7 +88,8 @@ class Filter : public Http::PassThroughDecoderFilter, Logger::Loggable(std::move(signer), stats_prefix, - context.scope(), config.host_rewrite(), config.unsigned_payload()); + auto filter_config = + std::make_shared(std::move(signer), stats_prefix, context.scope(), + config.host_rewrite(), config.unsigned_payload()); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); callbacks.addStreamDecoderFilter(filter); From 0ab2d442cd5ee632e85436268be332bc753182a8 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Tue, 6 Apr 2021 22:00:15 -0500 Subject: [PATCH 05/17] update aws mocks with new sign signature Signed-off-by: Jonathan Stewmon --- test/extensions/common/aws/mocks.h | 2 +- .../aws_request_signing_filter_test.cc | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index d89e1934c6d9d..5afc2b0bf3a15 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -24,7 +24,7 @@ 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&, bool)); MOCK_METHOD(void, sign, (Http::RequestHeaderMap&, const std::string&)); }; diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index 9fb6968faed78..e37eb63da16b1 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -13,6 +13,8 @@ namespace HttpFilters { namespace AwsRequestSigningFilter { namespace { +using ::testing::Matcher; + class MockFilterConfig : public FilterConfig { public: MockFilterConfig() { signer_ = std::make_shared(); } @@ -41,7 +43,7 @@ class AwsRequestSigningFilterTest : public testing::Test { // Verify filter functionality when signing works. TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(_)); + EXPECT_CALL(*(filter_config_->signer_), sign(Matcher(_), false)); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); @@ -52,7 +54,7 @@ TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { setup(); filter_config_->host_rewrite_ = "foo"; - EXPECT_CALL(*(filter_config_->signer_), sign(_)); + EXPECT_CALL(*(filter_config_->signer_), sign(_, false)); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); @@ -63,7 +65,7 @@ TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { // Verify filter functionality when signing fails. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(_)).WillOnce(Invoke([](Http::HeaderMap&) -> void { + EXPECT_CALL(*(filter_config_->signer_), sign(_, false)).WillOnce(Invoke([](Http::HeaderMap&) -> void { throw EnvoyException("failed"); })); From 59f1a032e956d32384f3053fd208b3ccb1a267d5 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Wed, 7 Apr 2021 16:59:01 -0500 Subject: [PATCH 06/17] aws filter tests compat with overloaded sign method Signed-off-by: Jonathan Stewmon --- .../http/aws_lambda/aws_lambda_filter_test.cc | 2 +- .../aws_request_signing_filter_test.cc | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc index bcc46b3dc5e33..1d3cb17d17cbc 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc @@ -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_, sign(An(), false)); Http::TestRequestHeaderMapImpl input_headers; const auto result = filter_->decodeHeaders(input_headers, true /*end_stream*/); EXPECT_EQ(Http::FilterHeadersStatus::Continue, result); diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index e37eb63da16b1..564a9cde9c7d6 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -13,7 +13,7 @@ namespace HttpFilters { namespace AwsRequestSigningFilter { namespace { -using ::testing::Matcher; +using ::testing::An; class MockFilterConfig : public FilterConfig { public: @@ -22,11 +22,13 @@ class MockFilterConfig : public FilterConfig { Common::Aws::Signer& signer() override { return *signer_; } FilterStats& stats() override { return stats_; } const std::string& hostRewrite() const override { return host_rewrite_; } + bool useUnsignedPayload() override { return unsigned_payload_; } std::shared_ptr signer_; Stats::IsolatedStoreImpl stats_store_; FilterStats stats_{Filter::generateStats("test", stats_store_)}; std::string host_rewrite_; + bool unsigned_payload_; }; class AwsRequestSigningFilterTest : public testing::Test { @@ -40,37 +42,37 @@ class AwsRequestSigningFilterTest : public testing::Test { std::unique_ptr filter_; }; -// Verify filter functionality when signing works. +// Verify filter functionality when signing works for header only request. TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(Matcher(_), false)); + EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)); Http::TestRequestHeaderMapImpl headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); } -// Verify filter functionality when a host rewrite happens. +// Verify filter functionality when a host rewrite happens for header only request. TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { setup(); filter_config_->host_rewrite_ = "foo"; - EXPECT_CALL(*(filter_config_->signer_), sign(_, false)); + EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)); Http::TestRequestHeaderMapImpl headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_EQ("foo", headers.getHostValue()); EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); } -// Verify filter functionality when signing fails. +// Verify filter functionality when signing fails for header only request. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(_, false)).WillOnce(Invoke([](Http::HeaderMap&) -> void { + EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)).WillOnce(Invoke([](Http::HeaderMap&, bool) -> void { throw EnvoyException("failed"); })); Http::TestRequestHeaderMapImpl headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_EQ(1UL, filter_config_->stats_.signing_failed_.value()); } @@ -79,7 +81,7 @@ TEST_F(AwsRequestSigningFilterTest, FilterConfigImplGetters) { Stats::IsolatedStoreImpl stats; auto signer = std::make_unique(); const auto* signer_ptr = signer.get(); - FilterConfigImpl config(std::move(signer), "prefix", stats, "foo"); + FilterConfigImpl config(std::move(signer), "prefix", stats, "foo", false); EXPECT_EQ(signer_ptr, &config.signer()); EXPECT_EQ(0UL, config.stats().signing_added_.value()); From 07fd41ea22ab9f8a03050fb94e9c2db5bca3d752 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Wed, 7 Apr 2021 22:44:58 -0500 Subject: [PATCH 07/17] test aws request signing from either decodeHeaders or decodeData Signed-off-by: Jonathan Stewmon --- .../aws_request_signing_filter_test.cc | 89 +++++++++++++++++-- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index 564a9cde9c7d6..561b80b075842 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -1,3 +1,5 @@ +#include "envoy/http/filter.h" + #include "extensions/common/aws/signer.h" #include "extensions/filters/http/aws_request_signing/aws_request_signing_filter.h" @@ -13,11 +15,15 @@ namespace HttpFilters { namespace AwsRequestSigningFilter { namespace { +using Common::Aws::MockSigner; using ::testing::An; +using ::testing::InSequence; +using ::testing::NiceMock; +using ::testing::StrictMock; class MockFilterConfig : public FilterConfig { public: - MockFilterConfig() { signer_ = std::make_shared(); } + MockFilterConfig() { signer_ = std::make_shared>(); } Common::Aws::Signer& signer() override { return *signer_; } FilterStats& stats() override { return stats_; } @@ -35,11 +41,14 @@ class AwsRequestSigningFilterTest : public testing::Test { public: void setup() { filter_config_ = std::make_shared(); + filter_config_->unsigned_payload_ = false; filter_ = std::make_unique(filter_config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); } std::shared_ptr filter_config_; std::unique_ptr filter_; + NiceMock decoder_callbacks_; }; // Verify filter functionality when signing works for header only request. @@ -52,6 +61,74 @@ TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); } +// Verify decodeHeaders signs when unsigned_payload is true and end_stream is false. +TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { + setup(); + filter_config_->unsigned_payload_ = true; + EXPECT_CALL(*(filter_config_->signer_), sign(An(), true)); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); +} + +// Verify decodeHeaders signs when unsigned_payload is true and end_stream is true. +TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayloadHeaderOnly) { + setup(); + filter_config_->unsigned_payload_ = true; + EXPECT_CALL(*(filter_config_->signer_), sign(An(), true)); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); +} + +// Verify decodeHeaders does not sign when unsigned_payload is false and end_stream is false. +TEST_F(AwsRequestSigningFilterTest, DecodeHeadersStopsIterationWithoutSigning) { + setup(); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); +} + +// Verify decodeData does not sign when end_stream is false. +TEST_F(AwsRequestSigningFilterTest, DecodeDataStopsIterationWithoutSigning) { + setup(); + + Buffer::OwnedImpl buffer; + EXPECT_EQ(Http::FilterDataStatus::StopIterationAndBuffer, filter_->decodeData(buffer, false)); +} + +// Verify decodeData signs when end_stream is true (empty payload). +TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsEmptyPayloadAndContinues) { + InSequence seq; + setup(); + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + + // echo -n '' | shasum -a 256 + const std::string hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + Buffer::OwnedImpl buffer; + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); + EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); + EXPECT_CALL(*(filter_config_->signer_), sign(HeaderMapEqualRef(&headers), hash)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); +} + +// Verify decodeData signs when end_stream is true (empty payload). +TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsPayloadAndContinues) { + InSequence seq; + setup(); + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + + // echo -n 'Action=SignThis' | shasum -a 256 + const std::string hash = "1db26ef86fca9f7c54d2273d4673a4f2a614fadf3185d16288d454619f1cf491"; + Buffer::OwnedImpl buffer("Action=SignThis"); + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); + EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); + EXPECT_CALL(*(filter_config_->signer_), sign(HeaderMapEqualRef(&headers), hash)); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); +} + // Verify filter functionality when a host rewrite happens for header only request. TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { setup(); @@ -67,25 +144,25 @@ TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { // Verify filter functionality when signing fails for header only request. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)).WillOnce(Invoke([](Http::HeaderMap&, bool) -> void { - throw EnvoyException("failed"); - })); + EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)) + .WillOnce(Invoke([](Http::HeaderMap&, bool) -> void { throw EnvoyException("failed"); })); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); EXPECT_EQ(1UL, filter_config_->stats_.signing_failed_.value()); } -// Verify FilterConfigImpl's getters. +// Verify FilterConfigImpl getters. TEST_F(AwsRequestSigningFilterTest, FilterConfigImplGetters) { Stats::IsolatedStoreImpl stats; auto signer = std::make_unique(); const auto* signer_ptr = signer.get(); - FilterConfigImpl config(std::move(signer), "prefix", stats, "foo", false); + FilterConfigImpl config(std::move(signer), "prefix", stats, "foo", true); EXPECT_EQ(signer_ptr, &config.signer()); EXPECT_EQ(0UL, config.stats().signing_added_.value()); EXPECT_EQ("foo", config.hostRewrite()); + EXPECT_EQ(true, config.useUnsignedPayload()); } } // namespace From e06233386127898742a93e7c79142d266c6810ed Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Thu, 8 Apr 2021 08:32:07 -0500 Subject: [PATCH 08/17] address PR feedback - document new param in aws signer docstring - mark useUnsignedPayload as const - explicitly type hash var to indicate which sign overload is called - assert request_headers_ was set by decodeHeaders Signed-off-by: Jonathan Stewmon --- source/extensions/common/aws/signer.h | 2 ++ .../aws_request_signing/aws_request_signing_filter.cc | 5 +++-- .../http/aws_request_signing/aws_request_signing_filter.h | 8 ++++---- .../aws_request_signing_filter_test.cc | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/source/extensions/common/aws/signer.h b/source/extensions/common/aws/signer.h index 7ee645a7285cb..96ccfbd4ae940 100644 --- a/source/extensions/common/aws/signer.h +++ b/source/extensions/common/aws/signer.h @@ -23,6 +23,8 @@ class Signer { /** * Sign an AWS request. * @param headers AWS API request headers. + * @param unsigned_payload use the literal string ``UNSIGNED-PAYLOAD`` to calculate the payload + * hash. * @throws EnvoyException if the request cannot be signed. */ virtual void sign(Http::RequestHeaderMap& headers, bool unsigned_payload = false) PURE; diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index 2a369873ca9b6..813d86a68c59d 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -23,7 +23,7 @@ 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() { return unsigned_payload_; } +bool FilterConfigImpl::useUnsignedPayload() const { return unsigned_payload_; } FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = prefix + "aws_request_signing."; @@ -69,10 +69,11 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea const Buffer::Instance& decoding_buffer = *decoder_callbacks_->decodingBuffer(); auto& hashing_util = Envoy::Common::Crypto::UtilitySingleton::get(); - const auto hash = Hex::encode(hashing_util.getSha256Digest(decoding_buffer)); + 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); config_->stats().signing_added_.inc(); } catch (const EnvoyException& e) { diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h index f851c5edd93bc..cfeacc17a72ec 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h @@ -54,7 +54,7 @@ class FilterConfig { /** * @return whether or not to buffer and sign the payload. */ - virtual bool useUnsignedPayload() PURE; + virtual bool useUnsignedPayload() const PURE; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -70,13 +70,13 @@ class FilterConfigImpl : public FilterConfig { Extensions::Common::Aws::Signer& signer() override; FilterStats& stats() override; const std::string& hostRewrite() const override; - bool useUnsignedPayload() override; + bool useUnsignedPayload() const override; private: Extensions::Common::Aws::SignerPtr signer_; FilterStats stats_; std::string host_rewrite_; - bool unsigned_payload_; + const bool unsigned_payload_; }; /** @@ -94,7 +94,7 @@ class Filter : public Http::PassThroughDecoderFilter, Logger::Loggable config_; - Http::RequestHeaderMap* request_headers_ = nullptr; + Http::RequestHeaderMap* request_headers_{}; }; } // namespace AwsRequestSigningFilter diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index 561b80b075842..fe20783595d7e 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -28,7 +28,7 @@ class MockFilterConfig : public FilterConfig { Common::Aws::Signer& signer() override { return *signer_; } FilterStats& stats() override { return stats_; } const std::string& hostRewrite() const override { return host_rewrite_; } - bool useUnsignedPayload() override { return unsigned_payload_; } + bool useUnsignedPayload() const override { return unsigned_payload_; } std::shared_ptr signer_; Stats::IsolatedStoreImpl stats_store_; From c33e053d4c2cabe52e934ea3cf6992fe82efbe0a Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Thu, 8 Apr 2021 15:43:55 -0500 Subject: [PATCH 09/17] address PR feedback - rename unsigned_payload to use_unsigned_payload - add stats counter for buffered payloads - code comments and docstring tweaks Signed-off-by: Jonathan Stewmon --- .../v3/aws_request_signing.proto | 2 +- .../aws_request_signing_filter.rst | 16 ++++---- docs/root/version_history/current.rst | 2 +- .../v3/aws_request_signing.proto | 2 +- source/extensions/common/aws/signer.h | 4 +- source/extensions/common/aws/signer_impl.cc | 4 +- source/extensions/common/aws/signer_impl.h | 2 +- .../aws_request_signing_filter.cc | 19 +++++---- .../aws_request_signing_filter.h | 12 +++--- .../http/aws_request_signing/config.cc | 2 +- .../extensions/common/aws/signer_impl_test.cc | 4 +- .../aws_request_signing_filter_test.cc | 40 ++++++++++++++----- 12 files changed, 70 insertions(+), 39 deletions(-) diff --git a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index f8e38b3b61165..b25591bc330b0 100644 --- a/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/api/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -47,5 +47,5 @@ message AwsRequestSigning { // 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 // `_ policy for details. - bool unsigned_payload = 4; + bool use_unsigned_payload = 4; } diff --git a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst index 02aac403dd5f9..07ac302092519 100644 --- a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst @@ -15,13 +15,15 @@ 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 ``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 ``unsigned_payload`` is ``false`` (the default), requests which exceed the configured buffer -limit will receive a 413 response. See the ref:`flow control docs ` for details. +The :ref:`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 ` +is false (the default), requests which exceed the configured buffer limit will receive a 413 response. See the +ref:`flow control docs ` for details. Example configuration --------------------- @@ -35,7 +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 - unsigned_payload: true + use_unsigned_payload: true Statistics diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index eaacb34e40f57..2fe3cafab8da2 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -25,7 +25,7 @@ Minor Behavior Changes 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 - ``unsigned_payload`` filter option (default ``false``). + `use_unsigned_payload` filter option (default false). * dns: both the :ref:`strict DNS ` and :ref:`logical DNS ` cluster types now honor the :ref:`hostname ` field if not empty. diff --git a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto index f8e38b3b61165..b25591bc330b0 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/aws_request_signing/v3/aws_request_signing.proto @@ -47,5 +47,5 @@ message AwsRequestSigning { // 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 // `_ policy for details. - bool unsigned_payload = 4; + bool use_unsigned_payload = 4; } diff --git a/source/extensions/common/aws/signer.h b/source/extensions/common/aws/signer.h index 96ccfbd4ae940..ed4b78ca35607 100644 --- a/source/extensions/common/aws/signer.h +++ b/source/extensions/common/aws/signer.h @@ -23,11 +23,11 @@ class Signer { /** * Sign an AWS request. * @param headers AWS API request headers. - * @param unsigned_payload use the literal string ``UNSIGNED-PAYLOAD`` to calculate the payload + * @param use_unsigned_payload use the literal string `UNSIGNED-PAYLOAD` to calculate the payload * hash. * @throws EnvoyException if the request cannot be signed. */ - virtual void sign(Http::RequestHeaderMap& headers, bool unsigned_payload = false) PURE; + virtual void sign(Http::RequestHeaderMap& headers, bool use_unsigned_payload = false) PURE; /** * Sign an AWS request. diff --git a/source/extensions/common/aws/signer_impl.cc b/source/extensions/common/aws/signer_impl.cc index 913fb25b8f06d..280490ea4ac17 100644 --- a/source/extensions/common/aws/signer_impl.cc +++ b/source/extensions/common/aws/signer_impl.cc @@ -23,8 +23,8 @@ void SignerImpl::sign(Http::RequestMessage& message, bool sign_body) { sign(headers, content_hash); } -void SignerImpl::sign(Http::RequestHeaderMap& headers, bool unsigned_payload) { - if (unsigned_payload) { +void SignerImpl::sign(Http::RequestHeaderMap& headers, bool use_unsigned_payload) { + if (use_unsigned_payload) { headers.setReference(SignatureHeaders::get().ContentSha256, SignatureConstants::get().UnsignedPayload); sign(headers, SignatureConstants::get().UnsignedPayload); diff --git a/source/extensions/common/aws/signer_impl.h b/source/extensions/common/aws/signer_impl.h index 7690943c8b543..d193ab766b01f 100644 --- a/source/extensions/common/aws/signer_impl.h +++ b/source/extensions/common/aws/signer_impl.h @@ -52,7 +52,7 @@ class SignerImpl : public Signer, public Logger::Loggable { short_date_formatter_(SignatureConstants::get().ShortDateFormat) {} void sign(Http::RequestMessage& message, bool sign_body = false) override; - void sign(Http::RequestHeaderMap& headers, bool unsigned_payload = false) override; + void sign(Http::RequestHeaderMap& headers, bool use_unsigned_payload = false) override; private: std::string createContentHash(Http::RequestMessage& message, bool sign_body) const; diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index 813d86a68c59d..6728e68b1aef2 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -12,9 +12,9 @@ namespace AwsRequestSigningFilter { FilterConfigImpl::FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer, const std::string& stats_prefix, Stats::Scope& scope, - const std::string& host_rewrite, bool unsigned_payload) + const std::string& host_rewrite, bool use_unsigned_payload) : signer_(std::move(signer)), stats_(Filter::generateStats(stats_prefix, scope)), - host_rewrite_(host_rewrite), unsigned_payload_{unsigned_payload} {} + host_rewrite_(host_rewrite), use_unsigned_payload_{use_unsigned_payload} {} Filter::Filter(const std::shared_ptr& config) : config_(config) {} @@ -23,7 +23,7 @@ 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 unsigned_payload_; } +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."; @@ -32,22 +32,24 @@ FilterStats Filter::generateStats(const std::string& prefix, Stats::Scope& scope Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { const auto& host_rewrite = config_->hostRewrite(); - bool unsigned_payload = config_->useUnsignedPayload(); + const bool use_unsigned_payload = config_->useUnsignedPayload(); if (!host_rewrite.empty()) { headers.setHost(host_rewrite); } - if (!unsigned_payload && !end_stream) { + if (!use_unsigned_payload && !end_stream) { request_headers_ = &headers; return Http::FilterHeadersStatus::StopIteration; } try { - ENVOY_LOG(debug, "aws request signing from decodeHeaders"); - config_->signer().sign(headers, unsigned_payload); + ENVOY_LOG(debug, "aws request signing from decodeHeaders use_unsigned_payload: {}", + use_unsigned_payload); + config_->signer().sign(headers, use_unsigned_payload); 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(); } @@ -76,9 +78,12 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea ASSERT(request_headers_ != nullptr); config_->signer().sign(*request_headers_, hash); config_->stats().signing_added_.inc(); + config_->stats().payload_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(); + config_->stats().payload_signing_failed_.inc(); } return Http::FilterDataStatus::Continue; diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h index cfeacc17a72ec..2903ec4898530 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.h @@ -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) \ + COUNTER(payload_signing_failed) // clang-format on /** @@ -65,7 +67,7 @@ using FilterConfigSharedPtr = std::shared_ptr; class FilterConfigImpl : public FilterConfig { public: FilterConfigImpl(Extensions::Common::Aws::SignerPtr&& signer, const std::string& stats_prefix, - Stats::Scope& scope, const std::string& host_rewrite, bool unsigned_payload); + Stats::Scope& scope, const std::string& host_rewrite, bool use_unsigned_payload); Extensions::Common::Aws::Signer& signer() override; FilterStats& stats() override; @@ -76,7 +78,7 @@ class FilterConfigImpl : public FilterConfig { Extensions::Common::Aws::SignerPtr signer_; FilterStats stats_; std::string host_rewrite_; - const bool unsigned_payload_; + const bool use_unsigned_payload_; }; /** diff --git a/source/extensions/filters/http/aws_request_signing/config.cc b/source/extensions/filters/http/aws_request_signing/config.cc index fd24d4d287946..11396f39e0523 100644 --- a/source/extensions/filters/http/aws_request_signing/config.cc +++ b/source/extensions/filters/http/aws_request_signing/config.cc @@ -27,7 +27,7 @@ Http::FilterFactoryCb AwsRequestSigningFilterFactory::createFilterFactoryFromPro auto filter_config = std::make_shared(std::move(signer), stats_prefix, context.scope(), - config.host_rewrite(), config.unsigned_payload()); + config.host_rewrite(), config.use_unsigned_payload()); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { auto filter = std::make_shared(filter_config); callbacks.addStreamDecoderFilter(filter); diff --git a/test/extensions/common/aws/signer_impl_test.cc b/test/extensions/common/aws/signer_impl_test.cc index f62fa40080fc2..2b9505db0cd95 100644 --- a/test/extensions/common/aws/signer_impl_test.cc +++ b/test/extensions/common/aws/signer_impl_test.cc @@ -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, bool unsigned_payload) { + absl::string_view payload, bool use_unsigned_payload) { auto* credentials_provider = new NiceMock(); EXPECT_CALL(*credentials_provider, getCredentials()).WillOnce(Return(credentials_)); Http::TestRequestHeaderMapImpl headers{}; @@ -50,7 +50,7 @@ class SignerImplTest : public testing::Test { SignerImpl signer(service_name, "region", CredentialsProviderSharedPtr{credentials_provider}, time_system_); - signer.sign(headers, unsigned_payload); + signer.sign(headers, use_unsigned_payload); EXPECT_EQ(fmt::format("AWS4-HMAC-SHA256 Credential=akid/20180102/region/{}/aws4_request, " "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index fe20783595d7e..68f553b409bbf 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -28,20 +28,20 @@ class MockFilterConfig : public FilterConfig { Common::Aws::Signer& signer() override { return *signer_; } FilterStats& stats() override { return stats_; } const std::string& hostRewrite() const override { return host_rewrite_; } - bool useUnsignedPayload() const override { return unsigned_payload_; } + bool useUnsignedPayload() const override { return use_unsigned_payload_; } std::shared_ptr signer_; Stats::IsolatedStoreImpl stats_store_; FilterStats stats_{Filter::generateStats("test", stats_store_)}; std::string host_rewrite_; - bool unsigned_payload_; + bool use_unsigned_payload_; }; class AwsRequestSigningFilterTest : public testing::Test { public: void setup() { filter_config_ = std::make_shared(); - filter_config_->unsigned_payload_ = false; + filter_config_->use_unsigned_payload_ = false; filter_ = std::make_unique(filter_config_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); } @@ -61,27 +61,27 @@ TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); } -// Verify decodeHeaders signs when unsigned_payload is true and end_stream is false. +// Verify decodeHeaders signs when use_unsigned_payload is true and end_stream is false. TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { setup(); - filter_config_->unsigned_payload_ = true; + filter_config_->use_unsigned_payload_ = true; EXPECT_CALL(*(filter_config_->signer_), sign(An(), true)); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); } -// Verify decodeHeaders signs when unsigned_payload is true and end_stream is true. +// Verify decodeHeaders signs when use_unsigned_payload is true and end_stream is true. TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayloadHeaderOnly) { setup(); - filter_config_->unsigned_payload_ = true; + filter_config_->use_unsigned_payload_ = true; EXPECT_CALL(*(filter_config_->signer_), sign(An(), true)); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); } -// Verify decodeHeaders does not sign when unsigned_payload is false and end_stream is false. +// Verify decodeHeaders does not sign when use_unsigned_payload is false and end_stream is false. TEST_F(AwsRequestSigningFilterTest, DecodeHeadersStopsIterationWithoutSigning) { setup(); @@ -111,6 +111,8 @@ TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsEmptyPayloadAndContinues) { EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); EXPECT_CALL(*(filter_config_->signer_), sign(HeaderMapEqualRef(&headers), hash)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); + EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); + EXPECT_EQ(1UL, filter_config_->stats_.payload_signing_added_.value()); } // Verify decodeData signs when end_stream is true (empty payload). @@ -141,7 +143,7 @@ TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { EXPECT_EQ(1UL, filter_config_->stats_.signing_added_.value()); } -// Verify filter functionality when signing fails for header only request. +// Verify filter functionality when signing fails in decodeHeaders. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)) @@ -152,6 +154,26 @@ TEST_F(AwsRequestSigningFilterTest, SignFails) { EXPECT_EQ(1UL, filter_config_->stats_.signing_failed_.value()); } +// Verify filter functionality when signing fails in decodeData. +TEST_F(AwsRequestSigningFilterTest, DecodeDataSignFails) { + setup(); + + Http::TestRequestHeaderMapImpl headers; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + + Buffer::OwnedImpl buffer; + EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); + EXPECT_CALL(decoder_callbacks_, decodingBuffer).WillOnce(Return(&buffer)); + EXPECT_CALL(*(filter_config_->signer_), + sign(An(), An())) + .WillOnce(Invoke( + [](Http::HeaderMap&, const std::string&) -> void { throw EnvoyException("failed"); })); + + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(buffer, true)); + EXPECT_EQ(1UL, filter_config_->stats_.signing_failed_.value()); + EXPECT_EQ(1UL, filter_config_->stats_.payload_signing_failed_.value()); +} + // Verify FilterConfigImpl getters. TEST_F(AwsRequestSigningFilterTest, FilterConfigImplGetters) { Stats::IsolatedStoreImpl stats; From 1d0039b70459142adecc3c992b280757f101f4e6 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Fri, 9 Apr 2021 09:02:47 -0500 Subject: [PATCH 10/17] change sha comments to appease spell check Signed-off-by: Jonathan Stewmon --- .../aws_request_signing/aws_request_signing_filter_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index 68f553b409bbf..c1631f28f09b8 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -104,7 +104,7 @@ TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsEmptyPayloadAndContinues) { Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); - // echo -n '' | shasum -a 256 + // sha256('') const std::string hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; Buffer::OwnedImpl buffer; EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); @@ -122,7 +122,7 @@ TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsPayloadAndContinues) { Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); - // echo -n 'Action=SignThis' | shasum -a 256 + // sha256('Action=SignThis') const std::string hash = "1db26ef86fca9f7c54d2273d4673a4f2a614fadf3185d16288d454619f1cf491"; Buffer::OwnedImpl buffer("Action=SignThis"); EXPECT_CALL(decoder_callbacks_, addDecodedData(_, false)); From 6fe9cedbd7b41766882fac7cbda5ae68824cb9c7 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Fri, 16 Apr 2021 11:53:55 -0500 Subject: [PATCH 11/17] fix version history docs after merging main Signed-off-by: Jonathan Stewmon --- docs/root/version_history/current.rst | 56 --------------------------- 1 file changed, 56 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index dd5aea6b29261..803a5983268cb 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -9,68 +9,12 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* access_logs: change command operator %UPSTREAM_CLUSTER% to resolve to :ref:`alt_stat_name ` if provided. This behavior can be reverted by disabling the runtime feature `envoy.reloadable_features.use_observable_cluster_name`. -* access_logs: fix substition formatter to recognize commands ending with an integer such as DOWNSTREAM_PEER_FINGERPRINT_256. -* access_logs: set the error flag `NC` for `no cluster found` instead of `NR` if the route is found but the corresponding cluster is not available. -* admin: added :ref:`observability_name ` information to GET /clusters?format=json :ref:`cluster status `. * 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). -* dns: both the :ref:`strict DNS ` and - :ref:`logical DNS ` cluster types now honor the - :ref:`hostname ` field if not empty. - Previously resolved hosts would have their hostname set to the configured DNS address for use with - logging, :ref:`auto_host_rewrite `, etc. - Setting the hostname manually allows overriding the internal hostname used for such features while - still allowing the original DNS resolution name to be used. -* grpc_json_transcoder: filter now adheres to encoder and decoder buffer limits. Requests and responses - that require buffering over the limits will be directly rejected. The behavior can be reverted by - disabling runtime feature `envoy.reloadable_features.grpc_json_transcoder_adhere_to_buffer_limits`. - To reduce or increase the buffer limits the filter adheres to, reference the :ref:`flow control documentation `. -* hds: support custom health check port via :ref:`health_check_config `. -* healthcheck: the :ref:`health check filter ` now sends the - :ref:`x-envoy-immediate-health-check-fail ` header - for all responses when Envoy is in the health check failed state. Additionally, receiving the - :ref:`x-envoy-immediate-health-check-fail ` - header (either in response to normal traffic or in response to an HTTP :ref:`active health check `) will - cause Envoy to immediately :ref:`exclude ` the host from - load balancing calculations. This has the useful property that such hosts, which are being - explicitly told to disable traffic, will not be counted for panic routing calculations. See the - excluded documentation for more information. This behavior can be temporarily reverted by setting - the `envoy.reloadable_features.health_check.immediate_failure_exclude_from_cluster` feature flag - to false. Note that the runtime flag covers *both* the health check filter responding with - `x-envoy-immediate-health-check-fail` in all cases (versus just non-HC requests) as well as - whether receiving `x-envoy-immediate-health-check-fail` will cause exclusion or not. Thus, - depending on the Envoy deployment, the feature flag may need to be flipped on both downstream - and upstream instances, depending on the reason. -* http: added support for internal redirects with bodies. This behavior can be disabled temporarily by setting `envoy.reloadable_features.internal_redirects_with_body` to false. -* http: allow to use path canonicalizer from `googleurl `_ - instead of `//source/common/chromium_url`. The new path canonicalizer is enabled by default. To - revert to the legacy path canonicalizer, enable the runtime flag - `envoy.reloadable_features.remove_forked_chromium_url`. -* http: increase the maximum allowed number of initial connection WINDOW_UPDATE frames sent by the peer from 1 to 5. -* http: no longer adding content-length: 0 for requests which should not have bodies. This behavior can be temporarily reverted by setting `envoy.reloadable_features.dont_add_content_length_for_bodiless_requests` false. -* http: upstream flood and abuse checks increment the count of opened HTTP/2 streams when Envoy sends - initial HEADERS frame for the new stream. Before the counter was incrementred when Envoy received - response HEADERS frame with the END_HEADERS flag set from upstream server. -* lua: added function `timestamp` to provide millisecond resolution timestamps by passing in `EnvoyTimestampResolution.MILLISECOND`. -* oauth filter: added the optional parameter :ref:`auth_scopes ` with default value of 'user' if not provided. Enables this value to be overridden in the Authorization request to the OAuth provider. -* perf: allow reading more bytes per operation from raw sockets to improve performance. -* router: extended custom date formatting to DOWNSTREAM_PEER_CERT_V_START and DOWNSTREAM_PEER_CERT_V_END when using :ref:`custom request/response header formats `. -* router: made the path rewrite available without finalizing headers, so the filter could calculate the current value of the final url. -* tracing: added `upstream_cluster.name` tag that resolves to resolve to :ref:`alt_stat_name ` if provided (and otherwise the cluster name). -* udp: configuration has been added for :ref:`GRO ` - which used to be force enabled if the OS supports it. The default is now disabled for server - sockets and enabled for client sockets (see the new features section for links). -* upstream: host weight changes now cause a full load balancer rebuild as opposed to happening - atomically inline. This change has been made to support load balancer pre-computation of data - structures based on host weight, but may have performance implications if host weight changes - are very frequent. This change can be disabled by setting the `envoy.reloadable_features.upstream_host_weight_change_causes_rebuild` - feature flag to false. If setting this flag to false is required in a deployment please open an - issue against the project. Bug Fixes --------- From 04d81aa2c5c54ca34ff2d191aaecce29950ee8a7 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Fri, 16 Apr 2021 14:24:09 -0500 Subject: [PATCH 12/17] try using Matcher to disambiguate sign call Signed-off-by: Jonathan Stewmon --- .../filters/http/aws_lambda/aws_lambda_filter_test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc index 1d3cb17d17cbc..1209e475895cf 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc @@ -26,6 +26,7 @@ using ::testing::An; using ::testing::ElementsAre; using ::testing::InSequence; using ::testing::Invoke; +using ::testing::Matcher; using ::testing::Pair; using ::testing::Return; using ::testing::ReturnRef; @@ -76,7 +77,7 @@ TEST_F(AwsLambdaFilterTest, DecodingHeaderStopIteration) { */ TEST_F(AwsLambdaFilterTest, HeaderOnlyShouldContinue) { setupFilter({arn_, InvocationMode::Synchronous, true /*passthrough*/}); - EXPECT_CALL(*signer_, sign(An(), false)); + EXPECT_CALL(*signer_, sign(An(), Matcher(false))); Http::TestRequestHeaderMapImpl input_headers; const auto result = filter_->decodeHeaders(input_headers, true /*end_stream*/); EXPECT_EQ(Http::FilterHeadersStatus::Continue, result); From 0b32662f438508a15cbaeb320837c7cf0b7c2b09 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Fri, 16 Apr 2021 16:27:50 -0500 Subject: [PATCH 13/17] use Matcher in aws request signing tests Signed-off-by: Jonathan Stewmon --- .../aws_request_signing_filter_test.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index c1631f28f09b8..ac89f984aaa04 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -18,6 +18,7 @@ namespace { using Common::Aws::MockSigner; using ::testing::An; using ::testing::InSequence; +using ::testing::Matcher; using ::testing::NiceMock; using ::testing::StrictMock; @@ -54,7 +55,8 @@ class AwsRequestSigningFilterTest : public testing::Test { // Verify filter functionality when signing works for header only request. TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)); + EXPECT_CALL(*(filter_config_->signer_), + sign(An(), Matcher(false))); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); @@ -65,7 +67,7 @@ TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { setup(); filter_config_->use_unsigned_payload_ = true; - EXPECT_CALL(*(filter_config_->signer_), sign(An(), true)); + EXPECT_CALL(*(filter_config_->signer_), sign(An(), Matcher(true))); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); @@ -75,7 +77,7 @@ TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayloadHeaderOnly) { setup(); filter_config_->use_unsigned_payload_ = true; - EXPECT_CALL(*(filter_config_->signer_), sign(An(), true)); + EXPECT_CALL(*(filter_config_->signer_), sign(An(), Matcher(true))); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); @@ -135,7 +137,8 @@ TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsPayloadAndContinues) { TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { setup(); filter_config_->host_rewrite_ = "foo"; - EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)); + EXPECT_CALL(*(filter_config_->signer_), + sign(An(), Matcher(false))); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); @@ -146,7 +149,7 @@ TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { // Verify filter functionality when signing fails in decodeHeaders. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(An(), false)) + EXPECT_CALL(*(filter_config_->signer_), sign(An(), Matcher(false))) .WillOnce(Invoke([](Http::HeaderMap&, bool) -> void { throw EnvoyException("failed"); })); Http::TestRequestHeaderMapImpl headers; From dc2303d09c0a3ce258f5084eac5450b2a3a598a6 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Thu, 22 Apr 2021 12:57:28 -0500 Subject: [PATCH 14/17] document new aws request signing filter stats Signed-off-by: Jonathan Stewmon --- .../http/http_filters/aws_request_signing_filter.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst index 07ac302092519..4c31387c503a8 100644 --- a/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst +++ b/docs/root/configuration/http/http_filters/aws_request_signing_filter.rst @@ -51,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 From 054f829aa64bbbc7731fe3a5b9b96abeb4a57335 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Thu, 22 Apr 2021 21:54:29 -0500 Subject: [PATCH 15/17] prefer discretely named methods in Signer interface Signed-off-by: Jonathan Stewmon --- source/extensions/common/aws/signer.h | 13 ++++++++---- source/extensions/common/aws/signer_impl.cc | 20 +++++++++---------- source/extensions/common/aws/signer_impl.h | 6 +++--- .../http/aws_lambda/aws_lambda_filter.cc | 2 +- .../aws_request_signing_filter.cc | 6 +++++- test/extensions/common/aws/mocks.h | 3 ++- .../extensions/common/aws/signer_impl_test.cc | 6 +++++- .../http/aws_lambda/aws_lambda_filter_test.cc | 2 +- .../aws_request_signing_filter_test.cc | 14 ++++++------- 9 files changed, 42 insertions(+), 30 deletions(-) diff --git a/source/extensions/common/aws/signer.h b/source/extensions/common/aws/signer.h index ed4b78ca35607..3dd9040bc63fb 100644 --- a/source/extensions/common/aws/signer.h +++ b/source/extensions/common/aws/signer.h @@ -21,13 +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. - * @param use_unsigned_payload use the literal string `UNSIGNED-PAYLOAD` to calculate the payload - * hash. * @throws EnvoyException if the request cannot be signed. */ - virtual void sign(Http::RequestHeaderMap& headers, bool use_unsigned_payload = false) PURE; + virtual void signUnsignedPayload(Http::RequestHeaderMap& headers) PURE; /** * Sign an AWS request. diff --git a/source/extensions/common/aws/signer_impl.cc b/source/extensions/common/aws/signer_impl.cc index 280490ea4ac17..a328610e28042 100644 --- a/source/extensions/common/aws/signer_impl.cc +++ b/source/extensions/common/aws/signer_impl.cc @@ -23,16 +23,16 @@ void SignerImpl::sign(Http::RequestMessage& message, bool sign_body) { sign(headers, content_hash); } -void SignerImpl::sign(Http::RequestHeaderMap& headers, bool use_unsigned_payload) { - if (use_unsigned_payload) { - 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) { diff --git a/source/extensions/common/aws/signer_impl.h b/source/extensions/common/aws/signer_impl.h index d193ab766b01f..b1b5ad3c550d4 100644 --- a/source/extensions/common/aws/signer_impl.h +++ b/source/extensions/common/aws/signer_impl.h @@ -52,7 +52,9 @@ class SignerImpl : public Signer, public Logger::Loggable { short_date_formatter_(SignatureConstants::get().ShortDateFormat) {} void sign(Http::RequestMessage& message, bool sign_body = false) override; - void sign(Http::RequestHeaderMap& headers, bool use_unsigned_payload = false) 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; @@ -70,8 +72,6 @@ class SignerImpl : public Signer, public Logger::Loggable { const std::map& 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_; diff --git a/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc b/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc index b4a43217a978a..6a908ff117e49 100644 --- a/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc +++ b/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc @@ -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; } diff --git a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc index 6728e68b1aef2..4a9fb083345dd 100644 --- a/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc +++ b/source/extensions/filters/http/aws_request_signing/aws_request_signing_filter.cc @@ -46,7 +46,11 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, try { ENVOY_LOG(debug, "aws request signing from decodeHeaders use_unsigned_payload: {}", use_unsigned_payload); - config_->signer().sign(headers, 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 diff --git a/test/extensions/common/aws/mocks.h b/test/extensions/common/aws/mocks.h index 5afc2b0bf3a15..05f7562197107 100644 --- a/test/extensions/common/aws/mocks.h +++ b/test/extensions/common/aws/mocks.h @@ -24,8 +24,9 @@ class MockSigner : public Signer { ~MockSigner() override; MOCK_METHOD(void, sign, (Http::RequestMessage&, bool)); - MOCK_METHOD(void, sign, (Http::RequestHeaderMap&, bool)); MOCK_METHOD(void, sign, (Http::RequestHeaderMap&, const std::string&)); + MOCK_METHOD(void, signEmptyPayload, (Http::RequestHeaderMap&)); + MOCK_METHOD(void, signUnsignedPayload, (Http::RequestHeaderMap&)); }; class MockMetadataFetcher { diff --git a/test/extensions/common/aws/signer_impl_test.cc b/test/extensions/common/aws/signer_impl_test.cc index 2b9505db0cd95..fb8625febbfc8 100644 --- a/test/extensions/common/aws/signer_impl_test.cc +++ b/test/extensions/common/aws/signer_impl_test.cc @@ -50,7 +50,11 @@ class SignerImplTest : public testing::Test { SignerImpl signer(service_name, "region", CredentialsProviderSharedPtr{credentials_provider}, time_system_); - signer.sign(headers, use_unsigned_payload); + 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, " diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc index 1209e475895cf..f7b0d93a2d7dc 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc @@ -77,7 +77,7 @@ TEST_F(AwsLambdaFilterTest, DecodingHeaderStopIteration) { */ TEST_F(AwsLambdaFilterTest, HeaderOnlyShouldContinue) { setupFilter({arn_, InvocationMode::Synchronous, true /*passthrough*/}); - EXPECT_CALL(*signer_, sign(An(), Matcher(false))); + EXPECT_CALL(*signer_, signEmptyPayload(An())); Http::TestRequestHeaderMapImpl input_headers; const auto result = filter_->decodeHeaders(input_headers, true /*end_stream*/); EXPECT_EQ(Http::FilterHeadersStatus::Continue, result); diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index ac89f984aaa04..ac88cb5939b03 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -55,8 +55,7 @@ class AwsRequestSigningFilterTest : public testing::Test { // Verify filter functionality when signing works for header only request. TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { setup(); - EXPECT_CALL(*(filter_config_->signer_), - sign(An(), Matcher(false))); + EXPECT_CALL(*(filter_config_->signer_), signEmptyPayload(An())); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); @@ -67,7 +66,7 @@ TEST_F(AwsRequestSigningFilterTest, SignSucceeds) { TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { setup(); filter_config_->use_unsigned_payload_ = true; - EXPECT_CALL(*(filter_config_->signer_), sign(An(), Matcher(true))); + EXPECT_CALL(*(filter_config_->signer_), signUnsignedPayload(An())); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); @@ -77,7 +76,7 @@ TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayload) { TEST_F(AwsRequestSigningFilterTest, DecodeHeadersSignsUnsignedPayloadHeaderOnly) { setup(); filter_config_->use_unsigned_payload_ = true; - EXPECT_CALL(*(filter_config_->signer_), sign(An(), Matcher(true))); + EXPECT_CALL(*(filter_config_->signer_), signUnsignedPayload(An())); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); @@ -137,8 +136,7 @@ TEST_F(AwsRequestSigningFilterTest, DecodeDataSignsPayloadAndContinues) { TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { setup(); filter_config_->host_rewrite_ = "foo"; - EXPECT_CALL(*(filter_config_->signer_), - sign(An(), Matcher(false))); + EXPECT_CALL(*(filter_config_->signer_), signEmptyPayload(An())); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); @@ -149,8 +147,8 @@ TEST_F(AwsRequestSigningFilterTest, SignWithHostRewrite) { // Verify filter functionality when signing fails in decodeHeaders. TEST_F(AwsRequestSigningFilterTest, SignFails) { setup(); - EXPECT_CALL(*(filter_config_->signer_), sign(An(), Matcher(false))) - .WillOnce(Invoke([](Http::HeaderMap&, bool) -> void { throw EnvoyException("failed"); })); + EXPECT_CALL(*(filter_config_->signer_), signEmptyPayload(An())) + .WillOnce(Invoke([](Http::HeaderMap&) -> void { throw EnvoyException("failed"); })); Http::TestRequestHeaderMapImpl headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, true)); From 1186e2dddb28a8863bbc18ee032fd768e9df9937 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Tue, 27 Apr 2021 09:45:28 -0500 Subject: [PATCH 16/17] remove unused using decl Matcher in aws filter tests Signed-off-by: Jonathan Stewmon --- .../extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc | 1 - .../http/aws_request_signing/aws_request_signing_filter_test.cc | 1 - 2 files changed, 2 deletions(-) diff --git a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc index f7b0d93a2d7dc..d8fb873cf2972 100644 --- a/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc +++ b/test/extensions/filters/http/aws_lambda/aws_lambda_filter_test.cc @@ -26,7 +26,6 @@ using ::testing::An; using ::testing::ElementsAre; using ::testing::InSequence; using ::testing::Invoke; -using ::testing::Matcher; using ::testing::Pair; using ::testing::Return; using ::testing::ReturnRef; diff --git a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc index ac88cb5939b03..2e730a8a70406 100644 --- a/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc +++ b/test/extensions/filters/http/aws_request_signing/aws_request_signing_filter_test.cc @@ -18,7 +18,6 @@ namespace { using Common::Aws::MockSigner; using ::testing::An; using ::testing::InSequence; -using ::testing::Matcher; using ::testing::NiceMock; using ::testing::StrictMock; From 7a964a5f5177f27c52deda62f1dbdc99afb325d6 Mon Sep 17 00:00:00 2001 From: Jonathan Stewmon Date: Fri, 7 May 2021 10:36:39 -0500 Subject: [PATCH 17/17] fix version history docs after merging main Signed-off-by: Jonathan Stewmon --- docs/root/version_history/current.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index b3fd7b8889c61..f1d0c37b191af 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -13,15 +13,13 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* +* access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. * 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` -* access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. * 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