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