Skip to content
Merged
15 changes: 14 additions & 1 deletion api/envoy/extensions/filters/http/jwt_authn/v3/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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::
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ New Features
* http: added support for :ref:`max_requests_per_connection <envoy_v3_api_field_config.core.v3.HttpProtocolOptions.max_requests_per_connection>` for both upstream and downstream connections.
* http: sanitizing the referer header as documented :ref:`here <config_http_conn_man_headers_referer>`. This feature can be temporarily turned off by setting runtime guard ``envoy.reloadable_features.sanitize_http_header_referer`` to false.
* jwt_authn: added support for :ref:`Jwt Cache <envoy_v3_api_field_extensions.filters.http.jwt_authn.v3.JwtProvider.jwt_cache_config>` and its size can be specified by :ref:`jwt_cache_size <envoy_v3_api_field_extensions.filters.http.jwt_authn.v3.JwtCacheConfig.jwt_cache_size>`.
* jwt_authn: added support for extracting JWTs from request cookies using :ref:`from_cookies <envoy_v3_api_field_extensions.filters.http.jwt_authn.v3.JwtProvider.from_cookies>`.
* listener: new listener metric ``downstream_cx_transport_socket_connect_timeout`` to track transport socket timeouts.
* rbac: added :ref:`destination_port_range <envoy_v3_api_field_config.rbac.v3.Permission.destination_port_range>` for matching range of destination ports.
* route config: added :ref:`dynamic_metadata <envoy_v3_api_field_config.route.v3.RouteMatch.dynamic_metadata>` for routing based on dynamic metadata.
Expand Down

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

1 change: 1 addition & 0 deletions source/extensions/filters/http/jwt_authn/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
Expand Down
71 changes: 58 additions & 13 deletions source/extensions/filters/http/jwt_authn/extractor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -111,6 +111,18 @@ 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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use NOT_IMPLEMENTED_GCOVR_EXCL_LINE

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added below the comment

NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
}
};

/**
* The class implements Extractor interface
*
Expand All @@ -133,6 +145,8 @@ class ExtractorImpl : public Logger::Loggable<Logger::Id::jwt>, 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);

Expand Down Expand Up @@ -164,6 +178,14 @@ class ExtractorImpl : public Logger::Loggable<Logger::Id::jwt>, public Extractor
// The map of a parameter key to set of issuers specified the parameter
std::map<std::string, ParamLocationSpec> 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.
absl::btree_map<std::string, CookieLocationSpec> cookie_locations_;

std::vector<LowerCaseString> forward_payload_headers_;
};

Expand All @@ -183,6 +205,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,
Expand Down Expand Up @@ -210,6 +235,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<JwtLocationConstPtr>
ExtractorImpl::extract(const Http::RequestHeaderMap& headers) const {
std::vector<JwtLocationConstPtr> tokens;
Expand All @@ -235,22 +265,37 @@ 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<const JwtParamLocation>(
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<const JwtParamLocation>(
it->second, location_spec.issuer_checker_, param_key));
// Check cookie locations.
if (!cookie_locations_.empty()) {
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;
const auto& location_spec = location_it.second;
const auto& it = cookies.find(cookie_key);
if (it != cookies.end()) {
tokens.push_back(
std::make_unique<const JwtCookieLocation>(it->second, location_spec.issuer_checker_));
}
}
}

return tokens;
}

Expand Down
33 changes: 33 additions & 0 deletions test/extensions/filters/http/jwt_authn/extractor_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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{
Expand Down