From 65f7e613ba06c49c31f86f0142a5acac05086791 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Sun, 15 Aug 2021 23:07:04 +0530 Subject: [PATCH 1/8] Support extraction of JWT from Cookies in JWT Extension Added "from_cookies" config directive to jwt_authn that enables JWT extraction from request cookies. Testing: unit tests Signed-off-by: Shubham Patil --- .../filters/http/jwt_authn/v3/config.proto | 15 ++++- .../http/jwt_authn/v4alpha/config.proto | 15 ++++- .../http/http_filters/jwt_authn_filter.rst | 1 + docs/root/version_history/current.rst | 1 + .../filters/http/jwt_authn/v3/config.proto | 15 ++++- .../http/jwt_authn/v4alpha/config.proto | 15 ++++- source/common/http/utility.cc | 33 +++++++++ source/common/http/utility.h | 7 ++ .../filters/http/jwt_authn/extractor.cc | 67 +++++++++++++++---- .../filters/http/jwt_authn/extractor_test.cc | 33 +++++++++ 10 files changed, 186 insertions(+), 16 deletions(-) diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 9e658ed8627ff..5fb16ab120bb0 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -52,7 +52,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // cache_duration: // seconds: 300 // -// [#next-free-field: 13] +// [#next-free-field: 14] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider"; @@ -224,6 +224,19 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; + + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 57c6630c940e7..2b652b814246f 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -52,7 +52,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 13] +// [#next-free-field: 14] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -224,6 +224,19 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; + + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/docs/root/configuration/http/http_filters/jwt_authn_filter.rst b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst index 905aaa6aecc43..b98e5f73edb91 100644 --- a/docs/root/configuration/http/http_filters/jwt_authn_filter.rst +++ b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst @@ -40,6 +40,7 @@ JwtProvider * *forward*: if true, JWT will be forwarded to the upstream. * *from_headers*: extract JWT from HTTP headers. * *from_params*: extract JWT from query parameters. +* *from_cookies*: extract JWT from HTTP request cookies. * *forward_payload_header*: forward the JWT payload in the specified HTTP header. * *jwt_cache_config*: Enables JWT cache, its size can be specified by *jwt_cache_size*. Only valid JWT tokens are cached. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 733a84bd04d1e..fc6b9d548dd5e 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -70,6 +70,7 @@ New Features * http: added :ref:`string_match ` in the header matcher. * http: added support for :ref:`max_requests_per_connection ` for both upstream and downstream connections. * jwt_authn: added support for :ref:`Jwt Cache ` and its size can be specified by :ref:`jwt_cache_size `. +* jwt_authn: added support for extracting JWTs from request cookies using :ref:`from_cookies ` * listener: new listener metric ``downstream_cx_transport_socket_connect_timeout`` to track transport socket timeouts. * rbac: added :ref:`destination_port_range ` for matching range of destination ports. * thrift_proxy: added support for :ref:`mirroring requests `. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 9e658ed8627ff..5fb16ab120bb0 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -52,7 +52,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // cache_duration: // seconds: 300 // -// [#next-free-field: 13] +// [#next-free-field: 14] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider"; @@ -224,6 +224,19 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; + + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 57c6630c940e7..2b652b814246f 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -52,7 +52,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 13] +// [#next-free-field: 14] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -224,6 +224,19 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; + + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 9fa5f12098952..f6424e958aef6 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -296,6 +296,39 @@ std::string parseCookie(const HeaderMap& headers, const std::string& key, return EMPTY_STRING; } +std::map Utility::parseCookies(const RequestHeaderMap& headers) { + std::map cookies; + const Http::HeaderMap::GetResult cookie_headers = headers.get(Http::Headers::get().Cookie); + + for (size_t index = 0; index < cookie_headers.size(); index++) { + auto cookie_header_value = cookie_headers[index]->value().getStringView(); + + // Split the cookie header into individual cookies. + for (const auto& s : StringUtil::splitToken(cookie_header_value, ";")) { + // Find the key part of the cookie (i.e. the name of the cookie). + size_t first_non_space = s.find_first_not_of(' '); + size_t equals_index = s.find('='); + if (equals_index == absl::string_view::npos) { + // The cookie is malformed if it does not have an `=`. Continue + // checking other cookies in this header. + continue; + } + absl::string_view k = s.substr(first_non_space, equals_index - first_non_space); + absl::string_view v = s.substr(equals_index + 1, s.size() - 1); + + // Cookie values may be wrapped in double quotes. + // https://tools.ietf.org/html/rfc6265#section-4.1.1 + if (v.size() >= 2 && v.back() == '"' && v[0] == '"') { + v = v.substr(1, v.size() - 2); + } + + cookies.emplace(std::string{k}, std::string{v}); + } + } + + return cookies; +} + bool Utility::Url::initialize(absl::string_view absolute_url, bool is_connect) { struct http_parser_url u; http_parser_url_init(&u); diff --git a/source/common/http/utility.h b/source/common/http/utility.h index 7929e687e5802..e8b9dd911999b 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -255,6 +255,13 @@ std::string stripQueryString(const HeaderString& path); **/ std::string parseCookieValue(const HeaderMap& headers, const std::string& key); +/** + * Parse cookies from header into a map. + * @param headers supplies the headers to get cookies from. + * @return std::map cookie map. + **/ +std::map parseCookies(const RequestHeaderMap& headers); + /** * Parse a particular value out of a set-cookie * @param headers supplies the headers to get the set-cookie from. diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 8b78af98d0a9b..1bac943c487b8 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -111,6 +111,17 @@ class JwtParamLocation : public JwtLocationBase { } }; +// The JwtLocation for cookie extraction. +class JwtCookieLocation : public JwtLocationBase { +public: + JwtCookieLocation(const std::string& token, const JwtIssuerChecker& issuer_checker) + : JwtLocationBase(token, issuer_checker) {} + + void removeJwt(Http::HeaderMap&) const override { + // TODO(theshubhamp): remove JWT from cookies. + } +}; + /** * The class implements Extractor interface * @@ -133,6 +144,8 @@ class ExtractorImpl : public Logger::Loggable, public Extractor const std::string& value_prefix); // add a query param config void addQueryParamConfig(const std::string& issuer, const std::string& param); + // add a query param config + void addCookieConfig(const std::string& issuer, const std::string& cookie); // ctor helper for a jwt provider config void addProvider(const JwtProvider& provider); @@ -164,6 +177,14 @@ class ExtractorImpl : public Logger::Loggable, public Extractor // The map of a parameter key to set of issuers specified the parameter std::map param_locations_; + // CookieMap value type to store issuers that specified this cookie. + struct CookieLocationSpec { + // Issuers that specified this param. + JwtIssuerChecker issuer_checker_; + }; + // The map of a cookie key to set of issuers specified the cookie. + std::map cookie_locations_; + std::vector forward_payload_headers_; }; @@ -183,6 +204,9 @@ void ExtractorImpl::addProvider(const JwtProvider& provider) { for (const std::string& param : provider.from_params()) { addQueryParamConfig(provider.issuer(), param); } + for (const std::string& cookie : provider.from_cookies()) { + addCookieConfig(provider.issuer(), cookie); + } // If not specified, use default locations. if (provider.from_headers().empty() && provider.from_params().empty()) { addHeaderConfig(provider.issuer(), Http::CustomHeaders::get().Authorization, @@ -210,6 +234,11 @@ void ExtractorImpl::addQueryParamConfig(const std::string& issuer, const std::st param_location_spec.issuer_checker_.add(issuer); } +void ExtractorImpl::addCookieConfig(const std::string& issuer, const std::string& cookie) { + auto& cookie_location_spec = cookie_locations_[cookie]; + cookie_location_spec.issuer_checker_.add(issuer); +} + std::vector ExtractorImpl::extract(const Http::RequestHeaderMap& headers) const { std::vector tokens; @@ -235,22 +264,36 @@ ExtractorImpl::extract(const Http::RequestHeaderMap& headers) const { } } - // If no query parameter locations specified, or Path() is null, bail out - if (param_locations_.empty() || headers.Path() == nullptr) { - return tokens; + // Check query parameter locations only if query parameter locations specified and Path() is not + // null + if (!param_locations_.empty() && headers.Path() != nullptr) { + const auto& params = Http::Utility::parseAndDecodeQueryString(headers.getPathValue()); + for (const auto& location_it : param_locations_) { + const auto& param_key = location_it.first; + const auto& location_spec = location_it.second; + const auto& it = params.find(param_key); + if (it != params.end()) { + tokens.push_back(std::make_unique( + it->second, location_spec.issuer_checker_, param_key)); + } + } } - // Check query parameter locations. - const auto& params = Http::Utility::parseAndDecodeQueryString(headers.getPathValue()); - for (const auto& location_it : param_locations_) { - const auto& param_key = location_it.first; - const auto& location_spec = location_it.second; - const auto& it = params.find(param_key); - if (it != params.end()) { - tokens.push_back(std::make_unique( - it->second, location_spec.issuer_checker_, param_key)); + // Check cookie locations. + if (!cookie_locations_.empty()) { + const auto& cookies = Http::Utility::parseCookies(headers); + + for (const auto& location_it : cookie_locations_) { + const auto& cookie_key = location_it.first; + const auto& location_spec = location_it.second; + const auto& it = cookies.find(cookie_key); + if (it != cookies.end()) { + tokens.push_back( + std::make_unique(it->second, location_spec.issuer_checker_)); + } } } + return tokens; } diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index b7dee6776f559..bd0a1f8a020ca 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -54,6 +54,15 @@ const char ExampleConfig[] = R"( from_headers: - name: prefix-header value_prefix: '"CCCDDD"' + provider9: + issuer: issuer9 + from_cookies: + - token-cookie + - token-cookie-2 + provider10: + issuer: issuer10 + from_cookies: + - token-cookie-3 )"; class ExtractorTest : public testing::Test { @@ -265,6 +274,30 @@ TEST_F(ExtractorTest, TestCustomParamToken) { tokens[0]->removeJwt(headers); } +// Test extracting token from a cookie +TEST_F(ExtractorTest, TestCookieToken) { + auto headers = TestRequestHeaderMapImpl{ + {"cookie", "token-cookie=token-cookie-value; token-cookie-2=token-cookie-value-2"}, + {"cookie", "token-cookie-3=token-cookie-value-3"}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 3); + + // only issuer9 has specified "token-cookie" cookie location. + EXPECT_EQ(tokens[0]->token(), "token-cookie-value"); + EXPECT_TRUE(tokens[0]->isIssuerAllowed("issuer9")); + EXPECT_FALSE(tokens[0]->isIssuerAllowed("issuer10")); + + // only issuer9 has specified "token-cookie-2" cookie location. + EXPECT_EQ(tokens[1]->token(), "token-cookie-value-2"); + EXPECT_TRUE(tokens[1]->isIssuerAllowed("issuer9")); + EXPECT_FALSE(tokens[1]->isIssuerAllowed("issuer10")); + + // only issuer10 has specified "token-cookie-3" cookie location. + EXPECT_EQ(tokens[2]->token(), "token-cookie-value-3"); + EXPECT_TRUE(tokens[2]->isIssuerAllowed("issuer10")); + EXPECT_FALSE(tokens[2]->isIssuerAllowed("issuer9")); +} + // Test extracting multiple tokens. TEST_F(ExtractorTest, TestMultipleTokens) { auto headers = TestRequestHeaderMapImpl{ From 3d37b65518b2ffc978031969a3d2512682d3446e Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Mon, 16 Aug 2021 00:31:12 +0530 Subject: [PATCH 2/8] End release note entry with a "." Signed-off-by: Shubham Patil --- docs/root/version_history/current.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index fc6b9d548dd5e..96570d4d13125 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -70,7 +70,7 @@ New Features * http: added :ref:`string_match ` in the header matcher. * http: added support for :ref:`max_requests_per_connection ` for both upstream and downstream connections. * jwt_authn: added support for :ref:`Jwt Cache ` and its size can be specified by :ref:`jwt_cache_size `. -* jwt_authn: added support for extracting JWTs from request cookies using :ref:`from_cookies ` +* jwt_authn: added support for extracting JWTs from request cookies using :ref:`from_cookies `. * listener: new listener metric ``downstream_cx_transport_socket_connect_timeout`` to track transport socket timeouts. * rbac: added :ref:`destination_port_range ` for matching range of destination ports. * thrift_proxy: added support for :ref:`mirroring requests `. From 09eaa9bc1253a25991d57135d750a7643ee4f77c Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Mon, 16 Aug 2021 22:05:10 +0530 Subject: [PATCH 3/8] Modify TestCookieToken ExtractorTest to cover quoted cookie values Signed-off-by: Shubham Patil --- test/extensions/filters/http/jwt_authn/extractor_test.cc | 2 +- 1 file changed, 1 insertion(+), 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 bd0a1f8a020ca..2adaf35e7e463 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -278,7 +278,7 @@ TEST_F(ExtractorTest, TestCustomParamToken) { TEST_F(ExtractorTest, TestCookieToken) { auto headers = TestRequestHeaderMapImpl{ {"cookie", "token-cookie=token-cookie-value; token-cookie-2=token-cookie-value-2"}, - {"cookie", "token-cookie-3=token-cookie-value-3"}}; + {"cookie", "token-cookie-3=\"token-cookie-value-3\""}}; auto tokens = extractor_->extract(headers); EXPECT_EQ(tokens.size(), 3); From c9f88efe4fd04b5c723ace6307c9917d3e1ce243 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 17 Aug 2021 23:28:05 +0530 Subject: [PATCH 4/8] Add TODO for cookie iterator rework in parseCookies Signed-off-by: Shubham Patil --- source/common/http/utility.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index f6424e958aef6..c843b2ac52aa1 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -297,6 +297,8 @@ std::string parseCookie(const HeaderMap& headers, const std::string& key, } std::map Utility::parseCookies(const RequestHeaderMap& headers) { + // TODO(theshubhamp): Replace the deuplicated logic from parseCookie(...) with a cookie iterator. + std::map cookies; const Http::HeaderMap::GetResult cookie_headers = headers.get(Http::Headers::get().Cookie); From 72ee06c14c3ee2330b2e95b51bf74aee75f1d52d Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 17 Aug 2021 23:43:06 +0530 Subject: [PATCH 5/8] Fix spellings to make pre-check happy Signed-off-by: Shubham Patil --- source/common/http/utility.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index c843b2ac52aa1..3534f05a09f2b 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -297,7 +297,7 @@ std::string parseCookie(const HeaderMap& headers, const std::string& key, } std::map Utility::parseCookies(const RequestHeaderMap& headers) { - // TODO(theshubhamp): Replace the deuplicated logic from parseCookie(...) with a cookie iterator. + // TODO(theshubhamp): Replace this duplicated logic from parseCookie(...) with a cookie iterator. std::map cookies; const Http::HeaderMap::GetResult cookie_headers = headers.get(Http::Headers::get().Cookie); From 1a958afbaa0ee7f9c85298845ce10b0c0e437267 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Sat, 21 Aug 2021 22:30:13 +0530 Subject: [PATCH 6/8] jwt_authn proto reordering & NOT_IMPLEMENTED_GCOVR_EXCL_LINE usage Reordered `from_cookies` to be after `from_params` in proto. Added NOT_IMPLEMENTED_GCOVR_EXCL_LINE in JwtCookieLocation::removeJwt(..) Signed-off-by: Shubham Patil --- .../filters/http/jwt_authn/v3/config.proto | 26 +++++++++---------- .../http/jwt_authn/v4alpha/config.proto | 26 +++++++++---------- .../filters/http/jwt_authn/v3/config.proto | 26 +++++++++---------- .../http/jwt_authn/v4alpha/config.proto | 26 +++++++++---------- .../filters/http/jwt_authn/extractor.cc | 1 + 5 files changed, 53 insertions(+), 52 deletions(-) diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 5fb16ab120bb0..9718dbe0550ab 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -181,6 +181,19 @@ message JwtProvider { // repeated string from_params = 7; + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; + // This field specifies the header name to forward a successfully verified JWT payload to the // backend. The forwarded data is:: // @@ -224,19 +237,6 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; - - // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. - // - // For example, if config is: - // - // .. code-block:: yaml - // - // from_cookies: - // - auth-token - // - // Then JWT will be extracted from `auth-token` cookie in the request. - // - repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 2b652b814246f..b7eb881de0998 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -181,6 +181,19 @@ message JwtProvider { // repeated string from_params = 7; + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; + // This field specifies the header name to forward a successfully verified JWT payload to the // backend. The forwarded data is:: // @@ -224,19 +237,6 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; - - // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. - // - // For example, if config is: - // - // .. code-block:: yaml - // - // from_cookies: - // - auth-token - // - // Then JWT will be extracted from `auth-token` cookie in the request. - // - repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 5fb16ab120bb0..9718dbe0550ab 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -181,6 +181,19 @@ message JwtProvider { // repeated string from_params = 7; + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; + // This field specifies the header name to forward a successfully verified JWT payload to the // backend. The forwarded data is:: // @@ -224,19 +237,6 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; - - // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. - // - // For example, if config is: - // - // .. code-block:: yaml - // - // from_cookies: - // - auth-token - // - // Then JWT will be extracted from `auth-token` cookie in the request. - // - repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 2b652b814246f..b7eb881de0998 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -181,6 +181,19 @@ message JwtProvider { // repeated string from_params = 7; + // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_cookies: + // - auth-token + // + // Then JWT will be extracted from `auth-token` cookie in the request. + // + repeated string from_cookies = 13; + // This field specifies the header name to forward a successfully verified JWT payload to the // backend. The forwarded data is:: // @@ -224,19 +237,6 @@ message JwtProvider { // Enables JWT cache, its size is specified by *jwt_cache_size*. // Only valid JWT tokens are cached. JwtCacheConfig jwt_cache_config = 12; - - // JWT is sent in a cookie. `from_cookies` represents the cookie names to extract from. - // - // For example, if config is: - // - // .. code-block:: yaml - // - // from_cookies: - // - auth-token - // - // Then JWT will be extracted from `auth-token` cookie in the request. - // - repeated string from_cookies = 13; } // This message specifies JWT Cache configuration. diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 1bac943c487b8..b0b0927723f8d 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -119,6 +119,7 @@ class JwtCookieLocation : public JwtLocationBase { void removeJwt(Http::HeaderMap&) const override { // TODO(theshubhamp): remove JWT from cookies. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } }; From e61bab0d8bb3b27df42a031d888a07dbd060effd Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 31 Aug 2021 16:57:03 +0530 Subject: [PATCH 7/8] Remove older parseCookies implementation Signed-off-by: Shubham Patil --- source/common/http/utility.cc | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 6893e74c8462a..9a803b9d8e906 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -329,41 +329,6 @@ Utility::parseCookies(const RequestHeaderMap& headers, return cookies; } -std::map Utility::parseCookies(const RequestHeaderMap& headers) { - // TODO(theshubhamp): Replace this duplicated logic from parseCookie(...) with a cookie iterator. - - std::map cookies; - const Http::HeaderMap::GetResult cookie_headers = headers.get(Http::Headers::get().Cookie); - - for (size_t index = 0; index < cookie_headers.size(); index++) { - auto cookie_header_value = cookie_headers[index]->value().getStringView(); - - // Split the cookie header into individual cookies. - for (const auto& s : StringUtil::splitToken(cookie_header_value, ";")) { - // Find the key part of the cookie (i.e. the name of the cookie). - size_t first_non_space = s.find_first_not_of(' '); - size_t equals_index = s.find('='); - if (equals_index == absl::string_view::npos) { - // The cookie is malformed if it does not have an `=`. Continue - // checking other cookies in this header. - continue; - } - absl::string_view k = s.substr(first_non_space, equals_index - first_non_space); - absl::string_view v = s.substr(equals_index + 1, s.size() - 1); - - // Cookie values may be wrapped in double quotes. - // https://tools.ietf.org/html/rfc6265#section-4.1.1 - if (v.size() >= 2 && v.back() == '"' && v[0] == '"') { - v = v.substr(1, v.size() - 2); - } - - cookies.emplace(std::string{k}, std::string{v}); - } - } - - return cookies; -} - bool Utility::Url::initialize(absl::string_view absolute_url, bool is_connect) { struct http_parser_url u; http_parser_url_init(&u); From 883eb218f6e1661330fc2f8e9912bbb6745e8e26 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 31 Aug 2021 18:00:25 +0530 Subject: [PATCH 8/8] Use parseCookies with filter predicate in jwt_auth cookie extraction Signed-off-by: Shubham Patil --- source/extensions/filters/http/jwt_authn/BUILD | 1 + source/extensions/filters/http/jwt_authn/extractor.cc | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/BUILD b/source/extensions/filters/http/jwt_authn/BUILD index 50f5952b2f707..915011c82f4b8 100644 --- a/source/extensions/filters/http/jwt_authn/BUILD +++ b/source/extensions/filters/http/jwt_authn/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( deps = [ "//source/common/http:header_utility_lib", "//source/common/http:utility_lib", + "@com_google_absl//absl/container:btree", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index b0b0927723f8d..bfa03f0ac4f05 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -10,7 +10,7 @@ #include "source/common/http/utility.h" #include "source/common/singleton/const_singleton.h" -#include "absl/container/node_hash_set.h" +#include "absl/container/btree_map.h" #include "absl/strings/match.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtProvider; @@ -184,7 +184,7 @@ class ExtractorImpl : public Logger::Loggable, public Extractor JwtIssuerChecker issuer_checker_; }; // The map of a cookie key to set of issuers specified the cookie. - std::map cookie_locations_; + absl::btree_map cookie_locations_; std::vector forward_payload_headers_; }; @@ -282,7 +282,8 @@ ExtractorImpl::extract(const Http::RequestHeaderMap& headers) const { // Check cookie locations. if (!cookie_locations_.empty()) { - const auto& cookies = Http::Utility::parseCookies(headers); + const auto& cookies = Http::Utility::parseCookies( + headers, [&](absl::string_view k) -> bool { return cookie_locations_.contains(k); }); for (const auto& location_it : cookie_locations_) { const auto& cookie_key = location_it.first;