This is an automated email from the ASF dual-hosted git repository.

jongyoul pushed a commit to branch branch-0.12
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/branch-0.12 by this push:
     new ea9b2466e3 [ZEPPELIN-6626] Fix JWT expiration validation security 
vulnerability
ea9b2466e3 is described below

commit ea9b2466e364741a5b7c0fb2615c08ee8eeee57c
Author: Dongmin Cha <[email protected]>
AuthorDate: Tue Aug 5 19:28:58 2025 +0900

    [ZEPPELIN-6626] Fix JWT expiration validation security vulnerability
    
    ### What is this PR for?
      This PR fixes a critical security vulnerability in JWT token validation 
where tokens without expiration time were incorrectly accepted as valid, 
potentially allowing indefinite unauthorized access. The
      fix ensures all JWT tokens must have a valid expiration time and adds 
comprehensive unit tests to prevent regression.
    
      ### What type of PR is it?
      Bug Fix
    
      ### Todos
      * [x] - Fix JWT expiration validation logic to reject null expiration 
tokens
      * [x] - Add security warning logs for rejected tokens
      * [x] - Create comprehensive unit tests for JWT validation scenarios
      * [x] - Verify backwards compatibility with existing valid tokens
    
      ### What is the Jira issue?
      * [ZEPPELIN-6266](https://issues.apache.org/jira/browse/ZEPPELIN-6266) 
Fix JWT expiration validation security vulnerability
    
      ### How should this be tested?
      * **Automated Unit Tests Added**:
        - New test file: `KnoxJwtRealmTest.java` with 3 comprehensive test 
scenarios
        - Run: `mvn test -Dtest=KnoxJwtRealmTest -pl zeppelin-server`
        - All tests pass: `Tests run: 3, Failures: 0, Errors: 0, Skipped: 0`
    
      * **Manual Testing Steps**:
        1. Create JWT token without expiration time → Should be rejected with 
security warning
        2. Create JWT token with valid future expiration → Should be accepted
        3. Create JWT token with past expiration → Should be rejected
        4. Check server logs for security warnings: "JWT token has no 
expiration time - rejecting token for security"
    
      * **Security Validation**:
        - Verify that tokens without expiration are properly rejected
        - Confirm existing valid tokens continue to work
        - Check security event logging
    
      ### Screenshots (if appropriate)
      N/A - Security fix with no UI changes
    
      ### Questions:
      * **Does the license files need to update?** No - only modified existing 
files and added test files with standard Apache license headers
      * **Is there breaking changes for older versions?** No - fully backwards 
compatible. Only affects tokens with missing expiration (which should not exist 
in proper JWT implementations)
      * **Does this needs documentation?** No - internal security fix that 
doesn't change public APIs or configuration
    
    Closes #5007 from chadongmin/ZEPPELIN-6266.
    
    Signed-off-by: Jongyoul Lee <[email protected]>
    (cherry picked from commit 4bf5ab99d32b00c2d80ce7c77d27aecebf58c81d)
    Signed-off-by: Jongyoul Lee <[email protected]>
---
 .../apache/zeppelin/realm/jwt/KnoxJwtRealm.java    |  10 +-
 .../zeppelin/realm/jwt/KnoxJwtRealmTest.java       | 102 +++++++++++++++++++++
 2 files changed, 108 insertions(+), 4 deletions(-)

diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealm.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealm.java
index 3a036889e6..0a1d8decc9 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealm.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealm.java
@@ -192,10 +192,12 @@ public class KnoxJwtRealm extends AuthorizingRealm {
     boolean valid = false;
     try {
       Date expires = jwtToken.getJWTClaimsSet().getExpirationTime();
-      if (expires == null || new Date().before(expires)) {
-        if (LOGGER.isDebugEnabled()) {
-          LOGGER.debug("SSO token expiration date has been " + "successfully 
validated");
-        }
+      if (expires == null) {
+        LOGGER.warn("JWT token has no expiration time - rejecting token for 
security");
+        return false;
+      }
+      if (new Date().before(expires)) {
+        LOGGER.debug("SSO token expiration date has been successfully 
validated");
         valid = true;
       } else {
         LOGGER.warn("SSO expiration date validation failed.");
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealmTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealmTest.java
new file mode 100644
index 0000000000..d9860f9107
--- /dev/null
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/realm/jwt/KnoxJwtRealmTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.zeppelin.realm.jwt;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class KnoxJwtRealmTest {
+
+  private KnoxJwtRealm knoxJwtRealm;
+
+  @BeforeEach
+  void setUp() throws Exception {
+    knoxJwtRealm = new KnoxJwtRealm();
+  }
+
+  private SignedJWT createJWT(String subject, Date expiration) throws 
Exception {
+    JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder()
+        .subject(subject)
+        .issuer("KNOXSSO")
+        .issueTime(new Date());
+    
+    if (expiration != null) {
+      claimsBuilder.expirationTime(expiration);
+    }
+    
+    JWTClaimsSet claimsSet = claimsBuilder.build();
+    SignedJWT signedJWT = new SignedJWT(
+        new JWSHeader.Builder(JWSAlgorithm.RS256).build(),
+        claimsSet
+    );
+
+    // Note: We don't sign the JWT for these tests as we're only testing 
expiration validation
+    return signedJWT;
+  }
+
+  @Test
+  void testValidateExpiration_WithNullExpiration_ShouldReturnFalse() throws 
Exception {
+    // Given: JWT token without expiration time
+    SignedJWT jwtWithoutExpiration = createJWT("testuser", null);
+    
+    // When: validating expiration
+    boolean result = knoxJwtRealm.validateExpiration(jwtWithoutExpiration);
+    
+    // Then: should return false
+    assertFalse(result, "JWT token without expiration time should be 
rejected");
+  }
+
+  @Test
+  void testValidateExpiration_WithValidFutureExpiration_ShouldReturnTrue() 
throws Exception {
+    // Given: JWT token with future expiration
+    Date futureDate = new Date(System.currentTimeMillis() + 3600000); // 1 
hour from now
+    SignedJWT jwtWithValidExpiration = createJWT("testuser", futureDate);
+    
+    // When: validating expiration
+    boolean result = knoxJwtRealm.validateExpiration(jwtWithValidExpiration);
+    
+    // Then: should return true
+    assertTrue(result, "JWT token with valid future expiration should be 
accepted");
+  }
+
+  @Test
+  void testValidateExpiration_WithPastExpiration_ShouldReturnFalse() throws 
Exception {
+    // Given: JWT token with past expiration
+    Date pastDate = new Date(System.currentTimeMillis() - 3600000); // 1 hour 
ago
+    SignedJWT jwtWithPastExpiration = createJWT("testuser", pastDate);
+    
+    // When: validating expiration
+    boolean result = knoxJwtRealm.validateExpiration(jwtWithPastExpiration);
+    
+    // Then: should return false
+    assertFalse(result, "JWT token with past expiration should be rejected");
+  }
+
+  // Note: Full token validation tests are omitted as they require complex 
setup
+  // including certificate files and signature validation. The core expiration 
+  // validation logic is tested above through direct method calls.
+}

Reply via email to