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

kumaab pushed a commit to branch ranger-2.10
in repository https://gitbox.apache.org/repos/asf/ranger.git


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

commit fba4111eac6b455167f19ca9d077313f2effd950
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]>
    (cherry picked from commit 0ea5ec5e80f609b0645bf2d9b5de5b8370382a8f)
---
 .../destination/RangerAuditServerDestination.java  |   5 +
 .../ranger/admin/client/RangerAdminRESTClient.java |  15 ++-
 .../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       |  93 +++++-----------
 7 files changed, 217 insertions(+), 68 deletions(-)

diff --git 
a/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
 
b/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
index 0dbb01ede..802e96113 100644
--- 
a/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
+++ 
b/agents-audit/dest-auditserver/src/main/java/org/apache/ranger/audit/destination/RangerAuditServerDestination.java
@@ -28,6 +28,7 @@
 import org.apache.ranger.audit.model.AuditEventBase;
 import org.apache.ranger.audit.model.AuthzAuditEvent;
 import org.apache.ranger.audit.provider.MiscUtil;
+import org.apache.ranger.plugin.authn.DefaultJwtProvider;
 import org.apache.ranger.plugin.util.RangerRESTClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -88,6 +89,10 @@ public void init(Properties props, String propPrefix) {
 
         this.restClient = new RangerRESTClient(url, sslConfigFileName, config);
 
+        if (AUTH_TYPE_JWT.equalsIgnoreCase(authType)) {
+            this.restClient.setJwtProvider(new 
DefaultJwtProvider("ranger.plugin.policy.rest.client", config));
+        }
+
         this.restClient.setRestClientConnTimeOutMs(connTimeoutMs);
         this.restClient.setRestClientReadTimeOutMs(readTimeoutMs);
         this.restClient.setMaxRetryAttempts(maxRetryAttempts);
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 f76c6d353..ceae18bc4 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
@@ -30,6 +30,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.*;
 import org.slf4j.Logger;
@@ -52,6 +53,7 @@ public class RangerAdminRESTClient extends 
AbstractRangerAdminClient {
     private String           serviceNameUrlParam;
        private String           pluginId;
        private String           clusterName;
+       private JwtProvider      jwtProvider;
        private RangerRESTClient restClient;
        private RangerRESTUtils  restUtils   = new RangerRESTUtils();
        private boolean                  supportsPolicyDeltas;
@@ -761,7 +763,15 @@ public void revokeAccess(final GrantRevokeRequest request) 
throws Exception {
 
        @Override
        public boolean isAuthenticationEnabled() {
-               return restClient.isAuthFilterPresent() || 
super.isAuthenticationEnabled();
+               return (restClient != null && restClient.isAuthFilterPresent()) 
|| super.isAuthenticationEnabled();
+       }
+
+       public void setJwtProvider(JwtProvider jwtProvider) {
+               this.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) {
@@ -770,6 +780,9 @@ private void init(String url, String sslConfigFileName, int 
restClientConnTimeOu
                }
 
                restClient = new RangerRESTClient(url, sslConfigFileName, 
config);
+               if (jwtProvider != null) {
+                       restClient.setJwtProvider(jwtProvider);
+               }
                restClient.setRestClientConnTimeOutMs(restClientConnTimeOutMs);
                restClient.setRestClientReadTimeOutMs(restClientReadTimeOutMs);
                restClient.setMaxRetryAttempts(restClientMaxRetryAttempts);
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 5dc25245c..8b763748e 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;
@@ -38,6 +40,7 @@ public class RangerPluginContext {
        private static final Logger LOG = 
LoggerFactory.getLogger(RangerPluginContext.class);
 
        private final    RangerPluginConfig                                     
                    config;
+       private          JwtProvider                                            
                    jwtProvider;
        private          RangerAuthContext                                      
                    authContext;
        private          RangerAuthContextListener                              
                    authContextListener;
        private          RangerAdminClient                                      
                    adminClient;
@@ -47,6 +50,7 @@ public class RangerPluginContext {
 
        public RangerPluginContext(RangerPluginConfig config) {
                this.config = config;
+               this.jwtProvider = new 
DefaultJwtProvider(config.getPropertyPrefix() + ".policy.rest.client", config);
        }
 
        public RangerPluginConfig getConfig() { return  config; }
@@ -170,6 +174,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);
@@ -182,4 +189,17 @@ 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;
+       }
 }
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 9f8b4f9b6..970473889 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
@@ -34,6 +34,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.RangerAdminUserStoreRetriever;
 import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
 import org.apache.ranger.plugin.model.RangerBaseModelObject;
@@ -215,6 +216,10 @@ public static AuditHandler getAuditProvider(String 
serviceName) {
                return ret;
        }
 
+       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 777f3bc75..aa7773612 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
@@ -19,13 +19,12 @@
 
 package org.apache.ranger.plugin.util;
 
-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.nio.charset.StandardCharsets;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -33,6 +32,7 @@
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
+import java.util.Base64;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
@@ -57,6 +57,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;
 
@@ -118,8 +119,10 @@ public class RangerRESTClient {
 
        private volatile Client client;
        private volatile Client cookieAuthClient;
+       private          JwtProvider  jwtProvider;
        private          ClientFilter jwtAuthFilter;
        private          ClientFilter basicAuthFilter;
+       private volatile String       basicAuthHeader;
 
        public RangerRESTClient(String url, String sslConfigFileName, 
Configuration config) {
                this(url, sslConfigFileName, config, getPropertyPrefix(config));
@@ -191,6 +194,13 @@ public void setBasicAuthInfo(String username, String 
password) {
                setBasicAuthFilter(username, password);
        }
 
+       public void setJwtProvider(JwtProvider jwtProvider) {
+               this.jwtProvider = jwtProvider;
+
+               setJWTFilter();
+               resetClient();
+       }
+
        public WebResource getResource(String relativeUrl) {
                WebResource ret = getClient().resource(getUrl() + relativeUrl);
 
@@ -290,14 +300,21 @@ 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 = 
StringUtils.trimToNull(jwtProvider.getJwt());
+
+                                       /* without a token, fall back to basic 
auth when it is configured */
+                                       if (jwt != null) {
+                                               
clientRequest.getHeaders().putSingle("Authorization", JWT_HEADER_PREFIX + jwt);
+                                       } else if (basicAuthHeader != null) {
+                                               
clientRequest.getHeaders().putSingle("Authorization", basicAuthHeader);
+                                       }
 
                                        return getNext().handle(clientRequest);
                                }
@@ -310,8 +327,10 @@ public ClientResponse handle(ClientRequest clientRequest) 
throws ClientHandlerEx
        private void setBasicAuthFilter(String username, String password) {
                if (StringUtils.isNotEmpty(username) && 
StringUtils.isNotEmpty(password)) {
                        basicAuthFilter = new HTTPBasicAuthFilter(username, 
password);
+                       basicAuthHeader = "Basic " + 
Base64.getEncoder().encodeToString((username + ":" + 
password).getBytes(StandardCharsets.UTF_8));
                } else {
                        basicAuthFilter = null;
+                       basicAuthHeader = null;
                }
        }
 
@@ -350,76 +369,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;
-
-                                                               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