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

abhi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 0ea5ec5e8 RANGER-5496: Decouple JWT lifecycle from Ranger plugin via 
pluggable JWT provider (#857)
0ea5ec5e8 is described below

commit 0ea5ec5e80f609b0645bf2d9b5de5b8370382a8f
Author: Abhishek Kumar <[email protected]>
AuthorDate: Wed Feb 25 09:50:11 2026 -0800

    RANGER-5496: Decouple JWT lifecycle from Ranger plugin via pluggable JWT 
provider (#857)
    
    Co-authored-by: Madhan Neethiraj <[email protected]>
---
 .../ranger/admin/client/RangerAdminRESTClient.java |   9 +-
 .../ranger/plugin/authn/DefaultJwtProvider.java    | 123 +++++++++++++++++++++
 .../apache/ranger/plugin/authn/JwtProvider.java    |  24 ++++
 .../plugin/policyengine/RangerPluginContext.java   |  20 ++++
 .../ranger/plugin/service/RangerBasePlugin.java    |   5 +
 .../ranger/plugin/util/RangerRESTClient.java       |  71 +++---------
 6 files changed, 193 insertions(+), 59 deletions(-)

diff --git 
a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
 
b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
index 62aac0a4b..7654017a6 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
@@ -28,6 +28,7 @@
 import org.apache.ranger.audit.provider.MiscUtil;
 import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
 import org.apache.ranger.authorization.utils.StringUtil;
+import org.apache.ranger.plugin.authn.JwtProvider;
 import org.apache.ranger.plugin.model.RangerRole;
 import org.apache.ranger.plugin.util.GrantRevokeRequest;
 import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
@@ -1024,7 +1025,13 @@ public ServiceGdsInfo getGdsInfoIfUpdated(long 
lastKnownVersion, long lastActiva
 
     @Override
     public boolean isAuthenticationEnabled() {
-        return restClient.isAuthFilterPresent() || 
super.isAuthenticationEnabled();
+        return (restClient != null && restClient.isAuthFilterPresent()) || 
super.isAuthenticationEnabled();
+    }
+
+    public void setJwtProvider(JwtProvider jwtProvider) {
+        if (restClient != null) {
+            restClient.setJwtProvider(jwtProvider);
+        }
     }
 
     private void init(String url, String sslConfigFileName, int 
restClientConnTimeOutMs, int restClientReadTimeOutMs, int 
restClientMaxRetryAttempts, int restClientRetryIntervalMs, Configuration 
config) {
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java
new file mode 100644
index 000000000..3c5b1a698
--- /dev/null
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ranger.plugin.authn;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.ranger.authorization.hadoop.utils.RangerCredentialProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class DefaultJwtProvider implements JwtProvider {
+    private static final Logger LOG = 
LoggerFactory.getLogger(DefaultJwtProvider.class);
+
+    public static final String JWT_SOURCE     = ".jwt.source";
+    public static final String JWT_ENV        = ".jwt.env";
+    public static final String JWT_FILE       = ".jwt.file";
+    public static final String JWT_CRED_FILE  = ".jwt.cred.file";
+    public static final String JWT_CRED_ALIAS = ".jwt.cred.alias";
+
+    private final String jwtEnvVar;
+    private final String jwtFilePath;
+    private final String jwtCredFilePath;
+    private final String jwtCredAlias;
+    private       long   jwtFileLastModified;
+    private       long   jwtCredFileLastCheckedAt;
+
+    private volatile String jwt;
+
+    public DefaultJwtProvider(String propertyPrefix, Configuration config) {
+        String jwtSrc = config.get(propertyPrefix + JWT_SOURCE);
+
+        if (jwtSrc == null) {
+            jwtSrc = "";
+        }
+
+        switch (jwtSrc) {
+            case "env":
+                this.jwtEnvVar       = config.get(propertyPrefix + JWT_ENV);
+                this.jwtFilePath     = null;
+                this.jwtCredFilePath = null;
+                this.jwtCredAlias    = null;
+                break;
+
+            case "file":
+                this.jwtEnvVar       = null;
+                this.jwtFilePath     = config.get(propertyPrefix + JWT_FILE);
+                this.jwtCredFilePath = null;
+                this.jwtCredAlias    = null;
+                break;
+
+            case "cred":
+                this.jwtEnvVar       = null;
+                this.jwtFilePath     = null;
+                this.jwtCredFilePath = config.get(propertyPrefix + 
JWT_CRED_FILE);
+                this.jwtCredAlias    = config.get(propertyPrefix + 
JWT_CRED_ALIAS);
+                break;
+
+            default:
+                this.jwtEnvVar       = null;
+                this.jwtFilePath     = null;
+                this.jwtCredFilePath = null;
+                this.jwtCredAlias    = null;
+                break;
+        }
+    }
+
+    @Override
+    public String getJwt() {
+        if (StringUtils.isNotEmpty(jwtEnvVar)) {
+            jwt = System.getenv(jwtEnvVar);
+        } else if (StringUtils.isNotEmpty(jwtFilePath)) {
+            File jwtFile = new File(jwtFilePath);
+
+            if (jwtFile.lastModified() != jwtFileLastModified && 
jwtFile.canRead()) {
+                try (BufferedReader reader = new BufferedReader(new 
FileReader(jwtFile))) {
+                    String line;
+
+                    while ((line = reader.readLine()) != null) {
+                        if (StringUtils.isNotBlank(line) && 
!line.startsWith("#")) {
+                            break;
+                        }
+                    }
+
+                    jwt                 = line;
+                    jwtFileLastModified = jwtFile.lastModified();
+                } catch (IOException e) {
+                    LOG.error("Failed to read JWT from file: {}", jwtFilePath, 
e);
+                }
+            }
+        } else if (StringUtils.isNotEmpty(jwtCredFilePath) && 
StringUtils.isNotEmpty(jwtCredAlias)) {
+            long currentTime = System.currentTimeMillis();
+
+            if ((currentTime - jwtCredFileLastCheckedAt) > 60_000) { // last 
check should be more than 1 minute ago
+                jwt                      = 
RangerCredentialProvider.getInstance().getCredentialString(jwtCredFilePath, 
jwtCredAlias);
+                jwtCredFileLastCheckedAt = currentTime;
+            }
+        }
+
+        return jwt;
+    }
+}
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java 
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java
new file mode 100644
index 000000000..1c9f0bd10
--- /dev/null
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java
@@ -0,0 +1,24 @@
+/*
+ * 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.ranger.plugin.authn;
+
+public interface JwtProvider {
+    String getJwt();
+}
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
index 234262504..db6668cda 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPluginContext.java
@@ -23,6 +23,8 @@
 import org.apache.ranger.admin.client.RangerAdminClient;
 import org.apache.ranger.admin.client.RangerAdminRESTClient;
 import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.authn.DefaultJwtProvider;
+import org.apache.ranger.plugin.authn.JwtProvider;
 import org.apache.ranger.plugin.model.RangerPolicy;
 import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher;
 import org.apache.ranger.plugin.service.RangerAuthContext;
@@ -40,12 +42,14 @@ public class RangerPluginContext {
     private final RangerPluginConfig                                           
              config;
     private final Map<String, Map<RangerPolicy.RangerPolicyResource, 
RangerResourceMatcher>> resourceMatchers = new HashMap<>();
     private final ReentrantReadWriteLock                                       
              lock             = new ReentrantReadWriteLock(true); // fair lock
+    private       JwtProvider                                                  
              jwtProvider;
     private       RangerAuthContext                                            
              authContext;
     private       RangerAuthContextListener                                    
              authContextListener;
     private       RangerAdminClient                                            
              adminClient;
 
     public RangerPluginContext(RangerPluginConfig config) {
         this.config = config;
+        this.jwtProvider = new DefaultJwtProvider(config.getPropertyPrefix() + 
".policy.rest.client", config);
     }
 
     public RangerPluginConfig getConfig() {
@@ -151,6 +155,9 @@ public RangerAdminClient 
createAdminClient(RangerPluginConfig pluginConfig) {
 
         if (ret == null) {
             ret = new RangerAdminRESTClient();
+            if (jwtProvider != null) {
+                ((RangerAdminRESTClient) ret).setJwtProvider(jwtProvider);
+            }
         }
 
         ret.init(pluginConfig.getServiceName(), pluginConfig.getAppId(), 
pluginConfig.getPropertyPrefix(), pluginConfig);
@@ -163,6 +170,19 @@ public RangerAdminClient 
createAdminClient(RangerPluginConfig pluginConfig) {
         return ret;
     }
 
+    public void registerJWTProvider(JwtProvider jwtProvider) {
+        this.jwtProvider = jwtProvider;
+
+        RangerAdminRESTClient restClient = (adminClient instanceof 
RangerAdminRESTClient) ? (RangerAdminRESTClient) adminClient : null;
+        if (restClient != null) {
+            restClient.setJwtProvider(jwtProvider);
+        }
+    }
+
+    public JwtProvider getJwtProvider() {
+        return jwtProvider;
+    }
+
     void cleanResourceMatchers() {
         LOG.debug("==> cleanResourceMatchers()");
 
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
index fac244d44..82292ad25 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java
@@ -32,6 +32,7 @@
 import org.apache.ranger.authorization.hadoop.config.RangerAuditConfig;
 import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
 import org.apache.ranger.authorization.utils.StringUtil;
+import org.apache.ranger.plugin.authn.JwtProvider;
 import org.apache.ranger.plugin.contextenricher.RangerAdminGdsInfoRetriever;
 import org.apache.ranger.plugin.contextenricher.RangerAdminUserStoreRetriever;
 import org.apache.ranger.plugin.contextenricher.RangerContextEnricher;
@@ -302,6 +303,10 @@ public static RangerResourceACLs 
getMergedResourceACLs(RangerResourceACLs baseAC
         return baseACLs;
     }
 
+    public void registerJwtProvider(JwtProvider jwtProvider) {
+        pluginContext.registerJWTProvider(jwtProvider);
+    }
+
     public String getServiceType() {
         return pluginConfig.getServiceType();
     }
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
index 02b375a84..b28901b43 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
@@ -37,6 +37,7 @@
 import org.apache.ranger.authorization.hadoop.utils.RangerCredentialProvider;
 import org.apache.ranger.authorization.utils.JsonUtils;
 import org.apache.ranger.authorization.utils.StringUtil;
+import org.apache.ranger.plugin.authn.JwtProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,11 +50,9 @@
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.Response;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.KeyManagementException;
@@ -109,6 +108,7 @@ public class RangerRESTClient {
     private          int          lastKnownActiveUrlIndex;
     private volatile Client       client;
     private volatile Client       cookieAuthClient;
+    private          JwtProvider  jwtProvider;
     private          ClientFilter jwtAuthFilter;
     private          ClientFilter basicAuthFilter;
 
@@ -190,6 +190,10 @@ public void setBasicAuthInfo(String username, String 
password) {
         setBasicAuthFilter(username, password);
     }
 
+    public void setJwtProvider(JwtProvider jwtProvider) {
+        this.jwtProvider = jwtProvider;
+    }
+
     public WebResource getResource(String relativeUrl) {
         return getClient().resource(getUrl() + relativeUrl);
     }
@@ -773,14 +777,16 @@ private Client buildClient() {
         return client;
     }
 
-    private void setJWTFilter(String jwtAsString) {
-        if (StringUtils.isNotBlank(jwtAsString)) {
+    private void setJWTFilter() {
+        JwtProvider jwtProvider = this.jwtProvider;
+        if (jwtProvider != null) {
             LOG.info("Registering JWT auth header in REST client");
-
             jwtAuthFilter = new ClientFilter() {
                 @Override
                 public ClientResponse handle(ClientRequest clientRequest) 
throws ClientHandlerException {
-                    clientRequest.getHeaders().add("Authorization", 
JWT_HEADER_PREFIX + jwtAsString);
+                    String jwt = jwtProvider.getJwt();
+
+                    clientRequest.getHeaders().putSingle("Authorization", 
JWT_HEADER_PREFIX + jwt);
 
                     return getNext().handle(clientRequest);
                 }
@@ -827,67 +833,16 @@ private void init(Configuration config) {
             }
         }
 
-        String jwtAsString = fetchJWT(propertyPrefix, config);
         String username    = config.get(propertyPrefix + 
".policy.rest.client.username");
         String password    = config.get(propertyPrefix + 
".policy.rest.client.password");
 
-        setJWTFilter(jwtAsString);
+        setJWTFilter();
 
         if (StringUtils.isNotBlank(username) && 
StringUtils.isNotBlank(password)) {
             setBasicAuthFilter(username, password);
         }
     }
 
-    private String fetchJWT(String propertyPrefix, Configuration config) {
-        final String jwtSrc = config.get(propertyPrefix + 
".policy.rest.client.jwt.source");
-
-        if (StringUtils.isNotEmpty(jwtSrc)) {
-            switch (jwtSrc) {
-                case "env":
-                    String jwtEnvVar = config.get(propertyPrefix + 
".policy.rest.client.jwt.env");
-                    if (StringUtils.isNotEmpty(jwtEnvVar)) {
-                        String jwt = System.getenv(jwtEnvVar);
-                        if (StringUtils.isNotBlank(jwt)) {
-                            return jwt;
-                        }
-                    }
-                    break;
-                case "file":
-                    String jwtFilePath = config.get(propertyPrefix + 
".policy.rest.client.jwt.file");
-                    if (StringUtils.isNotEmpty(jwtFilePath)) {
-                        File jwtFile = new File(jwtFilePath);
-                        if (jwtFile.exists()) {
-                            try (BufferedReader reader = new 
BufferedReader(new FileReader(jwtFile))) {
-                                String line = null;
-                                while ((line = reader.readLine()) != null) {
-                                    if (StringUtils.isNotBlank(line) && 
!line.startsWith("#")) {
-                                        return line;
-                                    }
-                                }
-                            } catch (IOException e) {
-                                LOG.error("Failed to read JWT from file: {}", 
jwtFilePath, e);
-                            }
-                        }
-                    }
-                    break;
-                case "cred":
-                    String credFilePath = config.get(propertyPrefix + 
".policy.rest.client.jwt.cred.file");
-                    String credAlias = config.get(propertyPrefix + 
".policy.rest.client.jwt.cred.alias");
-                    if (StringUtils.isNotEmpty(credFilePath) && 
StringUtils.isNotEmpty(credAlias)) {
-                        String jwt = 
RangerCredentialProvider.getInstance().getCredentialString(credFilePath, 
credAlias);
-                        if (StringUtils.isNotBlank(jwt)) {
-                            return jwt;
-                        }
-                    }
-                    break;
-            }
-        } else {
-            LOG.info("JWT source not configured, proceeding without JWT");
-        }
-
-        return null;
-    }
-
     private boolean isSslEnabled(String url) {
         return !StringUtils.isEmpty(url) && 
url.toLowerCase().startsWith("https");
     }

Reply via email to