From 8725fa93b7fa0b8040d0177cca69ba76b3149d4e Mon Sep 17 00:00:00 2001 From: Yaara Letz <3155222+letzya@users.noreply.github.com> Date: Wed, 9 May 2018 15:50:14 +0100 Subject: [PATCH 1/6] Added tests --- apidef/api_definitions.go | 3 + mw_jwt.go | 26 ++-- mw_jwt_test.go | 258 ++++++++++++++++++++++++++++++++++---- 3 files changed, 255 insertions(+), 32 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index ae73a02e6479..7fbf1afba3f3 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -343,6 +343,9 @@ type APIDefinition struct { JWTDisableIssuedAtValidation bool `bson:"jwt_disable_issued_at_validation" json:"jwt_disable_issued_at_validation"` JWTDisableExpiresAtValidation bool `bson:"jwt_disable_expires_at_validation" json:"jwt_disable_expires_at_validation"` JWTDisableNotBeforeValidation bool `bson:"jwt_disable_not_before_validation" json:"jwt_disable_not_before_validation"` + JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` + JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` + JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"` EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"` HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"` diff --git a/mw_jwt.go b/mw_jwt.go index a0a6150d4ef6..e9e4ab561c1e 100644 --- a/mw_jwt.go +++ b/mw_jwt.go @@ -465,21 +465,25 @@ func (k *JWTMiddleware) validateJWTClaims(c jwt.MapClaims) *jwt.ValidationError vErr := new(jwt.ValidationError) now := time.Now().Unix() - // The claims below are optional, by default, so if they are set to the - // default value in Go, let's not fail the verification for them. - if !k.Spec.JWTDisableExpiresAtValidation && c.VerifyExpiresAt(now, false) == false { - vErr.Inner = errors.New("Token is expired") - vErr.Errors |= jwt.ValidationErrorExpired + if !k.Spec.JWTDisableExpiresAtValidation { + if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) { + vErr.Inner = errors.New("token has expired") + vErr.Errors |= jwt.ValidationErrorExpired + } } - if !k.Spec.JWTDisableIssuedAtValidation && c.VerifyIssuedAt(now, false) == false { - vErr.Inner = fmt.Errorf("Token used before issued") - vErr.Errors |= jwt.ValidationErrorIssuedAt + if !k.Spec.JWTDisableIssuedAtValidation { + if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false { + vErr.Inner = errors.New("token used before issued") + vErr.Errors |= jwt.ValidationErrorIssuedAt + } } - if !k.Spec.JWTDisableNotBeforeValidation && c.VerifyNotBefore(now, false) == false { - vErr.Inner = fmt.Errorf("token is not valid yet") - vErr.Errors |= jwt.ValidationErrorNotValidYet + if !k.Spec.JWTDisableNotBeforeValidation { + if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false { + vErr.Inner = errors.New("token is not valid yet") + vErr.Errors |= jwt.ValidationErrorNotValidYet + } } if vErr.Errors == 0 { diff --git a/mw_jwt_test.go b/mw_jwt_test.go index 042e172d5420..e6adcbf6a434 100644 --- a/mw_jwt_test.go +++ b/mw_jwt_test.go @@ -498,7 +498,8 @@ func TestJWTSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { }) } -func TestJWTSessionInvalidClaims(t *testing.T) { +func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { + ts := newTykTestServer() defer ts.Close() @@ -514,35 +515,250 @@ func TestJWTSessionInvalidClaims(t *testing.T) { pID := createPolicy() - t.Run("Fail if token expired", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = false + t.Run("Expiry_After_now--Valid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user" - t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix() + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(+time.Second).Unix() //the jwt will be 1 second ahead }) authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token has expired", + }) + }) + + t.Run("Expiry_after_now--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value + loadAPI(spec) + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() //the jwt will be 1 second ahead + }) + authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 401, BodyMatch: "Key not authorized: Token is expired", + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token has expired", }) }) - t.Run("Pass if token expired and validation disabled", func(t *testing.T) { + t.Run("Expired_token-JWTDisableExpiresAtValidation--Valid_jwt", func(t *testing.T) { spec.JWTDisableExpiresAtValidation = true + spec.JWTExpiresAtValidationSkew = 1000 // This value doesn't matter since validation is disabled loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user" - t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix() + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() }) authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("Expired_token-EnableExpiresAtValidation-Add_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 1 //Default value + loadAPI(spec) + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, + }) + }) +} + +func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { + const issueAt = "iat" + + ts := newTykTestServer() + defer ts.Close() + + spec := buildAPI(func(spec *APISpec) { + spec.UseKeylessAccess = false + spec.EnableJWT = true + spec.JWTSigningMethod = "rsa" + spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey)) + spec.JWTIdentityBaseField = "user_id" + spec.JWTPolicyFieldName = "policy_id" + spec.Proxy.ListenPath = "/" + })[0] + + pID := createPolicy() + + t.Run("IssuedAt_Before_now-no_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("IssueAt-JWTDisableIssuedAtValidation--valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = true + spec.JWTIssuedAtValidationSkew = 1000 // This value doesn't matter since validation is disabled + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("IssueAt-After_now-no_skew--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(+time.Minute).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token used before issued", + }) + }) + + t.Run("IssueAt-After_now-Add_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 1 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) +} + +func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { + + const notBefore = "nbf" + + ts := newTykTestServer() + defer ts.Close() + + spec := buildAPI(func(spec *APISpec) { + spec.UseKeylessAccess = false + spec.EnableJWT = true + spec.JWTSigningMethod = "rsa" + spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey)) + spec.JWTIdentityBaseField = "user_id" + spec.JWTPolicyFieldName = "policy_id" + spec.Proxy.ListenPath = "/" + })[0] + + pID := createPolicy() + + t.Run("NotBefore_Before_now-Valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + //IssuedAt_Before_now-no_skew--Valid_jwt + + t.Run("NotBefore_After_now--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token is not valid yet", + }) + }) + + t.Run("NotBefore_After_now-JWTDisableNotBeforeValidation--valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = true + spec.JWTNotBeforeValidationSkew = 1000 // This value doesn't matter since validation is disabled + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("NotBefore_After_now-Add_skew--valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 1 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -576,7 +792,7 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { authHeaders := map[string]string{"authorization": jwtToken} t.Run("Initial request with valid policy", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) @@ -592,7 +808,7 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { authHeaders = map[string]string{"authorization": jwtTokenInvalidPolicy} t.Run("Request with invalid policy in JWT", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 403, + Headers: authHeaders, Code: http.StatusForbidden, }) }) } @@ -635,13 +851,13 @@ func TestJWTExistingSessionRSAWithRawSourcePolicyIDChanged(t *testing.T) { ts.Run( t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }, test.TestCase{ Method: http.MethodGet, Path: "/tyk/keys/" + sessionID, AdminAuth: true, - Code: 200, + Code: http.StatusOK, BodyMatch: `"quota_max":111`, }, ) @@ -662,13 +878,13 @@ func TestJWTExistingSessionRSAWithRawSourcePolicyIDChanged(t *testing.T) { t.Run("Request with new valid policy in JWT", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }, test.TestCase{ Method: http.MethodGet, Path: "/tyk/keys/" + sessionID, AdminAuth: true, - Code: 200, + Code: http.StatusOK, BodyMatch: `"quota_max":999`, }, ) @@ -709,7 +925,7 @@ func TestJWTSessionRSAWithJWK(t *testing.T) { t.Run("JWTSessionRSAWithJWK", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -728,7 +944,7 @@ func BenchmarkJWTSessionRSAWithJWK(b *testing.B) { b, test.TestCase{ Headers: authHeaders, - Code: 200, + Code: http.StatusOK, }, ) } @@ -772,7 +988,7 @@ func TestJWTSessionRSAWithEncodedJWK(t *testing.T) { loadAPI(spec) ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) @@ -781,7 +997,7 @@ func TestJWTSessionRSAWithEncodedJWK(t *testing.T) { loadAPI(spec) ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -804,7 +1020,7 @@ func BenchmarkJWTSessionRSAWithEncodedJWK(b *testing.B) { b, test.TestCase{ Headers: authHeaders, - Code: 200, + Code: http.StatusOK, }, ) } From 3aa2858d0699f3f4908c489f5123de5dedceb4a7 Mon Sep 17 00:00:00 2001 From: Yaara Letz <3155222+letzya@users.noreply.github.com> Date: Wed, 9 May 2018 15:50:14 +0100 Subject: [PATCH 2/6] Added tests --- apidef/api_definitions.go | 3 + mw_jwt.go | 26 ++-- mw_jwt_test.go | 258 ++++++++++++++++++++++++++++++++++---- 3 files changed, 255 insertions(+), 32 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index d87f7d59caef..79e5bd00b0b7 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -343,6 +343,9 @@ type APIDefinition struct { JWTDisableIssuedAtValidation bool `bson:"jwt_disable_issued_at_validation" json:"jwt_disable_issued_at_validation"` JWTDisableExpiresAtValidation bool `bson:"jwt_disable_expires_at_validation" json:"jwt_disable_expires_at_validation"` JWTDisableNotBeforeValidation bool `bson:"jwt_disable_not_before_validation" json:"jwt_disable_not_before_validation"` + JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` + JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` + JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"` EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"` HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"` diff --git a/mw_jwt.go b/mw_jwt.go index a0a6150d4ef6..e9e4ab561c1e 100644 --- a/mw_jwt.go +++ b/mw_jwt.go @@ -465,21 +465,25 @@ func (k *JWTMiddleware) validateJWTClaims(c jwt.MapClaims) *jwt.ValidationError vErr := new(jwt.ValidationError) now := time.Now().Unix() - // The claims below are optional, by default, so if they are set to the - // default value in Go, let's not fail the verification for them. - if !k.Spec.JWTDisableExpiresAtValidation && c.VerifyExpiresAt(now, false) == false { - vErr.Inner = errors.New("Token is expired") - vErr.Errors |= jwt.ValidationErrorExpired + if !k.Spec.JWTDisableExpiresAtValidation { + if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) { + vErr.Inner = errors.New("token has expired") + vErr.Errors |= jwt.ValidationErrorExpired + } } - if !k.Spec.JWTDisableIssuedAtValidation && c.VerifyIssuedAt(now, false) == false { - vErr.Inner = fmt.Errorf("Token used before issued") - vErr.Errors |= jwt.ValidationErrorIssuedAt + if !k.Spec.JWTDisableIssuedAtValidation { + if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false { + vErr.Inner = errors.New("token used before issued") + vErr.Errors |= jwt.ValidationErrorIssuedAt + } } - if !k.Spec.JWTDisableNotBeforeValidation && c.VerifyNotBefore(now, false) == false { - vErr.Inner = fmt.Errorf("token is not valid yet") - vErr.Errors |= jwt.ValidationErrorNotValidYet + if !k.Spec.JWTDisableNotBeforeValidation { + if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false { + vErr.Inner = errors.New("token is not valid yet") + vErr.Errors |= jwt.ValidationErrorNotValidYet + } } if vErr.Errors == 0 { diff --git a/mw_jwt_test.go b/mw_jwt_test.go index 8f4acf173b1c..f482c284346e 100644 --- a/mw_jwt_test.go +++ b/mw_jwt_test.go @@ -513,7 +513,8 @@ func TestJWTSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { }) } -func TestJWTSessionInvalidClaims(t *testing.T) { +func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { + ts := newTykTestServer() defer ts.Close() @@ -529,35 +530,250 @@ func TestJWTSessionInvalidClaims(t *testing.T) { pID := createPolicy() - t.Run("Fail if token expired", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = false + t.Run("Expiry_After_now--Valid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user" - t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix() + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(+time.Second).Unix() //the jwt will be 1 second ahead }) authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token has expired", + }) + }) + + t.Run("Expiry_after_now--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value + loadAPI(spec) + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() //the jwt will be 1 second ahead + }) + authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 401, BodyMatch: "Key not authorized: Token is expired", + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token has expired", }) }) - t.Run("Pass if token expired and validation disabled", func(t *testing.T) { + t.Run("Expired_token-JWTDisableExpiresAtValidation--Valid_jwt", func(t *testing.T) { spec.JWTDisableExpiresAtValidation = true + spec.JWTExpiresAtValidationSkew = 1000 // This value doesn't matter since validation is disabled loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user" - t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix() + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() }) authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("Expired_token-EnableExpiresAtValidation-Add_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 1 //Default value + loadAPI(spec) + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, + }) + }) +} + +func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { + const issueAt = "iat" + + ts := newTykTestServer() + defer ts.Close() + + spec := buildAPI(func(spec *APISpec) { + spec.UseKeylessAccess = false + spec.EnableJWT = true + spec.JWTSigningMethod = "rsa" + spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey)) + spec.JWTIdentityBaseField = "user_id" + spec.JWTPolicyFieldName = "policy_id" + spec.Proxy.ListenPath = "/" + })[0] + + pID := createPolicy() + + t.Run("IssuedAt_Before_now-no_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("IssueAt-JWTDisableIssuedAtValidation--valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = true + spec.JWTIssuedAtValidationSkew = 1000 // This value doesn't matter since validation is disabled + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("IssueAt-After_now-no_skew--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(+time.Minute).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token used before issued", + }) + }) + + t.Run("IssueAt-After_now-Add_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 1 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) +} + +func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { + + const notBefore = "nbf" + + ts := newTykTestServer() + defer ts.Close() + + spec := buildAPI(func(spec *APISpec) { + spec.UseKeylessAccess = false + spec.EnableJWT = true + spec.JWTSigningMethod = "rsa" + spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey)) + spec.JWTIdentityBaseField = "user_id" + spec.JWTPolicyFieldName = "policy_id" + spec.Proxy.ListenPath = "/" + })[0] + + pID := createPolicy() + + t.Run("NotBefore_Before_now-Valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + //IssuedAt_Before_now-no_skew--Valid_jwt + + t.Run("NotBefore_After_now--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token is not valid yet", + }) + }) + + t.Run("NotBefore_After_now-JWTDisableNotBeforeValidation--valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = true + spec.JWTNotBeforeValidationSkew = 1000 // This value doesn't matter since validation is disabled + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("NotBefore_After_now-Add_skew--valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 1 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -591,7 +807,7 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { authHeaders := map[string]string{"authorization": jwtToken} t.Run("Initial request with valid policy", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) @@ -607,7 +823,7 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { authHeaders = map[string]string{"authorization": jwtTokenInvalidPolicy} t.Run("Request with invalid policy in JWT", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 403, + Headers: authHeaders, Code: http.StatusForbidden, }) }) } @@ -650,13 +866,13 @@ func TestJWTExistingSessionRSAWithRawSourcePolicyIDChanged(t *testing.T) { ts.Run( t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }, test.TestCase{ Method: http.MethodGet, Path: "/tyk/keys/" + sessionID, AdminAuth: true, - Code: 200, + Code: http.StatusOK, BodyMatch: `"quota_max":111`, }, ) @@ -677,13 +893,13 @@ func TestJWTExistingSessionRSAWithRawSourcePolicyIDChanged(t *testing.T) { t.Run("Request with new valid policy in JWT", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }, test.TestCase{ Method: http.MethodGet, Path: "/tyk/keys/" + sessionID, AdminAuth: true, - Code: 200, + Code: http.StatusOK, BodyMatch: `"quota_max":999`, }, ) @@ -724,7 +940,7 @@ func TestJWTSessionRSAWithJWK(t *testing.T) { t.Run("JWTSessionRSAWithJWK", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -743,7 +959,7 @@ func BenchmarkJWTSessionRSAWithJWK(b *testing.B) { b, test.TestCase{ Headers: authHeaders, - Code: 200, + Code: http.StatusOK, }, ) } @@ -787,7 +1003,7 @@ func TestJWTSessionRSAWithEncodedJWK(t *testing.T) { loadAPI(spec) ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) @@ -796,7 +1012,7 @@ func TestJWTSessionRSAWithEncodedJWK(t *testing.T) { loadAPI(spec) ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -819,7 +1035,7 @@ func BenchmarkJWTSessionRSAWithEncodedJWK(b *testing.B) { b, test.TestCase{ Headers: authHeaders, - Code: 200, + Code: http.StatusOK, }, ) } From eb232f4effd634ba33ddd9af1fefa9d7c6c3c432 Mon Sep 17 00:00:00 2001 From: Yaara Letz <3155222+letzya@users.noreply.github.com> Date: Wed, 9 May 2018 15:50:14 +0100 Subject: [PATCH 3/6] Added tests --- apidef/api_definitions.go | 3 + mw_jwt.go | 26 ++-- mw_jwt_test.go | 258 ++++++++++++++++++++++++++++++++++---- 3 files changed, 255 insertions(+), 32 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index ebd3b5c2356c..228d6d46b207 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -346,6 +346,9 @@ type APIDefinition struct { JWTDisableIssuedAtValidation bool `bson:"jwt_disable_issued_at_validation" json:"jwt_disable_issued_at_validation"` JWTDisableExpiresAtValidation bool `bson:"jwt_disable_expires_at_validation" json:"jwt_disable_expires_at_validation"` JWTDisableNotBeforeValidation bool `bson:"jwt_disable_not_before_validation" json:"jwt_disable_not_before_validation"` + JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` + JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` + JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"` EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"` HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"` diff --git a/mw_jwt.go b/mw_jwt.go index a0a6150d4ef6..e9e4ab561c1e 100644 --- a/mw_jwt.go +++ b/mw_jwt.go @@ -465,21 +465,25 @@ func (k *JWTMiddleware) validateJWTClaims(c jwt.MapClaims) *jwt.ValidationError vErr := new(jwt.ValidationError) now := time.Now().Unix() - // The claims below are optional, by default, so if they are set to the - // default value in Go, let's not fail the verification for them. - if !k.Spec.JWTDisableExpiresAtValidation && c.VerifyExpiresAt(now, false) == false { - vErr.Inner = errors.New("Token is expired") - vErr.Errors |= jwt.ValidationErrorExpired + if !k.Spec.JWTDisableExpiresAtValidation { + if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) { + vErr.Inner = errors.New("token has expired") + vErr.Errors |= jwt.ValidationErrorExpired + } } - if !k.Spec.JWTDisableIssuedAtValidation && c.VerifyIssuedAt(now, false) == false { - vErr.Inner = fmt.Errorf("Token used before issued") - vErr.Errors |= jwt.ValidationErrorIssuedAt + if !k.Spec.JWTDisableIssuedAtValidation { + if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false { + vErr.Inner = errors.New("token used before issued") + vErr.Errors |= jwt.ValidationErrorIssuedAt + } } - if !k.Spec.JWTDisableNotBeforeValidation && c.VerifyNotBefore(now, false) == false { - vErr.Inner = fmt.Errorf("token is not valid yet") - vErr.Errors |= jwt.ValidationErrorNotValidYet + if !k.Spec.JWTDisableNotBeforeValidation { + if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false { + vErr.Inner = errors.New("token is not valid yet") + vErr.Errors |= jwt.ValidationErrorNotValidYet + } } if vErr.Errors == 0 { diff --git a/mw_jwt_test.go b/mw_jwt_test.go index 8f4acf173b1c..f482c284346e 100644 --- a/mw_jwt_test.go +++ b/mw_jwt_test.go @@ -513,7 +513,8 @@ func TestJWTSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { }) } -func TestJWTSessionInvalidClaims(t *testing.T) { +func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { + ts := newTykTestServer() defer ts.Close() @@ -529,35 +530,250 @@ func TestJWTSessionInvalidClaims(t *testing.T) { pID := createPolicy() - t.Run("Fail if token expired", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = false + t.Run("Expiry_After_now--Valid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user" - t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix() + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(+time.Second).Unix() //the jwt will be 1 second ahead }) authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token has expired", + }) + }) + + t.Run("Expiry_after_now--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value + loadAPI(spec) + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() //the jwt will be 1 second ahead + }) + authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 401, BodyMatch: "Key not authorized: Token is expired", + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token has expired", }) }) - t.Run("Pass if token expired and validation disabled", func(t *testing.T) { + t.Run("Expired_token-JWTDisableExpiresAtValidation--Valid_jwt", func(t *testing.T) { spec.JWTDisableExpiresAtValidation = true + spec.JWTExpiresAtValidationSkew = 1000 // This value doesn't matter since validation is disabled loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user" - t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Hour * 72).Unix() + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() }) authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("Expired_token-EnableExpiresAtValidation-Add_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableExpiresAtValidation = false //Default value + spec.JWTExpiresAtValidationSkew = 1 //Default value + loadAPI(spec) + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, + }) + }) +} + +func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { + const issueAt = "iat" + + ts := newTykTestServer() + defer ts.Close() + + spec := buildAPI(func(spec *APISpec) { + spec.UseKeylessAccess = false + spec.EnableJWT = true + spec.JWTSigningMethod = "rsa" + spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey)) + spec.JWTIdentityBaseField = "user_id" + spec.JWTPolicyFieldName = "policy_id" + spec.Proxy.ListenPath = "/" + })[0] + + pID := createPolicy() + + t.Run("IssuedAt_Before_now-no_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("IssueAt-JWTDisableIssuedAtValidation--valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = true + spec.JWTIssuedAtValidationSkew = 1000 // This value doesn't matter since validation is disabled + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("IssueAt-After_now-no_skew--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(+time.Minute).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + + Headers: authHeaders, + Code: http.StatusUnauthorized, + BodyMatch: "Key not authorized: token used before issued", + }) + }) + + t.Run("IssueAt-After_now-Add_skew--Valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 1 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) +} + +func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { + + const notBefore = "nbf" + + ts := newTykTestServer() + defer ts.Close() + + spec := buildAPI(func(spec *APISpec) { + spec.UseKeylessAccess = false + spec.EnableJWT = true + spec.JWTSigningMethod = "rsa" + spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey)) + spec.JWTIdentityBaseField = "user_id" + spec.JWTPolicyFieldName = "policy_id" + spec.Proxy.ListenPath = "/" + })[0] + + pID := createPolicy() + + t.Run("NotBefore_Before_now-Valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(-time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + //IssuedAt_Before_now-no_skew--Valid_jwt + + t.Run("NotBefore_After_now--Invalid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 0 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token is not valid yet", + }) + }) + + t.Run("NotBefore_After_now-JWTDisableNotBeforeValidation--valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = true + spec.JWTNotBeforeValidationSkew = 1000 // This value doesn't matter since validation is disabled + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, + }) + }) + + t.Run("NotBefore_After_now-Add_skew--valid_jwt", func(t *testing.T) { + spec.JWTDisableNotBeforeValidation = false + spec.JWTNotBeforeValidationSkew = 1 + + loadAPI(spec) + + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() + }) + authHeaders := map[string]string{"authorization": jwtToken} + ts.Run(t, test.TestCase{ + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -591,7 +807,7 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { authHeaders := map[string]string{"authorization": jwtToken} t.Run("Initial request with valid policy", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) @@ -607,7 +823,7 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { authHeaders = map[string]string{"authorization": jwtTokenInvalidPolicy} t.Run("Request with invalid policy in JWT", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 403, + Headers: authHeaders, Code: http.StatusForbidden, }) }) } @@ -650,13 +866,13 @@ func TestJWTExistingSessionRSAWithRawSourcePolicyIDChanged(t *testing.T) { ts.Run( t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }, test.TestCase{ Method: http.MethodGet, Path: "/tyk/keys/" + sessionID, AdminAuth: true, - Code: 200, + Code: http.StatusOK, BodyMatch: `"quota_max":111`, }, ) @@ -677,13 +893,13 @@ func TestJWTExistingSessionRSAWithRawSourcePolicyIDChanged(t *testing.T) { t.Run("Request with new valid policy in JWT", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }, test.TestCase{ Method: http.MethodGet, Path: "/tyk/keys/" + sessionID, AdminAuth: true, - Code: 200, + Code: http.StatusOK, BodyMatch: `"quota_max":999`, }, ) @@ -724,7 +940,7 @@ func TestJWTSessionRSAWithJWK(t *testing.T) { t.Run("JWTSessionRSAWithJWK", func(t *testing.T) { ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -743,7 +959,7 @@ func BenchmarkJWTSessionRSAWithJWK(b *testing.B) { b, test.TestCase{ Headers: authHeaders, - Code: 200, + Code: http.StatusOK, }, ) } @@ -787,7 +1003,7 @@ func TestJWTSessionRSAWithEncodedJWK(t *testing.T) { loadAPI(spec) ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) @@ -796,7 +1012,7 @@ func TestJWTSessionRSAWithEncodedJWK(t *testing.T) { loadAPI(spec) ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: 200, + Headers: authHeaders, Code: http.StatusOK, }) }) } @@ -819,7 +1035,7 @@ func BenchmarkJWTSessionRSAWithEncodedJWK(b *testing.B) { b, test.TestCase{ Headers: authHeaders, - Code: 200, + Code: http.StatusOK, }, ) } From fd143ad53adbaf5c34382de0600c8c0be9c4de05 Mon Sep 17 00:00:00 2001 From: Yaara Letz <3155222+letzya@users.noreply.github.com> Date: Sun, 10 Jun 2018 23:19:15 +0100 Subject: [PATCH 4/6] Remove jwt_disable_issued_at_validation/jwt_disable_issued_at_validation/ --- apidef/api_definitions.go | 47 ++++++++++++++++++--------------------- mw_jwt.go | 24 ++++++++------------ mw_jwt_test.go | 30 ++++++------------------- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index 228d6d46b207..06331893e3f1 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -329,31 +329,28 @@ type APIDefinition struct { AllowedAuthorizeTypes []osin.AuthorizeRequestType `bson:"allowed_authorize_types" json:"allowed_authorize_types"` AuthorizeLoginRedirect string `bson:"auth_login_redirect" json:"auth_login_redirect"` } `bson:"oauth_meta" json:"oauth_meta"` - Auth Auth `bson:"auth" json:"auth"` - UseBasicAuth bool `bson:"use_basic_auth" json:"use_basic_auth"` - UseMutualTLSAuth bool `bson:"use_mutual_tls_auth" json:"use_mutual_tls_auth"` - ClientCertificates []string `bson:"client_certificates" json:"client_certificates"` - UpstreamCertificates map[string]string `bson:"upstream_certificates" json:"upstream_certificates"` - PinnedPublicKeys map[string]string `bson:"pinned_public_keys" json:"pinned_public_keys"` - EnableJWT bool `bson:"enable_jwt" json:"enable_jwt"` - UseStandardAuth bool `bson:"use_standard_auth" json:"use_standard_auth"` - EnableCoProcessAuth bool `bson:"enable_coprocess_auth" json:"enable_coprocess_auth"` - JWTSigningMethod string `bson:"jwt_signing_method" json:"jwt_signing_method"` - JWTSource string `bson:"jwt_source" json:"jwt_source"` - JWTIdentityBaseField string `bson:"jwt_identit_base_field" json:"jwt_identity_base_field"` - JWTClientIDBaseField string `bson:"jwt_client_base_field" json:"jwt_client_base_field"` - JWTPolicyFieldName string `bson:"jwt_policy_field_name" json:"jwt_policy_field_name"` - JWTDisableIssuedAtValidation bool `bson:"jwt_disable_issued_at_validation" json:"jwt_disable_issued_at_validation"` - JWTDisableExpiresAtValidation bool `bson:"jwt_disable_expires_at_validation" json:"jwt_disable_expires_at_validation"` - JWTDisableNotBeforeValidation bool `bson:"jwt_disable_not_before_validation" json:"jwt_disable_not_before_validation"` - JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` - JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` - JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` - NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"` - EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"` - HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"` - BaseIdentityProvidedBy AuthTypeEnum `bson:"base_identity_provided_by" json:"base_identity_provided_by"` - VersionDefinition struct { + Auth Auth `bson:"auth" json:"auth"` + UseBasicAuth bool `bson:"use_basic_auth" json:"use_basic_auth"` + UseMutualTLSAuth bool `bson:"use_mutual_tls_auth" json:"use_mutual_tls_auth"` + ClientCertificates []string `bson:"client_certificates" json:"client_certificates"` + UpstreamCertificates map[string]string `bson:"upstream_certificates" json:"upstream_certificates"` + PinnedPublicKeys map[string]string `bson:"pinned_public_keys" json:"pinned_public_keys"` + EnableJWT bool `bson:"enable_jwt" json:"enable_jwt"` + UseStandardAuth bool `bson:"use_standard_auth" json:"use_standard_auth"` + EnableCoProcessAuth bool `bson:"enable_coprocess_auth" json:"enable_coprocess_auth"` + JWTSigningMethod string `bson:"jwt_signing_method" json:"jwt_signing_method"` + JWTSource string `bson:"jwt_source" json:"jwt_source"` + JWTIdentityBaseField string `bson:"jwt_identit_base_field" json:"jwt_identity_base_field"` + JWTClientIDBaseField string `bson:"jwt_client_base_field" json:"jwt_client_base_field"` + JWTPolicyFieldName string `bson:"jwt_policy_field_name" json:"jwt_policy_field_name"` + JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` + JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` + JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` + NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"` + EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"` + HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"` + BaseIdentityProvidedBy AuthTypeEnum `bson:"base_identity_provided_by" json:"base_identity_provided_by"` + VersionDefinition struct { Location string `bson:"location" json:"location"` Key string `bson:"key" json:"key"` StripPath bool `bson:"strip_path" json:"strip_path"` diff --git a/mw_jwt.go b/mw_jwt.go index e9e4ab561c1e..02b8318a2081 100644 --- a/mw_jwt.go +++ b/mw_jwt.go @@ -465,25 +465,19 @@ func (k *JWTMiddleware) validateJWTClaims(c jwt.MapClaims) *jwt.ValidationError vErr := new(jwt.ValidationError) now := time.Now().Unix() - if !k.Spec.JWTDisableExpiresAtValidation { - if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) { - vErr.Inner = errors.New("token has expired") - vErr.Errors |= jwt.ValidationErrorExpired - } + if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) { + vErr.Inner = errors.New("token has expired") + vErr.Errors |= jwt.ValidationErrorExpired } - if !k.Spec.JWTDisableIssuedAtValidation { - if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false { - vErr.Inner = errors.New("token used before issued") - vErr.Errors |= jwt.ValidationErrorIssuedAt - } + if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false { + vErr.Inner = errors.New("token used before issued") + vErr.Errors |= jwt.ValidationErrorIssuedAt } - if !k.Spec.JWTDisableNotBeforeValidation { - if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false { - vErr.Inner = errors.New("token is not valid yet") - vErr.Errors |= jwt.ValidationErrorNotValidYet - } + if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false { + vErr.Inner = errors.New("token is not valid yet") + vErr.Errors |= jwt.ValidationErrorNotValidYet } if vErr.Errors == 0 { diff --git a/mw_jwt_test.go b/mw_jwt_test.go index f482c284346e..9bb112919d2c 100644 --- a/mw_jwt_test.go +++ b/mw_jwt_test.go @@ -531,8 +531,7 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { pID := createPolicy() t.Run("Expiry_After_now--Valid_jwt", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = false //Default value - spec.JWTExpiresAtValidationSkew = 0 //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { @@ -541,16 +540,11 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(+time.Second).Unix() //the jwt will be 1 second ahead }) authHeaders := map[string]string{"authorization": jwtToken} - ts.Run(t, test.TestCase{ - Headers: authHeaders, - Code: http.StatusUnauthorized, - BodyMatch: "Key not authorized: token has expired", - }) + ts.Run(t, test.TestCase{Headers: authHeaders, Code: http.StatusOK}) }) t.Run("Expiry_after_now--Invalid_jwt", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = false //Default value - spec.JWTExpiresAtValidationSkew = 0 //Default value + spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { @@ -567,8 +561,7 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { }) t.Run("Expired_token-JWTDisableExpiresAtValidation--Valid_jwt", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = true - spec.JWTExpiresAtValidationSkew = 1000 // This value doesn't matter since validation is disabled + spec.JWTExpiresAtValidationSkew = 1000 // This value is so high that it actually disable validation loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { @@ -583,8 +576,7 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { }) t.Run("Expired_token-EnableExpiresAtValidation-Add_skew--Valid_jwt", func(t *testing.T) { - spec.JWTDisableExpiresAtValidation = false //Default value - spec.JWTExpiresAtValidationSkew = 1 //Default value + spec.JWTExpiresAtValidationSkew = 1 //Default value loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { @@ -618,7 +610,6 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { pID := createPolicy() t.Run("IssuedAt_Before_now-no_skew--Valid_jwt", func(t *testing.T) { - spec.JWTDisableIssuedAtValidation = false spec.JWTIssuedAtValidationSkew = 0 loadAPI(spec) @@ -636,8 +627,7 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { }) t.Run("IssueAt-JWTDisableIssuedAtValidation--valid_jwt", func(t *testing.T) { - spec.JWTDisableIssuedAtValidation = true - spec.JWTIssuedAtValidationSkew = 1000 // This value doesn't matter since validation is disabled + spec.JWTIssuedAtValidationSkew = 1000 // This value is so high that it actually disable validation loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { @@ -652,7 +642,6 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { }) t.Run("IssueAt-After_now-no_skew--Invalid_jwt", func(t *testing.T) { - spec.JWTDisableIssuedAtValidation = false spec.JWTIssuedAtValidationSkew = 0 loadAPI(spec) @@ -672,7 +661,6 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { }) t.Run("IssueAt-After_now-Add_skew--Valid_jwt", func(t *testing.T) { - spec.JWTDisableIssuedAtValidation = false spec.JWTIssuedAtValidationSkew = 1 loadAPI(spec) @@ -709,7 +697,6 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { pID := createPolicy() t.Run("NotBefore_Before_now-Valid_jwt", func(t *testing.T) { - spec.JWTDisableNotBeforeValidation = false spec.JWTNotBeforeValidationSkew = 0 loadAPI(spec) @@ -728,7 +715,6 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { //IssuedAt_Before_now-no_skew--Valid_jwt t.Run("NotBefore_After_now--Invalid_jwt", func(t *testing.T) { - spec.JWTDisableNotBeforeValidation = false spec.JWTNotBeforeValidationSkew = 0 loadAPI(spec) @@ -745,8 +731,7 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { }) t.Run("NotBefore_After_now-JWTDisableNotBeforeValidation--valid_jwt", func(t *testing.T) { - spec.JWTDisableNotBeforeValidation = true - spec.JWTNotBeforeValidationSkew = 1000 // This value doesn't matter since validation is disabled + spec.JWTNotBeforeValidationSkew = 1000 // This value is so high that it's actually similar to disableing the claim. loadAPI(spec) jwtToken := createJWKToken(func(t *jwt.Token) { @@ -761,7 +746,6 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { }) t.Run("NotBefore_After_now-Add_skew--valid_jwt", func(t *testing.T) { - spec.JWTDisableNotBeforeValidation = false spec.JWTNotBeforeValidationSkew = 1 loadAPI(spec) From 4f0e098b95e13621ccf02f97c35c6ed769529fbf Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 11 Jun 2018 18:09:09 +0500 Subject: [PATCH 5/6] Simplify tests --- mw_jwt_test.go | 145 +++++++++++++++---------------------------------- 1 file changed, 45 insertions(+), 100 deletions(-) diff --git a/mw_jwt_test.go b/mw_jwt_test.go index 4cffd33a44b6..602c44d8e10b 100644 --- a/mw_jwt_test.go +++ b/mw_jwt_test.go @@ -514,11 +514,20 @@ func TestJWTSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) { } func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { - const expiry = "exp" - ts := newTykTestServer() defer ts.Close() + pID := createPolicy() + jwtAuthHeaderGen := func(scew time.Duration) map[string]string { + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(scew).Unix() + }) + + return map[string]string{"authorization": jwtToken} + } + spec := buildAPI(func(spec *APISpec) { spec.UseKeylessAccess = false spec.EnableJWT = true @@ -529,22 +538,14 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { spec.Proxy.ListenPath = "/" })[0] - pID := createPolicy() - // This test is successful by definition t.Run("Expiry_After_now--Valid_jwt", func(t *testing.T) { spec.JWTDisableExpiresAtValidation = false //Default value spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[expiry] = time.Now().Add(+time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK, }) }) @@ -553,14 +554,8 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { spec.JWTExpiresAtValidationSkew = 0 //Default value loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[expiry] = time.Now().Add(-time.Second).Unix() //the jwt will be 1 second ahead - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, + Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token has expired", }) @@ -571,14 +566,8 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { spec.JWTExpiresAtValidationSkew = 1000 // This value doesn't matter since validation is disabled loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[expiry] = time.Now().Add(-time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK, }) }) @@ -587,24 +576,27 @@ func TestJWTSessionExpiresAtValidationConfigs(t *testing.T) { spec.JWTExpiresAtValidationSkew = 1 //Default value loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[expiry] = time.Now().Add(-time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK, }) }) } func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { - const issueAt = "iat" - ts := newTykTestServer() defer ts.Close() + pID := createPolicy() + jwtAuthHeaderGen := func(scew time.Duration) map[string]string { + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["iat"] = time.Now().Add(scew).Unix() + }) + + return map[string]string{"authorization": jwtToken} + } + spec := buildAPI(func(spec *APISpec) { spec.UseKeylessAccess = false spec.EnableJWT = true @@ -615,8 +607,6 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { spec.Proxy.ListenPath = "/" })[0] - pID := createPolicy() - // This test is successful by definition t.Run("IssuedAt_Before_now-no_skew--Valid_jwt", func(t *testing.T) { spec.JWTDisableIssuedAtValidation = false @@ -624,15 +614,8 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(-time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK, }) }) @@ -642,15 +625,8 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(+time.Minute).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - - Headers: authHeaders, + Headers: jwtAuthHeaderGen(+time.Minute), Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token used before issued", }) @@ -661,14 +637,8 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { spec.JWTIssuedAtValidationSkew = 1000 // This value doesn't matter since validation is disabled loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(+time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK, }) }) @@ -678,25 +648,27 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[issueAt] = time.Now().Add(time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK, }) }) } func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { - - const notBefore = "nbf" - ts := newTykTestServer() defer ts.Close() + pID := createPolicy() + jwtAuthHeaderGen := func(scew time.Duration) map[string]string { + jwtToken := createJWKToken(func(t *jwt.Token) { + t.Claims.(jwt.MapClaims)["policy_id"] = pID + t.Claims.(jwt.MapClaims)["user_id"] = "user123" + t.Claims.(jwt.MapClaims)["nbf"] = time.Now().Add(scew).Unix() + }) + + return map[string]string{"authorization": jwtToken} + } + spec := buildAPI(func(spec *APISpec) { spec.UseKeylessAccess = false spec.EnableJWT = true @@ -707,23 +679,14 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { spec.Proxy.ListenPath = "/" })[0] - pID := createPolicy() - // This test is successful by definition t.Run("NotBefore_Before_now-Valid_jwt", func(t *testing.T) { spec.JWTDisableNotBeforeValidation = false spec.JWTNotBeforeValidationSkew = 0 - loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(-time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(-time.Second), Code: http.StatusOK, }) }) @@ -735,14 +698,8 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token is not valid yet", + Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusUnauthorized, BodyMatch: "Key not authorized: token is not valid yet", }) }) @@ -751,14 +708,8 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { spec.JWTNotBeforeValidationSkew = 1000 // This value doesn't matter since validation is disabled loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK, }) }) @@ -768,14 +719,8 @@ func TestJWTSessionNotBeforeValidationConfigs(t *testing.T) { loadAPI(spec) - jwtToken := createJWKToken(func(t *jwt.Token) { - t.Claims.(jwt.MapClaims)["policy_id"] = pID - t.Claims.(jwt.MapClaims)["user_id"] = "user123" - t.Claims.(jwt.MapClaims)[notBefore] = time.Now().Add(+time.Second).Unix() - }) - authHeaders := map[string]string{"authorization": jwtToken} ts.Run(t, test.TestCase{ - Headers: authHeaders, Code: http.StatusOK, + Headers: jwtAuthHeaderGen(+time.Second), Code: http.StatusOK, }) }) } From 9fbf505b5061f2db8bd34abbbf862ea2c627d6d7 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 11 Jun 2018 18:35:27 +0500 Subject: [PATCH 6/6] Add test which replicates the bug --- apidef/api_definitions.go | 6 +++--- mw_jwt.go | 6 +++--- mw_jwt_test.go | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apidef/api_definitions.go b/apidef/api_definitions.go index 79e5bd00b0b7..0443dc3d018b 100644 --- a/apidef/api_definitions.go +++ b/apidef/api_definitions.go @@ -343,9 +343,9 @@ type APIDefinition struct { JWTDisableIssuedAtValidation bool `bson:"jwt_disable_issued_at_validation" json:"jwt_disable_issued_at_validation"` JWTDisableExpiresAtValidation bool `bson:"jwt_disable_expires_at_validation" json:"jwt_disable_expires_at_validation"` JWTDisableNotBeforeValidation bool `bson:"jwt_disable_not_before_validation" json:"jwt_disable_not_before_validation"` - JWTIssuedAtValidationSkew uint64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` - JWTExpiresAtValidationSkew uint64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` - JWTNotBeforeValidationSkew uint64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` + JWTIssuedAtValidationSkew int64 `bson:"jwt_issued_at_validation_skew" json:"jwt_issued_at_validation_skew"` + JWTExpiresAtValidationSkew int64 `bson:"jwt_expires_at_validation_skew" json:"jwt_expires_at_validation_skew"` + JWTNotBeforeValidationSkew int64 `bson:"jwt_not_before_validation_skew" json:"jwt_not_before_validation_skew"` NotificationsDetails NotificationsManager `bson:"notifications" json:"notifications"` EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"` HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"` diff --git a/mw_jwt.go b/mw_jwt.go index e9e4ab561c1e..ce56f8e594a2 100644 --- a/mw_jwt.go +++ b/mw_jwt.go @@ -466,21 +466,21 @@ func (k *JWTMiddleware) validateJWTClaims(c jwt.MapClaims) *jwt.ValidationError now := time.Now().Unix() if !k.Spec.JWTDisableExpiresAtValidation { - if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) { + if !c.VerifyExpiresAt(now - k.Spec.JWTExpiresAtValidationSkew, false) { vErr.Inner = errors.New("token has expired") vErr.Errors |= jwt.ValidationErrorExpired } } if !k.Spec.JWTDisableIssuedAtValidation { - if c.VerifyIssuedAt(now+int64(k.Spec.JWTIssuedAtValidationSkew), false) == false { + if c.VerifyIssuedAt(now + k.Spec.JWTIssuedAtValidationSkew, false) == false { vErr.Inner = errors.New("token used before issued") vErr.Errors |= jwt.ValidationErrorIssuedAt } } if !k.Spec.JWTDisableNotBeforeValidation { - if c.VerifyNotBefore(now+int64(k.Spec.JWTNotBeforeValidationSkew), false) == false { + if c.VerifyNotBefore(now + k.Spec.JWTNotBeforeValidationSkew, false) == false { vErr.Inner = errors.New("token is not valid yet") vErr.Errors |= jwt.ValidationErrorNotValidYet } diff --git a/mw_jwt_test.go b/mw_jwt_test.go index 602c44d8e10b..5671db498b3b 100644 --- a/mw_jwt_test.go +++ b/mw_jwt_test.go @@ -642,6 +642,16 @@ func TestJWTSessionIssueAtValidationConfigs(t *testing.T) { }) }) + t.Run("IssueAt-JWTDisableIssuedAtValidation--not_valid_jwt", func(t *testing.T) { + spec.JWTDisableIssuedAtValidation = false + spec.JWTIssuedAtValidationSkew = 2 // 2 seconds + loadAPI(spec) + + ts.Run(t, test.TestCase{ + Headers: jwtAuthHeaderGen(-3 * time.Second), Code: http.StatusOK, + }) + }) + t.Run("IssueAt-After_now-Add_skew--Valid_jwt", func(t *testing.T) { spec.JWTDisableIssuedAtValidation = false spec.JWTIssuedAtValidationSkew = 1