From 67857d0f1b0cad3d12b71accdebc787f6d221cf5 Mon Sep 17 00:00:00 2001 From: Larry West Date: Thu, 21 Mar 2019 22:30:53 -0700 Subject: [PATCH 01/22] Issue #6355: jwt_authn extracts JWT more flexibly Skips non-legit JWT chars after the "value-prefix" is found, then returns a substring up to the first non-JWT char, or the end of line. Signed-off-by: Larry West --- .../filters/http/jwt_authn/extractor.cc | 39 +++++++++- test/extensions/filters/http/jwt_authn/BUILD | 1 + .../filters/http/jwt_authn/extractor_test.cc | 77 +++++++++++++++++++ 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 979275981d735..1524acac25b55 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -102,7 +102,11 @@ class ExtractorImpl : public Extractor { // ctor helper for a jwt provider config void addProvider(const JwtProvider& provider); - // HeaderMap value type to store prefix and issuers that specified this +// retrieve base64url-encoded substring; see RFC-7519 + absl::string_view extractJWT(const absl::string_view &value_str, + absl::string_view::size_type after) const; + + // HeaderMap value type to store prefix and issuers that specified this // header. struct HeaderLocationSpec { HeaderLocationSpec(const Http::LowerCaseString& header, const std::string& value_prefix) @@ -171,6 +175,7 @@ void ExtractorImpl::addQueryParamConfig(const std::string& issuer, const std::st param_location_spec.specified_issuers_.insert(issuer); } + std::vector ExtractorImpl::extract(const Http::HeaderMap& headers) const { std::vector tokens; @@ -181,11 +186,12 @@ std::vector ExtractorImpl::extract(const Http::HeaderMap& h if (entry) { auto value_str = entry->value().getStringView(); if (!location_spec->value_prefix_.empty()) { - if (!absl::StartsWith(value_str, location_spec->value_prefix_)) { - // prefix doesn't match, skip it. + const auto pos = value_str.find(location_spec->value_prefix_); + if (pos == absl::string_view::npos) { + // value_prefix not found anywhere in value_str, so skip continue; } - value_str = value_str.substr(location_spec->value_prefix_.size()); + value_str = extractJWT(value_str, pos + location_spec->value_prefix_.length()); } tokens.push_back(std::make_unique( std::string(value_str), location_spec->specified_issuers_, location_spec->header_)); @@ -211,6 +217,31 @@ std::vector ExtractorImpl::extract(const Http::HeaderMap& h return tokens; } +// as specified in RFC-4648 § 5, plus dot (period, 0x2e), of which two are required in the JWT +constexpr char kBase64UrlEncodingCharsPlusDot[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; + +// Returns a token, not a URL: skips non-Base64Url-legal (or dot) characters, collects following +// Base64Url+dot string until first non-Base64Url char. +// +// For backwards compatibility, if it finds no suitable string, it returns value_str as-is. +// +// It is forgiving w.r.t. dots/periods, as the exact syntax will be verified after extraction. +// +// See RFC-7519 § 2, RFC-7515 § 2, and RFC-4648 "Base-N Encodings" § 5. +absl::string_view ExtractorImpl::extractJWT(const absl::string_view &value_str, + absl::string_view::size_type after) const { + const auto starting = value_str.find_first_of(kBase64UrlEncodingCharsPlusDot, after); + if (starting == value_str.npos) { + return value_str; + } + // There should be two dots (periods; 0x2e) inside the string, but we don't verify that here + auto ending = value_str.find_first_not_of(kBase64UrlEncodingCharsPlusDot, starting); + if (ending == value_str.npos) { // Base64Url-encoded string occupies the rest of the line + return value_str.substr(starting); + } + return value_str.substr(starting, ending-starting); +} + void ExtractorImpl::sanitizePayloadHeaders(Http::HeaderMap& headers) const { for (const auto& header : forward_payload_headers_) { headers.remove(header); diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index b225c4510ed08..c09a8a9a97749 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -34,6 +34,7 @@ envoy_extension_cc_test( extension_name = "envoy.filters.http.jwt_authn", deps = [ "//source/extensions/filters/http/jwt_authn:extractor_lib", + "//test/extensions/filters/http/jwt_authn:test_common_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index 58b902fbed5e4..25762863d5fb9 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -4,6 +4,9 @@ #include "test/test_common/utility.h" +#include "test/extensions/filters/http/jwt_authn/test_common.h" +#include "test/extensions/filters/http/jwt_authn/test_common.h" + using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtProvider; using Envoy::Http::TestHeaderMapImpl; @@ -46,6 +49,16 @@ const char ExampleConfig[] = R"( from_headers: - name: prefix-header value_prefix: AAABBB + provider7: + issuer: issuer7 + from_headers: + - name: prefix-header + value_prefix: CCCDDD + provider8: + issuer: issuer8 + from_headers: + - name: prefix-header + value_prefix: '"CCCDDD"' )"; class ExtractorTest : public testing::Test { @@ -102,6 +115,22 @@ TEST_F(ExtractorTest, TestDefaultHeaderLocation) { EXPECT_FALSE(headers.Authorization()); } +// Test extracting JWT as Bearer token from the default header location: "Authorization" - +// using an actual (correctly-formatted) JWT: +TEST_F(ExtractorTest, TestDefaultHeaderLocationWithValidJWT) { + auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + std::string(GoodToken)}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 1); + + // Only the issue1 is using default header location. + EXPECT_EQ(tokens[0]->token(), GoodToken); + EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer1")); + + // Test token remove + tokens[0]->removeJwt(headers); + EXPECT_FALSE(headers.Authorization()); +} + // Test extracting token from the default query parameter: "access_token" TEST_F(ExtractorTest, TestDefaultParamLocation) { auto headers = TestHeaderMapImpl{{":path", "/path?access_token=jwt_token"}}; @@ -172,6 +201,54 @@ TEST_F(ExtractorTest, TestPrefixHeaderMatch) { EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); } +// Test extracting token from the custom header: "prefix-header" +// The value is found after the "CCCDDD", then between the '=' and the ','. +TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch1) { + auto headers = TestHeaderMapImpl{{"prefix-header", "preamble CCCDDD=jwt_token,extra=more"}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 1); + + // Match issuer 7 with map key as: prefix-header + CCCDDD + EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer7")); + EXPECT_EQ(tokens[0]->token(), "jwt_token"); + + // Test token remove + tokens[0]->removeJwt(headers); + EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); +} + +TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { + auto headers = TestHeaderMapImpl{{"prefix-header", "CCCDDD=\"and0X3Rva2Vu\",comment=\"fish tag\""}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 1); + + // Match issuer 7 with map key as: prefix-header + AAA + EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer7")); + EXPECT_EQ(tokens[0]->token(), "and0X3Rva2Vu"); + + // Test token remove + tokens[0]->removeJwt(headers); + EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); +} + +TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch3) { + auto headers = TestHeaderMapImpl{{"prefix-header", "creds={\"authLevel\": \"20\", \"CCCDDD\": \"and0X3Rva2Vu\"}"}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 2); + + // Match issuer 8 with map key as: prefix-header + "CCCDDD" + EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer8")); + EXPECT_EQ(tokens[0]->token(), "and0X3Rva2Vu"); + + // Match issuer 7 with map key as: prefix-header + CCCDDD + EXPECT_TRUE(tokens[1]->isIssuerSpecified("issuer7")); + EXPECT_EQ(tokens[1]->token(), "and0X3Rva2Vu"); + + // Test token remove + tokens[0]->removeJwt(headers); + EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); +} + // Test extracting token from the custom query parameter: "token_param" TEST_F(ExtractorTest, TestCustomParamToken) { auto headers = TestHeaderMapImpl{{":path", "/path?token_param=jwt_token"}}; From 5e026dc0f1765b972b13649bb28e405f613137af Mon Sep 17 00:00:00 2001 From: Larry West Date: Mon, 25 Mar 2019 20:02:29 -0700 Subject: [PATCH 02/22] Issue 6355: run 'tools/check_format.py fix' on extractor.cc Signed-off-by: Larry West --- .../filters/http/jwt_authn/extractor.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 1524acac25b55..fe11ff9f4bdae 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -102,11 +102,11 @@ class ExtractorImpl : public Extractor { // ctor helper for a jwt provider config void addProvider(const JwtProvider& provider); -// retrieve base64url-encoded substring; see RFC-7519 - absl::string_view extractJWT(const absl::string_view &value_str, - absl::string_view::size_type after) const; + // retrieve base64url-encoded substring; see RFC-7519 + absl::string_view extractJWT(const absl::string_view& value_str, + absl::string_view::size_type after) const; - // HeaderMap value type to store prefix and issuers that specified this + // HeaderMap value type to store prefix and issuers that specified this // header. struct HeaderLocationSpec { HeaderLocationSpec(const Http::LowerCaseString& header, const std::string& value_prefix) @@ -175,7 +175,6 @@ void ExtractorImpl::addQueryParamConfig(const std::string& issuer, const std::st param_location_spec.specified_issuers_.insert(issuer); } - std::vector ExtractorImpl::extract(const Http::HeaderMap& headers) const { std::vector tokens; @@ -191,7 +190,7 @@ std::vector ExtractorImpl::extract(const Http::HeaderMap& h // value_prefix not found anywhere in value_str, so skip continue; } - value_str = extractJWT(value_str, pos + location_spec->value_prefix_.length()); + value_str = extractJWT(value_str, pos + location_spec->value_prefix_.length()); } tokens.push_back(std::make_unique( std::string(value_str), location_spec->specified_issuers_, location_spec->header_)); @@ -218,7 +217,8 @@ std::vector ExtractorImpl::extract(const Http::HeaderMap& h } // as specified in RFC-4648 § 5, plus dot (period, 0x2e), of which two are required in the JWT -constexpr char kBase64UrlEncodingCharsPlusDot[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; +constexpr char kBase64UrlEncodingCharsPlusDot[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; // Returns a token, not a URL: skips non-Base64Url-legal (or dot) characters, collects following // Base64Url+dot string until first non-Base64Url char. @@ -228,7 +228,7 @@ constexpr char kBase64UrlEncodingCharsPlusDot[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabc // It is forgiving w.r.t. dots/periods, as the exact syntax will be verified after extraction. // // See RFC-7519 § 2, RFC-7515 § 2, and RFC-4648 "Base-N Encodings" § 5. -absl::string_view ExtractorImpl::extractJWT(const absl::string_view &value_str, +absl::string_view ExtractorImpl::extractJWT(const absl::string_view& value_str, absl::string_view::size_type after) const { const auto starting = value_str.find_first_of(kBase64UrlEncodingCharsPlusDot, after); if (starting == value_str.npos) { @@ -239,7 +239,7 @@ absl::string_view ExtractorImpl::extractJWT(const absl::string_view &value_str, if (ending == value_str.npos) { // Base64Url-encoded string occupies the rest of the line return value_str.substr(starting); } - return value_str.substr(starting, ending-starting); + return value_str.substr(starting, ending - starting); } void ExtractorImpl::sanitizePayloadHeaders(Http::HeaderMap& headers) const { From c75bb4e6e9f4b566bcf6ce144353def6a9c39fb5 Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 11:18:14 -0700 Subject: [PATCH 03/22] Issue 6355: style: k -> Constant, comments No functional changes. Signed-off-by: Larry West --- .../extensions/filters/http/jwt_authn/extractor.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index fe11ff9f4bdae..6de4ecbb40364 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -102,7 +102,7 @@ class ExtractorImpl : public Extractor { // ctor helper for a jwt provider config void addProvider(const JwtProvider& provider); - // retrieve base64url-encoded substring; see RFC-7519 + // @return what should be the 3-part base64url-encoded substring; see RFC-7519 absl::string_view extractJWT(const absl::string_view& value_str, absl::string_view::size_type after) const; @@ -217,12 +217,16 @@ std::vector ExtractorImpl::extract(const Http::HeaderMap& h } // as specified in RFC-4648 § 5, plus dot (period, 0x2e), of which two are required in the JWT -constexpr char kBase64UrlEncodingCharsPlusDot[] = +constexpr char ConstantBase64UrlEncodingCharsPlusDot[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; // Returns a token, not a URL: skips non-Base64Url-legal (or dot) characters, collects following // Base64Url+dot string until first non-Base64Url char. // +// The input parameters: +// "value_str" - the header value string, perhaps "Bearer eyJhbG....", and +// "after" - the offset into that string after which to begin looking for JWT-legal characters +// // For backwards compatibility, if it finds no suitable string, it returns value_str as-is. // // It is forgiving w.r.t. dots/periods, as the exact syntax will be verified after extraction. @@ -230,12 +234,12 @@ constexpr char kBase64UrlEncodingCharsPlusDot[] = // See RFC-7519 § 2, RFC-7515 § 2, and RFC-4648 "Base-N Encodings" § 5. absl::string_view ExtractorImpl::extractJWT(const absl::string_view& value_str, absl::string_view::size_type after) const { - const auto starting = value_str.find_first_of(kBase64UrlEncodingCharsPlusDot, after); + const auto starting = value_str.find_first_of(ConstantBase64UrlEncodingCharsPlusDot, after); if (starting == value_str.npos) { return value_str; } // There should be two dots (periods; 0x2e) inside the string, but we don't verify that here - auto ending = value_str.find_first_not_of(kBase64UrlEncodingCharsPlusDot, starting); + auto ending = value_str.find_first_not_of(ConstantBase64UrlEncodingCharsPlusDot, starting); if (ending == value_str.npos) { // Base64Url-encoded string occupies the rest of the line return value_str.substr(starting); } From 54577292688915ba596d8240c69b5326422f2b1d Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 11:55:05 -0700 Subject: [PATCH 04/22] Issue 6355: fix style problems in test Duplicated #includes, line-length limit. Signed-off-by: Larry West --- .../filters/http/jwt_authn/extractor_test.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index 25762863d5fb9..3b1cdd0179bf6 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -2,9 +2,6 @@ #include "extensions/filters/http/jwt_authn/extractor.h" -#include "test/test_common/utility.h" - -#include "test/extensions/filters/http/jwt_authn/test_common.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; @@ -218,7 +215,8 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch1) { } TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { - auto headers = TestHeaderMapImpl{{"prefix-header", "CCCDDD=\"and0X3Rva2Vu\",comment=\"fish tag\""}}; + auto headers = TestHeaderMapImpl{{"prefix-header", + "CCCDDD=\"and0X3Rva2Vu\",comment=\"fish tag\""}}; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 1); @@ -232,7 +230,9 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { } TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch3) { - auto headers = TestHeaderMapImpl{{"prefix-header", "creds={\"authLevel\": \"20\", \"CCCDDD\": \"and0X3Rva2Vu\"}"}}; + auto headers = TestHeaderMapImpl{{"prefix-header", + "creds={\"authLevel\": \"20\", \"CCCDDD\": \"and0X3Rva2Vu\"}"} + }; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 2); From 631ef16b26ed494d2ff60942ee3e16109568f18f Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 12:03:04 -0700 Subject: [PATCH 05/22] Issue #6355: auto-fix style in test Signed-off-by: Larry West --- test/extensions/filters/http/jwt_authn/extractor_test.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index 3b1cdd0179bf6..26d23ea156f9d 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -215,8 +215,8 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch1) { } TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { - auto headers = TestHeaderMapImpl{{"prefix-header", - "CCCDDD=\"and0X3Rva2Vu\",comment=\"fish tag\""}}; + auto headers = + TestHeaderMapImpl{{"prefix-header", "CCCDDD=\"and0X3Rva2Vu\",comment=\"fish tag\""}}; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 1); @@ -230,9 +230,8 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { } TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch3) { - auto headers = TestHeaderMapImpl{{"prefix-header", - "creds={\"authLevel\": \"20\", \"CCCDDD\": \"and0X3Rva2Vu\"}"} - }; + auto headers = TestHeaderMapImpl{ + {"prefix-header", "creds={\"authLevel\": \"20\", \"CCCDDD\": \"and0X3Rva2Vu\"}"}}; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 2); From 8add0e5734b999912136baa2db908805334db3a0 Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 12:22:26 -0700 Subject: [PATCH 06/22] Issue #6355: restore #include accidentally removed 2 commits ago (while fixing style in 'extractor_test.cc'). Signed-off-by: Larry West --- test/extensions/filters/http/jwt_authn/extractor_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index 26d23ea156f9d..b9632265bf5d1 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -2,6 +2,8 @@ #include "extensions/filters/http/jwt_authn/extractor.h" +#include "test/test_common/utility.h" + #include "test/extensions/filters/http/jwt_authn/test_common.h" using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; From af5ce5c43e5c9ce591009b08335744437e61e9c6 Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 12:29:03 -0700 Subject: [PATCH 07/22] Issue #6355: pedantic 'spelling' fixes in comments. Hopefully quoting the values will prevent them from being spell-checked. (Though the extractor.cc complaint about "Bearer eyJhbG...." puzzles me.) Signed-off-by: Larry West --- source/extensions/filters/http/jwt_authn/extractor.cc | 2 +- test/extensions/filters/http/jwt_authn/extractor_test.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 6de4ecbb40364..815d00f339775 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -224,7 +224,7 @@ constexpr char ConstantBase64UrlEncodingCharsPlusDot[] = // Base64Url+dot string until first non-Base64Url char. // // The input parameters: -// "value_str" - the header value string, perhaps "Bearer eyJhbG....", and +// "value_str" - the header value string, perhaps "Bearer string....", and // "after" - the offset into that string after which to begin looking for JWT-legal characters // // For backwards compatibility, if it finds no suitable string, it returns value_str as-is. diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index b9632265bf5d1..f1bcd3d63ad59 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -207,7 +207,7 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch1) { auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 1); - // Match issuer 7 with map key as: prefix-header + CCCDDD + // Match issuer 7 with map key as: prefix-header + 'CCCDDD' EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer7")); EXPECT_EQ(tokens[0]->token(), "jwt_token"); @@ -237,11 +237,11 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch3) { auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 2); - // Match issuer 8 with map key as: prefix-header + "CCCDDD" + // Match issuer 8 with map key as: prefix-header + '"CCCDDD"' EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer8")); EXPECT_EQ(tokens[0]->token(), "and0X3Rva2Vu"); - // Match issuer 7 with map key as: prefix-header + CCCDDD + // Match issuer 7 with map key as: prefix-header + 'CCCDDD' EXPECT_TRUE(tokens[1]->isIssuerSpecified("issuer7")); EXPECT_EQ(tokens[1]->token(), "and0X3Rva2Vu"); From 52ff8352602d9c06c393edcc140349dda466722b Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 12:47:59 -0700 Subject: [PATCH 08/22] Issue #6355: auto-fix of header inclusion ordering Signed-off-by: Larry West --- test/extensions/filters/http/jwt_authn/extractor_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index f1bcd3d63ad59..eec94c8d69c6a 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -2,9 +2,8 @@ #include "extensions/filters/http/jwt_authn/extractor.h" -#include "test/test_common/utility.h" - #include "test/extensions/filters/http/jwt_authn/test_common.h" +#include "test/test_common/utility.h" using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication; using ::envoy::config::filter::http::jwt_authn::v2alpha::JwtProvider; From 42cbc4a377adbf086d79bdd3ca57cd0aa51a1862 Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 15:03:26 -0700 Subject: [PATCH 09/22] Issue 6355: per qizwhang review comment, remove... ...redundant tests of removeJwt(). Signed-off-by: Larry West --- .../filters/http/jwt_authn/extractor_test.cc | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index eec94c8d69c6a..4c886512cdf51 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -123,10 +123,6 @@ TEST_F(ExtractorTest, TestDefaultHeaderLocationWithValidJWT) { // Only the issue1 is using default header location. EXPECT_EQ(tokens[0]->token(), GoodToken); EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer1")); - - // Test token remove - tokens[0]->removeJwt(headers); - EXPECT_FALSE(headers.Authorization()); } // Test extracting token from the default query parameter: "access_token" @@ -209,10 +205,6 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch1) { // Match issuer 7 with map key as: prefix-header + 'CCCDDD' EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer7")); EXPECT_EQ(tokens[0]->token(), "jwt_token"); - - // Test token remove - tokens[0]->removeJwt(headers); - EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); } TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { @@ -224,10 +216,6 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch2) { // Match issuer 7 with map key as: prefix-header + AAA EXPECT_TRUE(tokens[0]->isIssuerSpecified("issuer7")); EXPECT_EQ(tokens[0]->token(), "and0X3Rva2Vu"); - - // Test token remove - tokens[0]->removeJwt(headers); - EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); } TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch3) { @@ -243,10 +231,6 @@ TEST_F(ExtractorTest, TestPrefixHeaderFlexibleMatch3) { // Match issuer 7 with map key as: prefix-header + 'CCCDDD' EXPECT_TRUE(tokens[1]->isIssuerSpecified("issuer7")); EXPECT_EQ(tokens[1]->token(), "and0X3Rva2Vu"); - - // Test token remove - tokens[0]->removeJwt(headers); - EXPECT_FALSE(headers.get(Http::LowerCaseString("prefix-header"))); } // Test extracting token from the custom query parameter: "token_param" From 6ad16804aa99d027e6614d6958df1f33c450034e Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 15:04:51 -0700 Subject: [PATCH 10/22] Issue 6355: update api/.../jwt_authn/.../README.md To describe the (updated) functionality of the value_prefix field. Signed-off-by: Larry West --- .../filter/http/jwt_authn/v2alpha/README.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/api/envoy/config/filter/http/jwt_authn/v2alpha/README.md b/api/envoy/config/filter/http/jwt_authn/v2alpha/README.md index 9d083389a5aea..c390a4d5ce506 100644 --- a/api/envoy/config/filter/http/jwt_authn/v2alpha/README.md +++ b/api/envoy/config/filter/http/jwt_authn/v2alpha/README.md @@ -29,3 +29,38 @@ If a custom location is desired, `from_headers` or `from_params` can be used to ## HTTP header to pass successfully verified JWT If a JWT is valid, its payload will be passed to the backend in a new HTTP header specified in `forward_payload_header` field. Its value is base64 encoded JWT payload in JSON. + + +## Further header options + +In addition to the `name` field, which specifies the HTTP header name, +the `from_headers` section can specify an optional `value_prefix` value, as in: + +```yaml + from_headers: + - name: bespoke + value_prefix: jwt_value +``` + +The above will cause the jwt_authn filter to look for the JWT in the `bespoke` header, following the tag `jwt_value`. + +Any non-JWT characters (i.e., anything _other than_ alphanumerics, `_`, `-`, and `.`) will be skipped, +and all following, contiguous, JWT-legal chars will be taken as the JWT. + +This means all of the following will return a JWT of `eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk`: + +```text +bespoke: jwt_value=eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk + +bespoke: {"jwt_value": "eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk"} + +bespoke: beta:true,jwt_value:"eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk",trace=1234 +``` + +The header `name` may be `Authorization`. + +The `value_prefix` must match exactly, i.e., case-sensitively. +If the `value_prefix` is not found, the header is skipped: not considered as a source for a JWT token. + +If there are no JWT-legal characters after the `value_prefix`, the entire string after it +is taken to be the JWT token. This is unlikely to succeed; the error will reported by the JWT parser. \ No newline at end of file From 394fae4b6ef1aecf121b9cd35d49283d8eb91c68 Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 18:05:26 -0700 Subject: [PATCH 11/22] Issue #6355: empty commit to trigger CircleCI (None of the 12 CircleCI tasks had begun in the three hours since the previous commit) Signed-off-by: Larry West From cea55d331d085a8f5be2f9bcf3a5a8ba028e5ae8 Mon Sep 17 00:00:00 2001 From: Larry West Date: Tue, 26 Mar 2019 20:03:40 -0700 Subject: [PATCH 12/22] Issue #6355: another empty commit to trigger CircleCI Code Coverage 'failed' with no actual indication of how or why on earlier job, 189678 - https://circleci.com/gh/envoyproxy/envoy/189678?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link Signed-off-by: Larry West From 07b29766383a305f64d7faa1bfe79183cb5e6777 Mon Sep 17 00:00:00 2001 From: Larry West Date: Wed, 27 Mar 2019 09:07:31 -0700 Subject: [PATCH 13/22] Issue #6355: change input param from const& to const ...for extractJWT() only. I.e., pass by copying the value rather than the reference. Per review comment by lizan. Signed-off-by: Larry West --- source/extensions/filters/http/jwt_authn/extractor.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 815d00f339775..29f1bb629325b 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -103,7 +103,7 @@ class ExtractorImpl : public Extractor { void addProvider(const JwtProvider& provider); // @return what should be the 3-part base64url-encoded substring; see RFC-7519 - absl::string_view extractJWT(const absl::string_view& value_str, + absl::string_view extractJWT(const absl::string_view value_str, absl::string_view::size_type after) const; // HeaderMap value type to store prefix and issuers that specified this @@ -217,7 +217,7 @@ std::vector ExtractorImpl::extract(const Http::HeaderMap& h } // as specified in RFC-4648 § 5, plus dot (period, 0x2e), of which two are required in the JWT -constexpr char ConstantBase64UrlEncodingCharsPlusDot[] = +constexpr absl::string_view ConstantBase64UrlEncodingCharsPlusDot = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; // Returns a token, not a URL: skips non-Base64Url-legal (or dot) characters, collects following @@ -232,7 +232,7 @@ constexpr char ConstantBase64UrlEncodingCharsPlusDot[] = // It is forgiving w.r.t. dots/periods, as the exact syntax will be verified after extraction. // // See RFC-7519 § 2, RFC-7515 § 2, and RFC-4648 "Base-N Encodings" § 5. -absl::string_view ExtractorImpl::extractJWT(const absl::string_view& value_str, +absl::string_view ExtractorImpl::extractJWT(const absl::string_view value_str, absl::string_view::size_type after) const { const auto starting = value_str.find_first_of(ConstantBase64UrlEncodingCharsPlusDot, after); if (starting == value_str.npos) { From 1ea97c9c0cc822213f76f93345e70aa069c74908 Mon Sep 17 00:00:00 2001 From: Larry West Date: Wed, 27 Mar 2019 09:25:39 -0700 Subject: [PATCH 14/22] Issue #6355: change test object initialization ...to use absl::StrCat() rather than C++ string '+' concatenation. Per PR#6384 review comments by lizan. Signed-off-by: Larry West --- test/extensions/filters/http/jwt_authn/extractor_test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index 4c886512cdf51..f8bcfd808276b 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -116,7 +116,8 @@ TEST_F(ExtractorTest, TestDefaultHeaderLocation) { // Test extracting JWT as Bearer token from the default header location: "Authorization" - // using an actual (correctly-formatted) JWT: TEST_F(ExtractorTest, TestDefaultHeaderLocationWithValidJWT) { - auto headers = TestHeaderMapImpl{{"Authorization", "Bearer " + std::string(GoodToken)}}; + auto headers = TestHeaderMapImpl{{absl::StrCat("Authorization"), + absl::StrCat("Bearer ", GoodToken)}}; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 1); From e5f735b035b2005f4446eb9adb6a6e1900c24180 Mon Sep 17 00:00:00 2001 From: Larry West Date: Wed, 27 Mar 2019 09:27:21 -0700 Subject: [PATCH 15/22] Issue #6355: auto-fix style Signed-off-by: Larry West --- test/extensions/filters/http/jwt_authn/extractor_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index f8bcfd808276b..05d9c340d3bd3 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -116,8 +116,8 @@ TEST_F(ExtractorTest, TestDefaultHeaderLocation) { // Test extracting JWT as Bearer token from the default header location: "Authorization" - // using an actual (correctly-formatted) JWT: TEST_F(ExtractorTest, TestDefaultHeaderLocationWithValidJWT) { - auto headers = TestHeaderMapImpl{{absl::StrCat("Authorization"), - absl::StrCat("Bearer ", GoodToken)}}; + auto headers = + TestHeaderMapImpl{{absl::StrCat("Authorization"), absl::StrCat("Bearer ", GoodToken)}}; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 1); From 152e1246245350d609c8eae774cc5415ab274772 Mon Sep 17 00:00:00 2001 From: Larry West Date: Thu, 28 Mar 2019 08:52:38 -0700 Subject: [PATCH 16/22] Issue #6355: change input param from const to const& Reversing a change made earlier. Based on a review comment from lizan. Signed-off-by: Larry West --- source/extensions/filters/http/jwt_authn/extractor.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 29f1bb629325b..0fbf2bd65ce72 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -103,7 +103,7 @@ class ExtractorImpl : public Extractor { void addProvider(const JwtProvider& provider); // @return what should be the 3-part base64url-encoded substring; see RFC-7519 - absl::string_view extractJWT(const absl::string_view value_str, + absl::string_view extractJWT(const absl::string_view& value_str, absl::string_view::size_type after) const; // HeaderMap value type to store prefix and issuers that specified this @@ -232,7 +232,7 @@ constexpr absl::string_view ConstantBase64UrlEncodingCharsPlusDot = // It is forgiving w.r.t. dots/periods, as the exact syntax will be verified after extraction. // // See RFC-7519 § 2, RFC-7515 § 2, and RFC-4648 "Base-N Encodings" § 5. -absl::string_view ExtractorImpl::extractJWT(const absl::string_view value_str, +absl::string_view ExtractorImpl::extractJWT(const absl::string_view& value_str, absl::string_view::size_type after) const { const auto starting = value_str.find_first_of(ConstantBase64UrlEncodingCharsPlusDot, after); if (starting == value_str.npos) { From 7f7ff1bf55c404cdd8aa7152147e6a8690ae718f Mon Sep 17 00:00:00 2001 From: Larry West Date: Mon, 1 Apr 2019 11:36:18 -0700 Subject: [PATCH 17/22] Issue #6355: pass string_view as recommended See the Absiel Tip of the Week #1: https://abseil.io/tips/1 for explanation. Change requested by lizan. Signed-off-by: Larry West --- source/extensions/filters/http/jwt_authn/extractor.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 0fbf2bd65ce72..9e0bd9ea64043 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -103,7 +103,7 @@ class ExtractorImpl : public Extractor { void addProvider(const JwtProvider& provider); // @return what should be the 3-part base64url-encoded substring; see RFC-7519 - absl::string_view extractJWT(const absl::string_view& value_str, + absl::string_view extractJWT(absl::string_view value_str, absl::string_view::size_type after) const; // HeaderMap value type to store prefix and issuers that specified this @@ -232,7 +232,7 @@ constexpr absl::string_view ConstantBase64UrlEncodingCharsPlusDot = // It is forgiving w.r.t. dots/periods, as the exact syntax will be verified after extraction. // // See RFC-7519 § 2, RFC-7515 § 2, and RFC-4648 "Base-N Encodings" § 5. -absl::string_view ExtractorImpl::extractJWT(const absl::string_view& value_str, +absl::string_view ExtractorImpl::extractJWT(absl::string_view value_str, absl::string_view::size_type after) const { const auto starting = value_str.find_first_of(ConstantBase64UrlEncodingCharsPlusDot, after); if (starting == value_str.npos) { From 87bc5c265e6ec71e20e83c39a87f89f9bdcb4ade Mon Sep 17 00:00:00 2001 From: Larry West Date: Mon, 1 Apr 2019 14:25:51 -0700 Subject: [PATCH 18/22] Issue #6355: empty commit to trigger re-build earlier code coverage build failed (didn't run properly) - https://circleci.com/gh/envoyproxy/envoy/192105 Signed-off-by: Larry West From 4b89509462c0f58d67cc330e08e040832665d04e Mon Sep 17 00:00:00 2001 From: larrywest Date: Wed, 3 Apr 2019 12:27:38 -0700 Subject: [PATCH 19/22] Mention enhancement of jwt_authn filter (PR#6384) See Issue #6355 Signed-off-by: Larry West --- docs/root/intro/version_history.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index b307ddc5d1bdb..845bdded427a9 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -46,6 +46,10 @@ Version history * mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to ::ref:`MySQL proxy` for more details. * http: added :ref:`max request headers size `. The default behaviour is unchanged. * http: added modifyDecodingBuffer/modifyEncodingBuffer to allow modifying the buffered request/response data. +* http: added encodeComplete/decodeComplete. These are invoked at the end of the stream, after all data has been encoded/decoded respectively. Default implementation is a no-op. +* http/jwt_authn: make parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` +* outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. +* mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy` for more details. * performance: new buffer implementation (disabled by default; to test it, add "--use-libevent-buffers 0" to the command-line arguments when starting Envoy). * http: added encodeComplete/decodeComplete. These are invoked at the end of the stream, after all data has been encoded/decoded respectively. Default implementation is a no-op. * ratelimit: removed deprecated rate limit configuration from bootstrap. From a3110c768620c1d004362ff72c5bdc57b66a0a71 Mon Sep 17 00:00:00 2001 From: larrywest Date: Wed, 3 Apr 2019 12:40:11 -0700 Subject: [PATCH 20/22] Fix to pass check_format.py first token must be alphabetic, no "/". Signed-off-by: Larry West --- docs/root/intro/version_history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 845bdded427a9..f7aff401f8898 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -47,7 +47,7 @@ Version history * http: added :ref:`max request headers size `. The default behaviour is unchanged. * http: added modifyDecodingBuffer/modifyEncodingBuffer to allow modifying the buffered request/response data. * http: added encodeComplete/decodeComplete. These are invoked at the end of the stream, after all data has been encoded/decoded respectively. Default implementation is a no-op. -* http/jwt_authn: make parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` +* http: make jwt_authn parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` * outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. * mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy` for more details. * performance: new buffer implementation (disabled by default; to test it, add "--use-libevent-buffers 0" to the command-line arguments when starting Envoy). From 8f94bf731153a9c011865bab0df6cefad12786f7 Mon Sep 17 00:00:00 2001 From: Larry West Date: Wed, 3 Apr 2019 14:02:49 -0700 Subject: [PATCH 21/22] Issue #6355: change version_history line to "jwt_authn:" begin with "jwt_authn:" rather than "http:" Signed-off-by: Larry West --- docs/root/intro/version_history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 217201fc3b07c..b8dda546e1f24 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -50,7 +50,7 @@ Version history * http: added :ref:`max request headers size `. The default behaviour is unchanged. * http: added modifyDecodingBuffer/modifyEncodingBuffer to allow modifying the buffered request/response data. * http: added encodeComplete/decodeComplete. These are invoked at the end of the stream, after all data has been encoded/decoded respectively. Default implementation is a no-op. -* http: make jwt_authn parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` +* jwt_authn: make filter's parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123`` * outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. * mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy` for more details. * performance: new buffer implementation (disabled by default; to test it, add "--use-libevent-buffers 0" to the command-line arguments when starting Envoy). From d752369893e87448614c6dc7bf06798e69c4c4f8 Mon Sep 17 00:00:00 2001 From: Larry West Date: Wed, 3 Apr 2019 14:07:00 -0700 Subject: [PATCH 22/22] Issue #6355: remove 4 duplicated lines Probably an artifact of my merge. Signed-off-by: Larry West --- docs/root/intro/version_history.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index b8dda546e1f24..724e153f5b28a 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -54,10 +54,6 @@ Version history * outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. * mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy` for more details. * performance: new buffer implementation (disabled by default; to test it, add "--use-libevent-buffers 0" to the command-line arguments when starting Envoy). -* http: added encodeComplete/decodeComplete. These are invoked at the end of the stream, after all data has been encoded/decoded respectively. Default implementation is a no-op. -* outlier_detection: added support for :ref:`outlier detection event protobuf-based logging `. -* mysql: added a MySQL proxy filter that is capable of parsing SQL queries over MySQL wire protocol. Refer to :ref:`MySQL proxy` for more details. -* performance: new buffer implementation (disabled by default; to test it, add "--use-libevent-buffers 0" to the command-line arguments when starting Envoy). * ratelimit: removed deprecated rate limit configuration from bootstrap. * redis: added :ref:`hashtagging ` to guarantee a given key's upstream. * redis: added :ref:`latency stats ` for commands.