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 0e4294608384f..a10fa68f30437 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -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"; @@ -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. 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 53ee84fd65ea6..2746640fa7380 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -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. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 734a5c8f175d4..23fb80a590f8b 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -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( diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 07412294aeb13..599e4d6d12586 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -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 ` 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 ` 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 ` is now the default action to terminate the process if watchdog kill / multikill is enabled. 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 0e4294608384f..a10fa68f30437 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 @@ -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"; @@ -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. 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 53ee84fd65ea6..2746640fa7380 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 @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -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. diff --git a/source/extensions/filters/http/jwt_authn/authenticator.cc b/source/extensions/filters/http/jwt_authn/authenticator.cc index 1b73eeaf08b22..cf447f68bdaac 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.cc +++ b/source/extensions/filters/http/jwt_authn/authenticator.cc @@ -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; @@ -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(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_); @@ -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; diff --git a/test/extensions/filters/http/jwt_authn/authenticator_test.cc b/test/extensions/filters/http/jwt_authn/authenticator_test.cc index e9ceb23cbbbdd..fbb732a632983 100644 --- a/test/extensions/filters/http/jwt_authn/authenticator_test.cc +++ b/test/extensions/filters/http/jwt_authn/authenticator_test.cc @@ -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);