Skip to content

Commit

Permalink
jwt_authn: update to jwt_verify_lib with 1 minute clock skew (envoypr…
Browse files Browse the repository at this point in the history
…oxy#13872)

When verifying Jwt clock constraint,  it is recommend to use some clock skew.   grpc is using 1 minute clock [skew](https://github.com/grpc/grpc/blob/4645da201ae2c7d0b15fe56d86b41354fa4af0ca/src/core/lib/security/credentials/jwt/jwt_verifier.cc#L388-L389).

[jwt_verify_lib](google/jwt_verify_lib#57) has been updated to add 1 minute clock skew.

In the old code,  time constraint verification is done in jwt_authn filter, and jwt_verify_lib::verifyJwt() is doing time constraint verification again.

Change  jwt_verify_lib to split the time constraint verification to Jwt class so it can be called separately. And call verify() without the time checking.

Risk Level: None
Testing:  unit-test is done in jwt_verify_lib repo
Docs Changes: None
Release Notes: Added

Signed-off-by: Wayne Zhang <[email protected]>
Signed-off-by: Qin Qin <[email protected]>
  • Loading branch information
qiwzhang authored and qqustc committed Nov 24, 2020
1 parent fed9841 commit 061f38b
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 30 deletions.
6 changes: 5 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 @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// cache_duration:
// seconds: 300
//
// [#next-free-field: 10]
// [#next-free-field: 11]
message JwtProvider {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider";
Expand Down Expand Up @@ -191,6 +191,10 @@ message JwtProvider {
// exp: 1501281058
//
string payload_in_metadata = 9;

// Specify the clock skew in seconds when verifying JWT time constraint,
// such as `exp`, and `nbf`. If not specified, default is 60 seconds.
uint32 clock_skew_seconds = 10;
}

// This message specifies how to fetch JWKS from remote and how to cache it.
Expand Down

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

6 changes: 3 additions & 3 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -408,13 +408,13 @@ REPOSITORY_LOCATIONS_SPEC = dict(
project_name = "jwt_verify_lib",
project_desc = "JWT verification library for C++",
project_url = "https://github.com/google/jwt_verify_lib",
version = "7276a339af8426724b744216f619c99152f8c141",
sha256 = "f1fde4f3ebb3b2d841332c7a02a4b50e0529a19709934c63bc6208d1bbe28fb1",
version = "28efec2e4df1072db0ed03597591360ec9f80aac",
sha256 = "7a5c35b7cbf633398503ae12cad8c2833e92b3a796eed68b6256d22d51ace5e1",
strip_prefix = "jwt_verify_lib-{version}",
urls = ["https://github.com/google/jwt_verify_lib/archive/{version}.tar.gz"],
use_category = ["dataplane_ext"],
extensions = ["envoy.filters.http.jwt_authn"],
release_date = "2020-07-10",
release_date = "2020-11-04",
cpe = "N/A",
),
com_github_nodejs_http_parser = dict(
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 @@ -17,6 +17,7 @@ Minor Behavior Changes
* ext_authz filter: disable `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` by default.
* ext_authz filter: the deprecated field :ref:`use_alpha <envoy_api_field_config.filter.http.ext_authz.v2.ExtAuthz.use_alpha>` is no longer supported and cannot be set anymore.
* grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before.
* jwt_authn filter: added support of Jwt time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds <envoy_v3_api_field_extensions.filters.http.jwt_authn.v3.JwtProvider.clock_skew_seconds>` to configure it.
* memory: enable new tcmalloc with restartable sequences for aarch64 builds.
* tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults.
* watchdog: the watchdog action :ref:`abort_action <envoy_v3_api_msg_watchdog.v3alpha.AbortActionConfig>` is now the default action to terminate the process if watchdog kill / multikill is enabled.
Expand Down

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

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

37 changes: 14 additions & 23 deletions source/extensions/filters/http/jwt_authn/authenticator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void AuthenticatorImpl::startVerify() {

jwt_ = std::make_unique<::google::jwt_verify::Jwt>();
ENVOY_LOG(debug, "{}: Parse Jwt {}", name(), curr_token_->token());
const Status status = jwt_->parseFromString(curr_token_->token());
Status status = jwt_->parseFromString(curr_token_->token());
if (status != Status::Ok) {
doneWithStatus(status);
return;
Expand All @@ -163,33 +163,23 @@ void AuthenticatorImpl::startVerify() {
}
}

// TODO(qiwzhang): Cross-platform-wise the below unix_timestamp code is wrong as the
// epoch is not guaranteed to be defined as the unix epoch. We should use
// the abseil time functionality instead or use the jwt_verify_lib to check
// the validity of a JWT.
// Check "exp" claim.
const uint64_t unix_timestamp =
std::chrono::duration_cast<std::chrono::seconds>(timeSource().systemTime().time_since_epoch())
.count();
// If the nbf claim does *not* appear in the JWT, then the nbf field is defaulted
// to 0.
if (jwt_->nbf_ > unix_timestamp) {
doneWithStatus(Status::JwtNotYetValid);
return;
}
// If the exp claim does *not* appear in the JWT then the exp field is defaulted
// to 0.
if (jwt_->exp_ > 0 && jwt_->exp_ < unix_timestamp) {
doneWithStatus(Status::JwtExpired);
return;
}

// Check the issuer is configured or not.
jwks_data_ = provider_ ? jwks_cache_.findByProvider(provider_.value())
: jwks_cache_.findByIssuer(jwt_->iss_);
// isIssuerSpecified() check already make sure the issuer is in the cache.
ASSERT(jwks_data_ != nullptr);

// Default is 60 seconds
uint64_t clock_skew_seconds = ::google::jwt_verify::kClockSkewInSecond;
if (jwks_data_->getJwtProvider().clock_skew_seconds() > 0) {
clock_skew_seconds = jwks_data_->getJwtProvider().clock_skew_seconds();
}
status = jwt_->verifyTimeConstraint(absl::ToUnixSeconds(absl::Now()), clock_skew_seconds);
if (status != Status::Ok) {
doneWithStatus(status);
return;
}

// Check if audience is allowed
bool is_allowed = check_audience_ ? check_audience_->areAudiencesAllowed(jwt_->audiences_)
: jwks_data_->areAudiencesAllowed(jwt_->audiences_);
Expand Down Expand Up @@ -247,7 +237,8 @@ void AuthenticatorImpl::onDestroy() {

// Verify with a specific public key.
void AuthenticatorImpl::verifyKey() {
const Status status = ::google::jwt_verify::verifyJwt(*jwt_, *jwks_data_->getJwksObj());
const Status status =
::google::jwt_verify::verifyJwtWithoutTimeChecking(*jwt_, *jwks_data_->getJwksObj());
if (status != Status::Ok) {
doneWithStatus(status);
return;
Expand Down
17 changes: 17 additions & 0 deletions test/extensions/filters/http/jwt_authn/authenticator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,23 @@ TEST_F(AuthenticatorTest, TestExpiredJWT) {
expectVerifyStatus(Status::JwtExpired, headers);
}

// This test verifies when a JWT is expired but with a big clock skew.
TEST_F(AuthenticatorTest, TestExpiredJWTWithABigClockSkew) {
auto& provider = (*proto_config_.mutable_providers())[std::string(ProviderName)];
// Token is expired at 1205005587, but add clock skew at another 1205005587.
provider.set_clock_skew_seconds(1205005587);
createAuthenticator();

EXPECT_CALL(*raw_fetcher_, fetch(_, _, _))
.WillOnce(Invoke([this](const envoy::config::core::v3::HttpUri&, Tracing::Span&,
JwksFetcher::JwksReceiver& receiver) {
receiver.onJwksSuccess(std::move(jwks_));
}));

Http::TestRequestHeaderMapImpl headers{{"Authorization", "Bearer " + std::string(ExpiredToken)}};
expectVerifyStatus(Status::Ok, headers);
}

// This test verifies when a JWT is not yet valid, JwtNotYetValid status is returned.
TEST_F(AuthenticatorTest, TestNotYetValidJWT) {
EXPECT_CALL(*raw_fetcher_, fetch(_, _, _)).Times(0);
Expand Down

0 comments on commit 061f38b

Please sign in to comment.