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");
}