From 7f263ac41282c3919fc2bacab90630e5749030f0 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 10 Sep 2026 17:18:03 -0700 Subject: [PATCH 1/4] RANGER-5795: Support pluggable token supplier in Ranger REST client to obtain and refresh bearer tokens dynamically --- .../RangerAuditServerDestination.java | 2 +- .../admin/client/RangerAdminRESTClient.java | 14 +- .../plugin/authn/DefaultJwtProvider.java | 6 +- .../ranger/plugin/authn/JwtProvider.java | 24 ---- .../policyengine/RangerPluginContext.java | 66 +++++++-- .../plugin/service/RangerBasePlugin.java | 6 +- .../ranger/plugin/util/RangerRESTClient.java | 18 +-- .../plugin/authn/TestDefaultJwtProvider.java | 52 ++++++++ .../TestRangerPluginContextJwtProvider.java | 125 ++++++++++++++++++ .../plugin/util/TestRangerRESTClient.java | 38 ++++++ 10 files changed, 294 insertions(+), 57 deletions(-) delete mode 100644 agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java create mode 100644 agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java create mode 100644 agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java 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 421ac0710bb..1b029de83e0 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 @@ -91,7 +91,7 @@ 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.setTokenSupplier(new DefaultJwtProvider("ranger.plugin.policy.rest.client", config)); } this.restClient.setRestClientConnTimeOutMs(connTimeoutMs); 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 1a35eab1daa..dfd295bbce0 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 @@ -27,7 +27,6 @@ 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; @@ -55,6 +54,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; public class RangerAdminRESTClient extends AbstractRangerAdminClient { private static final Logger LOG = LoggerFactory.getLogger(RangerAdminRESTClient.class); @@ -67,7 +67,7 @@ public class RangerAdminRESTClient extends AbstractRangerAdminClient { private String serviceNameUrlParam; private String pluginId; private String clusterName; - private JwtProvider jwtProvider; + private Supplier tokenSupplier; private RangerRESTClient restClient; private boolean supportsPolicyDeltas; private boolean supportsTagDeltas; @@ -1021,11 +1021,11 @@ public boolean isAuthenticationEnabled() { return (restClient != null && restClient.isAuthFilterPresent()) || super.isAuthenticationEnabled(); } - public void setJwtProvider(JwtProvider jwtProvider) { - this.jwtProvider = jwtProvider; + public void setTokenSupplier(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; if (restClient != null) { - restClient.setJwtProvider(jwtProvider); + restClient.setTokenSupplier(tokenSupplier); } } @@ -1033,8 +1033,8 @@ private void init(String url, String sslConfigFileName, int restClientConnTimeOu LOG.debug("==> RangerAdminRESTClient.init({}, {})", url, sslConfigFileName); restClient = new RangerRESTClient(url, sslConfigFileName, config); - if (jwtProvider != null) { - restClient.setJwtProvider(jwtProvider); + if (tokenSupplier != null) { + restClient.setTokenSupplier(tokenSupplier); } restClient.setRestClientConnTimeOutMs(restClientConnTimeOutMs); 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 index 3c5b1a698ec..e5ce8d37f9d 100644 --- 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 @@ -29,8 +29,9 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.function.Supplier; -public class DefaultJwtProvider implements JwtProvider { +public class DefaultJwtProvider implements Supplier { private static final Logger LOG = LoggerFactory.getLogger(DefaultJwtProvider.class); public static final String JWT_SOURCE = ".jwt.source"; @@ -38,6 +39,7 @@ public class DefaultJwtProvider implements JwtProvider { 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"; + public static final String JWT_PROVIDER = ".jwt.provider"; // class name of a Supplier implementation; defaults to DefaultJwtProvider private final String jwtEnvVar; private final String jwtFilePath; @@ -87,7 +89,7 @@ public DefaultJwtProvider(String propertyPrefix, Configuration config) { } @Override - public String getJwt() { + public String get() { if (StringUtils.isNotEmpty(jwtEnvVar)) { jwt = System.getenv(jwtEnvVar); } else if (StringUtils.isNotEmpty(jwtFilePath)) { 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 deleted file mode 100644 index 1c9f0bd1094..00000000000 --- a/agents-common/src/main/java/org/apache/ranger/plugin/authn/JwtProvider.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 db6668cda4f..375a9a20bca 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 @@ -20,11 +20,11 @@ package org.apache.ranger.plugin.policyengine; import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.conf.Configuration; 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; @@ -32,9 +32,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; public class RangerPluginContext { private static final Logger LOG = LoggerFactory.getLogger(RangerPluginContext.class); @@ -42,20 +44,62 @@ public class RangerPluginContext { private final RangerPluginConfig config; private final Map> resourceMatchers = new HashMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); // fair lock - private JwtProvider jwtProvider; + private Supplier tokenSupplier; 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); + this.config = config; + this.tokenSupplier = getTokenSupplier(config.getPropertyPrefix() + ".policy.rest.client", config); } public RangerPluginConfig getConfig() { return config; } + private static Supplier getTokenSupplier(String propertyPrefix, RangerPluginConfig config) { + String clzName = config.get(propertyPrefix + DefaultJwtProvider.JWT_PROVIDER); + Supplier ret; + + if (StringUtils.isBlank(clzName) || DefaultJwtProvider.class.getName().equals(clzName)) { + ret = new DefaultJwtProvider(propertyPrefix, config); + } else { + ret = getCustomTokenSupplier(clzName, config); + } + + LOG.info("Using Token supplier [{}], config: [{}]", ret.getClass().getName(), propertyPrefix + DefaultJwtProvider.JWT_PROVIDER); + + return ret; + } + + @SuppressWarnings("unchecked") + private static Supplier getCustomTokenSupplier(String clzName, RangerPluginConfig config) { + Supplier ret; + + try { + Class clz = Class.forName(clzName); + + if (!Supplier.class.isAssignableFrom(clz)) { + throw new IllegalArgumentException(clzName + " does not implement " + Supplier.class.getName()); + } + + try { + // prefer a constructor that accepts the Ranger Configuration + Constructor ctor = clz.getDeclaredConstructor(Configuration.class); + + ret = (Supplier) ctor.newInstance(config); + } catch (NoSuchMethodException excp) { + // fall back to the no-argument constructor + ret = (Supplier) clz.getDeclaredConstructor().newInstance(); + } + } catch (ReflectiveOperationException excp) { + throw new IllegalArgumentException("Failed to instantiate custom token supplier: " + clzName, excp); + } + + return ret; + } + public String getClusterName() { return config.getClusterName(); } @@ -155,8 +199,8 @@ public RangerAdminClient createAdminClient(RangerPluginConfig pluginConfig) { if (ret == null) { ret = new RangerAdminRESTClient(); - if (jwtProvider != null) { - ((RangerAdminRESTClient) ret).setJwtProvider(jwtProvider); + if (tokenSupplier != null) { + ((RangerAdminRESTClient) ret).setTokenSupplier(tokenSupplier); } } @@ -170,17 +214,17 @@ public RangerAdminClient createAdminClient(RangerPluginConfig pluginConfig) { return ret; } - public void registerJWTProvider(JwtProvider jwtProvider) { - this.jwtProvider = jwtProvider; + public void registerTokenSupplier(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; RangerAdminRESTClient restClient = (adminClient instanceof RangerAdminRESTClient) ? (RangerAdminRESTClient) adminClient : null; if (restClient != null) { - restClient.setJwtProvider(jwtProvider); + restClient.setTokenSupplier(tokenSupplier); } } - public JwtProvider getJwtProvider() { - return jwtProvider; + public Supplier getTokenSupplier() { + return tokenSupplier; } void 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 3a3411d30de..33bf339499b 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,7 +32,6 @@ 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; @@ -87,6 +86,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; public class RangerBasePlugin { private static final Logger LOG = LoggerFactory.getLogger(RangerBasePlugin.class); @@ -303,8 +303,8 @@ public static RangerResourceACLs getMergedResourceACLs(RangerResourceACLs baseAC return baseACLs; } - public void registerJwtProvider(JwtProvider jwtProvider) { - pluginContext.registerJWTProvider(jwtProvider); + public void registerTokenSupplier(Supplier tokenSupplier) { + pluginContext.registerTokenSupplier(tokenSupplier); } public String 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 94046a549ef..8dfcf5e47dc 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 @@ -26,7 +26,6 @@ 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.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.slf4j.Logger; @@ -67,6 +66,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; public class RangerRESTClient { private static final Logger LOG = LoggerFactory.getLogger(RangerRESTClient.class); @@ -143,7 +143,7 @@ public String getMethod() { private int lastKnownActiveUrlIndex; private volatile Client client; private volatile Client cookieAuthClient; - private JwtProvider jwtProvider; + private Supplier tokenSupplier; private volatile String authHeader; private volatile Map trustedAuthHeaders = Collections.emptyMap(); @@ -183,7 +183,7 @@ public String getPassword() { } public boolean isAuthFilterPresent() { - return jwtProvider != null || hasBasicAuth(); + return tokenSupplier != null || hasBasicAuth(); } public int getRestClientConnTimeOutMs() { @@ -235,8 +235,8 @@ public void setBasicAuthInfo(String username, String password) { setBasicAuthFilter(username, password); } - public void setJwtProvider(JwtProvider jwtProvider) { - this.jwtProvider = jwtProvider; + public void setTokenSupplier(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; resetClient(); } @@ -664,7 +664,7 @@ private Client buildClient() { // Validate that MOXy prevention is properly configured RangerJersey2ClientBuilder.validateAntiMoxyConfiguration(config); - if (jwtProvider != null) { + if (tokenSupplier != null) { config.register(new javax.ws.rs.client.ClientRequestFilter() { @Override public void filter(javax.ws.rs.client.ClientRequestContext requestContext) { @@ -695,7 +695,7 @@ public void filter(javax.ws.rs.client.ClientRequestContext requestContext) { } private void setJWTFilter() { - JwtProvider provider = jwtProvider; + Supplier provider = tokenSupplier; if (provider != null) { LOG.info("Registering JWT auth header in REST client"); @@ -818,9 +818,9 @@ private String getAuthHeader() { } private String getCurrentJwt() { - JwtProvider provider = jwtProvider; + Supplier provider = tokenSupplier; - return provider != null ? StringUtils.trimToNull(provider.getJwt()) : null; + return provider != null ? StringUtils.trimToNull(provider.get()) : null; } private boolean hasBasicAuth() { diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java new file mode 100644 index 00000000000..4f0498218f4 --- /dev/null +++ b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java @@ -0,0 +1,52 @@ +/* + * 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.hadoop.conf.Configuration; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestDefaultJwtProvider { + @Test + public void defaultJwtProviderIsSupplierAndReadsTokenFromFile() throws Exception { + assertTrue(Supplier.class.isAssignableFrom(DefaultJwtProvider.class), "DefaultJwtProvider should implement Supplier"); + + File jwtFile = File.createTempFile("ranger-jwt", ".token"); + + jwtFile.deleteOnExit(); + + Files.write(jwtFile.toPath(), "file-jwt-token".getBytes()); + + Configuration config = new Configuration(); + + config.set("test.prefix" + DefaultJwtProvider.JWT_SOURCE, "file"); + config.set("test.prefix" + DefaultJwtProvider.JWT_FILE, jwtFile.getAbsolutePath()); + + Supplier provider = new DefaultJwtProvider("test.prefix", config); + + assertEquals("file-jwt-token", provider.get()); + } +} diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java new file mode 100644 index 00000000000..a06e043eee5 --- /dev/null +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java @@ -0,0 +1,125 @@ +/* + * 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.policyengine; + +import org.apache.hadoop.conf.Configuration; +import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig; +import org.apache.ranger.plugin.authn.DefaultJwtProvider; +import org.junit.jupiter.api.Test; + +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestRangerPluginContextJwtProvider { + @Test + public void usesDefaultProviderWhenNotConfigured() { + RangerPluginConfig config = newConfig(); + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + } + + @Test + public void usesDefaultProviderWhenExplicitlyConfigured() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), DefaultJwtProvider.class.getName()); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + } + + @Test + public void usesCustomProviderWithNoArgConstructor() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), NoArgJwtProvider.class.getName()); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(NoArgJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals("no-arg-token", ctx.getTokenSupplier().get()); + } + + @Test + public void usesCustomProviderWithConfigConstructor() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), ConfigJwtProvider.class.getName()); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(ConfigJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals("config-token", ctx.getTokenSupplier().get()); + } + + @Test + public void throwsForClassThatIsNotSupplier() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), NotASupplier.class.getName()); + + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + } + + @Test + public void throwsForUnknownClass() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), "org.apache.ranger.NoSuchJwtProvider"); + + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + } + + private RangerPluginConfig newConfig() { + return new RangerPluginConfig("hive", "test-service", "test-app", "cl1", "on-perm", new RangerPolicyEngineOptions()); + } + + private String jwtProviderProperty(RangerPluginConfig config) { + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultJwtProvider.JWT_PROVIDER; + } + + public static class NoArgJwtProvider implements Supplier { + @Override + public String get() { + return "no-arg-token"; + } + } + + public static class ConfigJwtProvider implements Supplier { + public ConfigJwtProvider(Configuration config) { + // custom providers may accept the Ranger Configuration + } + + @Override + public String get() { + return "config-token"; + } + } + + public static class NotASupplier { + public String get() { + return "not-a-supplier"; + } + } +} diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java b/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java index 2f5f6ca221e..a8395ec78f2 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerRESTClient.java @@ -65,6 +65,44 @@ public void testPluginInit_WithValidUrl_Succeeds() { assertNotNull(plugin, "RangerBasePlugin should be initialized successfully"); } + @Test + public void jwtProviderInjectsBearerAuthorizationHeader() throws Exception { + AtomicReference capturedAuthHeader = new AtomicReference<>(); + HttpServer httpServer = HttpServer.create(new InetSocketAddress(0), 0); + + httpServer.createContext("/", exchange -> { + List values = exchange.getRequestHeaders().get("Authorization"); + + if (values != null && !values.isEmpty()) { + capturedAuthHeader.set(values.get(0)); + } + + exchange.sendResponseHeaders(200, -1); + exchange.close(); + }); + + httpServer.start(); + + try { + String serverUrl = "http://localhost:" + httpServer.getAddress().getPort(); + Configuration conf = new Configuration(); + RangerRESTClient client = new RangerRESTClient(serverUrl, null, conf); + + // custom token provider is now a plain java.util.function.Supplier (no dependency on Ranger interfaces) + client.setTokenSupplier(() -> "test-jwt-token"); + + assertTrue(client.isAuthFilterPresent(), "JWT auth filter should be present when a token supplier is registered"); + + try (Response response = client.get("/test", Collections.emptyMap())) { + assertEquals(200, response.getStatus()); + } + + assertEquals("Bearer test-jwt-token", capturedAuthHeader.get()); + } finally { + httpServer.stop(0); + } + } + @Test public void setTrustedAuthHeadersAddsHeaderToOutboundRequest() throws Exception { AtomicReference capturedSpiffeHeader = new AtomicReference<>(); From e4a769911bee00b8e768c4eabccd9f623fe69b15 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 11 Sep 2026 19:35:21 -0700 Subject: [PATCH 2/4] RANGER-5795: Do not create a token supplier when JWT is not configured --- .../policyengine/RangerPluginContext.java | 99 +++++++++++-------- .../TestRangerPluginContextJwtProvider.java | 77 ++++++++++++--- 2 files changed, 123 insertions(+), 53 deletions(-) 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 375a9a20bca..9e98b683841 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 @@ -58,48 +58,6 @@ public RangerPluginConfig getConfig() { return config; } - private static Supplier getTokenSupplier(String propertyPrefix, RangerPluginConfig config) { - String clzName = config.get(propertyPrefix + DefaultJwtProvider.JWT_PROVIDER); - Supplier ret; - - if (StringUtils.isBlank(clzName) || DefaultJwtProvider.class.getName().equals(clzName)) { - ret = new DefaultJwtProvider(propertyPrefix, config); - } else { - ret = getCustomTokenSupplier(clzName, config); - } - - LOG.info("Using Token supplier [{}], config: [{}]", ret.getClass().getName(), propertyPrefix + DefaultJwtProvider.JWT_PROVIDER); - - return ret; - } - - @SuppressWarnings("unchecked") - private static Supplier getCustomTokenSupplier(String clzName, RangerPluginConfig config) { - Supplier ret; - - try { - Class clz = Class.forName(clzName); - - if (!Supplier.class.isAssignableFrom(clz)) { - throw new IllegalArgumentException(clzName + " does not implement " + Supplier.class.getName()); - } - - try { - // prefer a constructor that accepts the Ranger Configuration - Constructor ctor = clz.getDeclaredConstructor(Configuration.class); - - ret = (Supplier) ctor.newInstance(config); - } catch (NoSuchMethodException excp) { - // fall back to the no-argument constructor - ret = (Supplier) clz.getDeclaredConstructor().newInstance(); - } - } catch (ReflectiveOperationException excp) { - throw new IllegalArgumentException("Failed to instantiate custom token supplier: " + clzName, excp); - } - - return ret; - } - public String getClusterName() { return config.getClusterName(); } @@ -240,4 +198,61 @@ void cleanResourceMatchers() { LOG.debug("<== cleanResourceMatchers()"); } + + private static Supplier getTokenSupplier(String propertyPrefix, RangerPluginConfig config) { + String providerProp = propertyPrefix + DefaultJwtProvider.JWT_PROVIDER; + String clzName = config.get(providerProp); + Supplier ret = null; + + if (StringUtils.isNotBlank(clzName) && !DefaultJwtProvider.class.getName().equals(clzName)) { + ret = getCustomTokenSupplier(clzName, config, providerProp); + } + + /* DefaultJwtProvider is used only when configured explicitly or when a JWT source is set: otherwise no + * token supplier is created, so the plugin keeps the authentication it had before (basic-auth or none). */ + if (ret == null && (DefaultJwtProvider.class.getName().equals(clzName) || isJwtSourceConfigured(propertyPrefix, config))) { + ret = new DefaultJwtProvider(propertyPrefix, config); + } + + if (ret != null) { + LOG.info("Using Token supplier [{}], config: [{}]", ret.getClass().getName(), providerProp); + } else { + LOG.debug("No token supplier configured, config: [{}]", providerProp); + } + + return ret; + } + + private static boolean isJwtSourceConfigured(String propertyPrefix, RangerPluginConfig config) { + return StringUtils.isNotBlank(config.get(propertyPrefix + DefaultJwtProvider.JWT_SOURCE)); + } + + @SuppressWarnings("unchecked") + private static Supplier getCustomTokenSupplier(String clzName, RangerPluginConfig config, String providerProp) { + Supplier ret = null; + + /* a misconfigured supplier must not prevent plugin initialization: this returns null and the caller decides + * the fallback. Throwable covers LinkageError/NoClassDefFoundError from a class on an incomplete classpath. */ + try { + Class clz = Class.forName(clzName); + + if (Supplier.class.isAssignableFrom(clz)) { + try { + /* prefer a constructor that accepts the Ranger Configuration */ + Constructor ctor = clz.getDeclaredConstructor(Configuration.class); + + ret = (Supplier) ctor.newInstance(config); + } catch (NoSuchMethodException excp) { + /* fall back to the no-argument constructor */ + ret = (Supplier) clz.getDeclaredConstructor().newInstance(); + } + } else { + LOG.error("{}={}: class does not implement {}. Requests may be sent without a bearer token", providerProp, clzName, Supplier.class.getName()); + } + } catch (Throwable excp) { + LOG.error("{}={}: failed to instantiate token supplier. Requests may be sent without a bearer token", providerProp, clzName, excp); + } + + return ret; + } } diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java index a06e043eee5..1abfac55612 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java @@ -27,14 +27,25 @@ import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertNull; public class TestRangerPluginContextJwtProvider { @Test - public void usesDefaultProviderWhenNotConfigured() { + public void noTokenSupplierWhenNotConfigured() { RangerPluginConfig config = newConfig(); RangerPluginContext ctx = new RangerPluginContext(config); + assertNull(ctx.getTokenSupplier()); + } + + @Test + public void usesDefaultProviderWhenJwtSourceConfigured() { + RangerPluginConfig config = newConfig(); + + config.set(jwtSourceProperty(config), "env"); + + RangerPluginContext ctx = new RangerPluginContext(config); + assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); } @@ -74,29 +85,50 @@ public void usesCustomProviderWithConfigConstructor() { } @Test - public void throwsForClassThatIsNotSupplier() { + public void fallsBackToDefaultForClassThatIsNotSupplier() { RangerPluginConfig config = newConfig(); config.set(jwtProviderProperty(config), NotASupplier.class.getName()); + config.set(jwtSourceProperty(config), "env"); + + RangerPluginContext ctx = new RangerPluginContext(config); - assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); } @Test - public void throwsForUnknownClass() { + public void fallsBackToDefaultForUnknownClass() { RangerPluginConfig config = newConfig(); config.set(jwtProviderProperty(config), "org.apache.ranger.NoSuchJwtProvider"); + config.set(jwtSourceProperty(config), "env"); + + RangerPluginContext ctx = new RangerPluginContext(config); - assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); } - private RangerPluginConfig newConfig() { - return new RangerPluginConfig("hive", "test-service", "test-app", "cl1", "on-perm", new RangerPolicyEngineOptions()); + @Test + public void fallsBackToDefaultWhenProviderConstructorThrows() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), FailingJwtProvider.class.getName()); + config.set(jwtSourceProperty(config), "env"); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); } - private String jwtProviderProperty(RangerPluginConfig config) { - return config.getPropertyPrefix() + ".policy.rest.client" + DefaultJwtProvider.JWT_PROVIDER; + @Test + public void noTokenSupplierWhenCustomProviderFailsAndNoJwtSource() { + RangerPluginConfig config = newConfig(); + + config.set(jwtProviderProperty(config), "org.apache.ranger.NoSuchJwtProvider"); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertNull(ctx.getTokenSupplier()); } public static class NoArgJwtProvider implements Supplier { @@ -108,7 +140,7 @@ public String get() { public static class ConfigJwtProvider implements Supplier { public ConfigJwtProvider(Configuration config) { - // custom providers may accept the Ranger Configuration + /* custom providers may accept the Ranger Configuration */ } @Override @@ -122,4 +154,27 @@ public String get() { return "not-a-supplier"; } } + + public static class FailingJwtProvider implements Supplier { + public FailingJwtProvider() { + throw new IllegalStateException("token source unavailable"); + } + + @Override + public String get() { + return "never-returned"; + } + } + + private RangerPluginConfig newConfig() { + return new RangerPluginConfig("hive", "test-service", "test-app", "cl1", "on-perm", new RangerPolicyEngineOptions()); + } + + private String jwtProviderProperty(RangerPluginConfig config) { + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultJwtProvider.JWT_PROVIDER; + } + + private String jwtSourceProperty(RangerPluginConfig config) { + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultJwtProvider.JWT_SOURCE; + } } From 5863fe122ab134d930be2dc88ceed31e2472624d Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Sun, 13 Sep 2026 00:08:12 -0700 Subject: [PATCH 3/4] RANGER-5795: Rename to DefaultTokenSupplier for consistency, make tokenSupplier volatile --- .../RangerAuditServerDestination.java | 4 +- ...rovider.java => DefaultTokenSupplier.java} | 10 ++-- .../policyengine/RangerPluginContext.java | 14 +++--- .../ranger/plugin/util/RangerRESTClient.java | 48 +++++++++---------- ...der.java => TestDefaultTokenSupplier.java} | 12 ++--- ...TestRangerPluginContextTokenSupplier.java} | 48 +++++++++---------- 6 files changed, 69 insertions(+), 67 deletions(-) rename agents-common/src/main/java/org/apache/ranger/plugin/authn/{DefaultJwtProvider.java => DefaultTokenSupplier.java} (92%) rename agents-common/src/test/java/org/apache/ranger/plugin/authn/{TestDefaultJwtProvider.java => TestDefaultTokenSupplier.java} (73%) rename agents-common/src/test/java/org/apache/ranger/plugin/policyengine/{TestRangerPluginContextJwtProvider.java => TestRangerPluginContextTokenSupplier.java} (71%) 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 1b029de83e0..07a546650fe 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 @@ -26,7 +26,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.authn.DefaultTokenSupplier; import org.apache.ranger.plugin.util.PluginHeaderAuthConfig; import org.apache.ranger.plugin.util.RangerRESTClient; import org.slf4j.Logger; @@ -91,7 +91,7 @@ public void init(Properties props, String propPrefix) { this.restClient = new RangerRESTClient(url, sslConfigFileName, config); if (AUTH_TYPE_JWT.equalsIgnoreCase(authType)) { - this.restClient.setTokenSupplier(new DefaultJwtProvider("ranger.plugin.policy.rest.client", config)); + this.restClient.setTokenSupplier(new DefaultTokenSupplier("ranger.plugin.policy.rest.client", config)); } this.restClient.setRestClientConnTimeOutMs(connTimeoutMs); 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/DefaultTokenSupplier.java similarity index 92% rename from agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java rename to agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultTokenSupplier.java index e5ce8d37f9d..25ee5d956d3 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultJwtProvider.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/authn/DefaultTokenSupplier.java @@ -31,15 +31,17 @@ import java.io.IOException; import java.util.function.Supplier; -public class DefaultJwtProvider implements Supplier { - private static final Logger LOG = LoggerFactory.getLogger(DefaultJwtProvider.class); +public class DefaultTokenSupplier implements Supplier { + private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenSupplier.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"; - public static final String JWT_PROVIDER = ".jwt.provider"; // class name of a Supplier implementation; defaults to DefaultJwtProvider + + // class name of a Supplier implementation; defaults to DefaultTokenSupplier if JWT_SOURCE is configured + public static final String JWT_SUPPLIER = ".jwt.supplier"; private final String jwtEnvVar; private final String jwtFilePath; @@ -50,7 +52,7 @@ public class DefaultJwtProvider implements Supplier { private volatile String jwt; - public DefaultJwtProvider(String propertyPrefix, Configuration config) { + public DefaultTokenSupplier(String propertyPrefix, Configuration config) { String jwtSrc = config.get(propertyPrefix + JWT_SOURCE); if (jwtSrc == null) { 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 9e98b683841..d6e317287d8 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 @@ -24,7 +24,7 @@ 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.DefaultTokenSupplier; import org.apache.ranger.plugin.model.RangerPolicy; import org.apache.ranger.plugin.resourcematcher.RangerResourceMatcher; import org.apache.ranger.plugin.service.RangerAuthContext; @@ -200,18 +200,18 @@ void cleanResourceMatchers() { } private static Supplier getTokenSupplier(String propertyPrefix, RangerPluginConfig config) { - String providerProp = propertyPrefix + DefaultJwtProvider.JWT_PROVIDER; + String providerProp = propertyPrefix + DefaultTokenSupplier.JWT_SUPPLIER; String clzName = config.get(providerProp); Supplier ret = null; - if (StringUtils.isNotBlank(clzName) && !DefaultJwtProvider.class.getName().equals(clzName)) { + if (StringUtils.isNotBlank(clzName) && !DefaultTokenSupplier.class.getName().equals(clzName)) { ret = getCustomTokenSupplier(clzName, config, providerProp); } - /* DefaultJwtProvider is used only when configured explicitly or when a JWT source is set: otherwise no + /* DefaultTokenSupplier is used only when configured explicitly or when a JWT source is set: otherwise no * token supplier is created, so the plugin keeps the authentication it had before (basic-auth or none). */ - if (ret == null && (DefaultJwtProvider.class.getName().equals(clzName) || isJwtSourceConfigured(propertyPrefix, config))) { - ret = new DefaultJwtProvider(propertyPrefix, config); + if (ret == null && (DefaultTokenSupplier.class.getName().equals(clzName) || isJwtSourceConfigured(propertyPrefix, config))) { + ret = new DefaultTokenSupplier(propertyPrefix, config); } if (ret != null) { @@ -224,7 +224,7 @@ private static Supplier getTokenSupplier(String propertyPrefix, RangerPl } private static boolean isJwtSourceConfigured(String propertyPrefix, RangerPluginConfig config) { - return StringUtils.isNotBlank(config.get(propertyPrefix + DefaultJwtProvider.JWT_SOURCE)); + return StringUtils.isNotBlank(config.get(propertyPrefix + DefaultTokenSupplier.JWT_SOURCE)); } @SuppressWarnings("unchecked") 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 8dfcf5e47dc..30e2d8ab659 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 @@ -121,30 +121,30 @@ public String getMethod() { public static final String RANGER_DT_OPERATION_TYPE_RENEW = "RENEWDELEGATIONTOKEN"; public static final String RANGER_DT_OPERATION_TYPE_CANCEL = "CANCELDELEGATIONTOKEN"; - private final List configuredURLs; - private final String propertyPrefix; - private String mUrl; - private final String mSslConfigFileName; - private String mUsername; - private String mPassword; - private boolean mIsSSL; - private String mKeyStoreURL; - private String mKeyStoreAlias; - private String mKeyStoreFile; - private String mKeyStoreType; - private String mTrustStoreURL; - private String mTrustStoreAlias; - private String mTrustStoreFile; - private String mTrustStoreType; - private int mRestClientConnTimeOutMs; - private int mRestClientReadTimeOutMs; - private int maxRetryAttempts; - private int retryIntervalMs; - private int lastKnownActiveUrlIndex; - private volatile Client client; - private volatile Client cookieAuthClient; - private Supplier tokenSupplier; - private volatile String authHeader; + private final List configuredURLs; + private final String propertyPrefix; + private String mUrl; + private final String mSslConfigFileName; + private String mUsername; + private String mPassword; + private boolean mIsSSL; + private String mKeyStoreURL; + private String mKeyStoreAlias; + private String mKeyStoreFile; + private String mKeyStoreType; + private String mTrustStoreURL; + private String mTrustStoreAlias; + private String mTrustStoreFile; + private String mTrustStoreType; + private int mRestClientConnTimeOutMs; + private int mRestClientReadTimeOutMs; + private int maxRetryAttempts; + private int retryIntervalMs; + private int lastKnownActiveUrlIndex; + private volatile Client client; + private volatile Client cookieAuthClient; + private volatile Supplier tokenSupplier; + private volatile String authHeader; private volatile Map trustedAuthHeaders = Collections.emptyMap(); public RangerRESTClient(String url, String sslConfigFileName, Configuration config) { diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java similarity index 73% rename from agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java rename to agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java index 4f0498218f4..80903121510 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultJwtProvider.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java @@ -29,10 +29,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestDefaultJwtProvider { +public class TestDefaultTokenSupplier { @Test - public void defaultJwtProviderIsSupplierAndReadsTokenFromFile() throws Exception { - assertTrue(Supplier.class.isAssignableFrom(DefaultJwtProvider.class), "DefaultJwtProvider should implement Supplier"); + public void defaultTokenSupplierIsSupplierAndReadsTokenFromFile() throws Exception { + assertTrue(Supplier.class.isAssignableFrom(DefaultTokenSupplier.class), "DefaultTokenSupplier should implement Supplier"); File jwtFile = File.createTempFile("ranger-jwt", ".token"); @@ -42,10 +42,10 @@ public void defaultJwtProviderIsSupplierAndReadsTokenFromFile() throws Exception Configuration config = new Configuration(); - config.set("test.prefix" + DefaultJwtProvider.JWT_SOURCE, "file"); - config.set("test.prefix" + DefaultJwtProvider.JWT_FILE, jwtFile.getAbsolutePath()); + config.set("test.prefix" + DefaultTokenSupplier.JWT_SOURCE, "file"); + config.set("test.prefix" + DefaultTokenSupplier.JWT_FILE, jwtFile.getAbsolutePath()); - Supplier provider = new DefaultJwtProvider("test.prefix", config); + Supplier provider = new DefaultTokenSupplier("test.prefix", config); assertEquals("file-jwt-token", provider.get()); } diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java similarity index 71% rename from agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java rename to agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java index 1abfac55612..abf33b12a2c 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextJwtProvider.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java @@ -21,7 +21,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig; -import org.apache.ranger.plugin.authn.DefaultJwtProvider; +import org.apache.ranger.plugin.authn.DefaultTokenSupplier; import org.junit.jupiter.api.Test; import java.util.function.Supplier; @@ -29,7 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -public class TestRangerPluginContextJwtProvider { +public class TestRangerPluginContextTokenSupplier { @Test public void noTokenSupplierWhenNotConfigured() { RangerPluginConfig config = newConfig(); @@ -46,29 +46,29 @@ public void usesDefaultProviderWhenJwtSourceConfigured() { RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); } @Test public void usesDefaultProviderWhenExplicitlyConfigured() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), DefaultJwtProvider.class.getName()); + config.set(tokenSupplierProperty(config), DefaultTokenSupplier.class.getName()); RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); } @Test public void usesCustomProviderWithNoArgConstructor() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), NoArgJwtProvider.class.getName()); + config.set(tokenSupplierProperty(config), NoArgTokenSupplier.class.getName()); RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(NoArgJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(NoArgTokenSupplier.class, ctx.getTokenSupplier().getClass()); assertEquals("no-arg-token", ctx.getTokenSupplier().get()); } @@ -76,11 +76,11 @@ public void usesCustomProviderWithNoArgConstructor() { public void usesCustomProviderWithConfigConstructor() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), ConfigJwtProvider.class.getName()); + config.set(tokenSupplierProperty(config), ConfigTokenSupplier.class.getName()); RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(ConfigJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(ConfigTokenSupplier.class, ctx.getTokenSupplier().getClass()); assertEquals("config-token", ctx.getTokenSupplier().get()); } @@ -88,58 +88,58 @@ public void usesCustomProviderWithConfigConstructor() { public void fallsBackToDefaultForClassThatIsNotSupplier() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), NotASupplier.class.getName()); + config.set(tokenSupplierProperty(config), NotASupplier.class.getName()); config.set(jwtSourceProperty(config), "env"); RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); } @Test public void fallsBackToDefaultForUnknownClass() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), "org.apache.ranger.NoSuchJwtProvider"); + config.set(tokenSupplierProperty(config), "org.apache.ranger.NoSuchTokenSupplier"); config.set(jwtSourceProperty(config), "env"); RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); } @Test public void fallsBackToDefaultWhenProviderConstructorThrows() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), FailingJwtProvider.class.getName()); + config.set(tokenSupplierProperty(config), FailingTokenSupplier.class.getName()); config.set(jwtSourceProperty(config), "env"); RangerPluginContext ctx = new RangerPluginContext(config); - assertEquals(DefaultJwtProvider.class, ctx.getTokenSupplier().getClass()); + assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); } @Test public void noTokenSupplierWhenCustomProviderFailsAndNoJwtSource() { RangerPluginConfig config = newConfig(); - config.set(jwtProviderProperty(config), "org.apache.ranger.NoSuchJwtProvider"); + config.set(tokenSupplierProperty(config), "org.apache.ranger.NoSuchTokenSupplier"); RangerPluginContext ctx = new RangerPluginContext(config); assertNull(ctx.getTokenSupplier()); } - public static class NoArgJwtProvider implements Supplier { + public static class NoArgTokenSupplier implements Supplier { @Override public String get() { return "no-arg-token"; } } - public static class ConfigJwtProvider implements Supplier { - public ConfigJwtProvider(Configuration config) { + public static class ConfigTokenSupplier implements Supplier { + public ConfigTokenSupplier(Configuration config) { /* custom providers may accept the Ranger Configuration */ } @@ -155,8 +155,8 @@ public String get() { } } - public static class FailingJwtProvider implements Supplier { - public FailingJwtProvider() { + public static class FailingTokenSupplier implements Supplier { + public FailingTokenSupplier() { throw new IllegalStateException("token source unavailable"); } @@ -170,11 +170,11 @@ private RangerPluginConfig newConfig() { return new RangerPluginConfig("hive", "test-service", "test-app", "cl1", "on-perm", new RangerPolicyEngineOptions()); } - private String jwtProviderProperty(RangerPluginConfig config) { - return config.getPropertyPrefix() + ".policy.rest.client" + DefaultJwtProvider.JWT_PROVIDER; + private String tokenSupplierProperty(RangerPluginConfig config) { + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultTokenSupplier.JWT_SUPPLIER; } private String jwtSourceProperty(RangerPluginConfig config) { - return config.getPropertyPrefix() + ".policy.rest.client" + DefaultJwtProvider.JWT_SOURCE; + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultTokenSupplier.JWT_SOURCE; } } From 39ffc736e9ef072d7e8b594dd2c056b4e9ff2a9f Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 14 Sep 2026 10:12:58 -0700 Subject: [PATCH 4/4] RANGER-5795: Simplify token supplier selection, throw IllegalArgumentException on misconfigured supplier --- .../policyengine/RangerPluginContext.java | 67 ++++++++++--------- .../TestRangerPluginContextTokenSupplier.java | 30 ++------- 2 files changed, 41 insertions(+), 56 deletions(-) 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 d6e317287d8..98239221161 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 @@ -200,24 +200,24 @@ void cleanResourceMatchers() { } private static Supplier getTokenSupplier(String propertyPrefix, RangerPluginConfig config) { - String providerProp = propertyPrefix + DefaultTokenSupplier.JWT_SUPPLIER; - String clzName = config.get(providerProp); - Supplier ret = null; + String providerProp = propertyPrefix + DefaultTokenSupplier.JWT_SUPPLIER; + String clzName = config.get(providerProp); + final Supplier ret; - if (StringUtils.isNotBlank(clzName) && !DefaultTokenSupplier.class.getName().equals(clzName)) { - ret = getCustomTokenSupplier(clzName, config, providerProp); - } + if (StringUtils.isNotBlank(clzName)) { + LOG.info("Using Token supplier [{}], config: [{}]", clzName, providerProp); - /* DefaultTokenSupplier is used only when configured explicitly or when a JWT source is set: otherwise no - * token supplier is created, so the plugin keeps the authentication it had before (basic-auth or none). */ - if (ret == null && (DefaultTokenSupplier.class.getName().equals(clzName) || isJwtSourceConfigured(propertyPrefix, config))) { + if (DefaultTokenSupplier.class.getName().equals(clzName)) { + ret = new DefaultTokenSupplier(propertyPrefix, config); + } else { + ret = getCustomTokenSupplier(clzName, config, providerProp); + } + } else if (isJwtSourceConfigured(propertyPrefix, config)) { ret = new DefaultTokenSupplier(propertyPrefix, config); - } - - if (ret != null) { - LOG.info("Using Token supplier [{}], config: [{}]", ret.getClass().getName(), providerProp); } else { LOG.debug("No token supplier configured, config: [{}]", providerProp); + + ret = null; } return ret; @@ -229,30 +229,31 @@ private static boolean isJwtSourceConfigured(String propertyPrefix, RangerPlugin @SuppressWarnings("unchecked") private static Supplier getCustomTokenSupplier(String clzName, RangerPluginConfig config, String providerProp) { - Supplier ret = null; + final Class clz; - /* a misconfigured supplier must not prevent plugin initialization: this returns null and the caller decides - * the fallback. Throwable covers LinkageError/NoClassDefFoundError from a class on an incomplete classpath. */ + /* a misconfigured supplier is rejected: Throwable covers LinkageError/NoClassDefFoundError from an incomplete classpath */ try { - Class clz = Class.forName(clzName); - - if (Supplier.class.isAssignableFrom(clz)) { - try { - /* prefer a constructor that accepts the Ranger Configuration */ - Constructor ctor = clz.getDeclaredConstructor(Configuration.class); - - ret = (Supplier) ctor.newInstance(config); - } catch (NoSuchMethodException excp) { - /* fall back to the no-argument constructor */ - ret = (Supplier) clz.getDeclaredConstructor().newInstance(); - } - } else { - LOG.error("{}={}: class does not implement {}. Requests may be sent without a bearer token", providerProp, clzName, Supplier.class.getName()); - } + clz = Class.forName(clzName); } catch (Throwable excp) { - LOG.error("{}={}: failed to instantiate token supplier. Requests may be sent without a bearer token", providerProp, clzName, excp); + throw new IllegalArgumentException(providerProp + "=" + clzName + ": failed to load token supplier class", excp); } - return ret; + if (!Supplier.class.isAssignableFrom(clz)) { + throw new IllegalArgumentException(providerProp + "=" + clzName + ": class does not implement " + Supplier.class.getName()); + } + + try { + try { + /* prefer a constructor that accepts the Ranger Configuration */ + Constructor ctor = clz.getDeclaredConstructor(Configuration.class); + + return (Supplier) ctor.newInstance(config); + } catch (NoSuchMethodException excp) { + /* fall back to the no-argument constructor */ + return (Supplier) clz.getDeclaredConstructor().newInstance(); + } + } catch (Throwable excp) { + throw new IllegalArgumentException(providerProp + "=" + clzName + ": failed to instantiate token supplier", excp); + } } } diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java index abf33b12a2c..0ebe4afbd34 100644 --- a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java @@ -28,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; public class TestRangerPluginContextTokenSupplier { @Test @@ -85,50 +86,33 @@ public void usesCustomProviderWithConfigConstructor() { } @Test - public void fallsBackToDefaultForClassThatIsNotSupplier() { + public void throwsForClassThatIsNotSupplier() { RangerPluginConfig config = newConfig(); config.set(tokenSupplierProperty(config), NotASupplier.class.getName()); config.set(jwtSourceProperty(config), "env"); - RangerPluginContext ctx = new RangerPluginContext(config); - - assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); } @Test - public void fallsBackToDefaultForUnknownClass() { + public void throwsForUnknownClass() { RangerPluginConfig config = newConfig(); config.set(tokenSupplierProperty(config), "org.apache.ranger.NoSuchTokenSupplier"); config.set(jwtSourceProperty(config), "env"); - RangerPluginContext ctx = new RangerPluginContext(config); - - assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); } @Test - public void fallsBackToDefaultWhenProviderConstructorThrows() { + public void throwsWhenProviderConstructorThrows() { RangerPluginConfig config = newConfig(); config.set(tokenSupplierProperty(config), FailingTokenSupplier.class.getName()); config.set(jwtSourceProperty(config), "env"); - RangerPluginContext ctx = new RangerPluginContext(config); - - assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); - } - - @Test - public void noTokenSupplierWhenCustomProviderFailsAndNoJwtSource() { - RangerPluginConfig config = newConfig(); - - config.set(tokenSupplierProperty(config), "org.apache.ranger.NoSuchTokenSupplier"); - - RangerPluginContext ctx = new RangerPluginContext(config); - - assertNull(ctx.getTokenSupplier()); + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); } public static class NoArgTokenSupplier implements Supplier {