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 421ac0710b..07a546650f 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.setJwtProvider(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/admin/client/RangerAdminRESTClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java index 1a35eab1da..dfd295bbce 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/DefaultTokenSupplier.java similarity index 91% 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 3c5b1a698e..25ee5d956d 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 @@ -29,9 +29,10 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.function.Supplier; -public class DefaultJwtProvider implements JwtProvider { - 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"; @@ -39,6 +40,9 @@ public class DefaultJwtProvider implements JwtProvider { public static final String JWT_CRED_FILE = ".jwt.cred.file"; public static final String JWT_CRED_ALIAS = ".jwt.cred.alias"; + // 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; private final String jwtCredFilePath; @@ -48,7 +52,7 @@ public class DefaultJwtProvider implements JwtProvider { 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) { @@ -87,7 +91,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 1c9f0bd109..0000000000 --- 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 db6668cda4..9823922116 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.authn.DefaultTokenSupplier; 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,14 +44,14 @@ 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() { @@ -155,8 +157,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 +172,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() { @@ -196,4 +198,62 @@ void cleanResourceMatchers() { LOG.debug("<== cleanResourceMatchers()"); } + + private static Supplier getTokenSupplier(String propertyPrefix, RangerPluginConfig config) { + String providerProp = propertyPrefix + DefaultTokenSupplier.JWT_SUPPLIER; + String clzName = config.get(providerProp); + final Supplier ret; + + if (StringUtils.isNotBlank(clzName)) { + LOG.info("Using Token supplier [{}], config: [{}]", clzName, providerProp); + + 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); + } else { + LOG.debug("No token supplier configured, config: [{}]", providerProp); + + ret = null; + } + + return ret; + } + + private static boolean isJwtSourceConfigured(String propertyPrefix, RangerPluginConfig config) { + return StringUtils.isNotBlank(config.get(propertyPrefix + DefaultTokenSupplier.JWT_SOURCE)); + } + + @SuppressWarnings("unchecked") + private static Supplier getCustomTokenSupplier(String clzName, RangerPluginConfig config, String providerProp) { + final Class clz; + + /* a misconfigured supplier is rejected: Throwable covers LinkageError/NoClassDefFoundError from an incomplete classpath */ + try { + clz = Class.forName(clzName); + } catch (Throwable excp) { + throw new IllegalArgumentException(providerProp + "=" + clzName + ": failed to load token supplier class", excp); + } + + 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/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java index 3a3411d30d..33bf339499 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 94046a549e..30e2d8ab65 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); @@ -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 JwtProvider jwtProvider; - 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) { @@ -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/TestDefaultTokenSupplier.java b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.java new file mode 100644 index 0000000000..8090312151 --- /dev/null +++ b/agents-common/src/test/java/org/apache/ranger/plugin/authn/TestDefaultTokenSupplier.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 TestDefaultTokenSupplier { + @Test + public void defaultTokenSupplierIsSupplierAndReadsTokenFromFile() throws Exception { + assertTrue(Supplier.class.isAssignableFrom(DefaultTokenSupplier.class), "DefaultTokenSupplier 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" + DefaultTokenSupplier.JWT_SOURCE, "file"); + config.set("test.prefix" + DefaultTokenSupplier.JWT_FILE, jwtFile.getAbsolutePath()); + + 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/TestRangerPluginContextTokenSupplier.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java new file mode 100644 index 0000000000..0ebe4afbd3 --- /dev/null +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerPluginContextTokenSupplier.java @@ -0,0 +1,164 @@ +/* + * 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.DefaultTokenSupplier; +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.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestRangerPluginContextTokenSupplier { + @Test + 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(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); + } + + @Test + public void usesDefaultProviderWhenExplicitlyConfigured() { + RangerPluginConfig config = newConfig(); + + config.set(tokenSupplierProperty(config), DefaultTokenSupplier.class.getName()); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(DefaultTokenSupplier.class, ctx.getTokenSupplier().getClass()); + } + + @Test + public void usesCustomProviderWithNoArgConstructor() { + RangerPluginConfig config = newConfig(); + + config.set(tokenSupplierProperty(config), NoArgTokenSupplier.class.getName()); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(NoArgTokenSupplier.class, ctx.getTokenSupplier().getClass()); + assertEquals("no-arg-token", ctx.getTokenSupplier().get()); + } + + @Test + public void usesCustomProviderWithConfigConstructor() { + RangerPluginConfig config = newConfig(); + + config.set(tokenSupplierProperty(config), ConfigTokenSupplier.class.getName()); + + RangerPluginContext ctx = new RangerPluginContext(config); + + assertEquals(ConfigTokenSupplier.class, ctx.getTokenSupplier().getClass()); + assertEquals("config-token", ctx.getTokenSupplier().get()); + } + + @Test + public void throwsForClassThatIsNotSupplier() { + RangerPluginConfig config = newConfig(); + + config.set(tokenSupplierProperty(config), NotASupplier.class.getName()); + config.set(jwtSourceProperty(config), "env"); + + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + } + + @Test + public void throwsForUnknownClass() { + RangerPluginConfig config = newConfig(); + + config.set(tokenSupplierProperty(config), "org.apache.ranger.NoSuchTokenSupplier"); + config.set(jwtSourceProperty(config), "env"); + + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + } + + @Test + public void throwsWhenProviderConstructorThrows() { + RangerPluginConfig config = newConfig(); + + config.set(tokenSupplierProperty(config), FailingTokenSupplier.class.getName()); + config.set(jwtSourceProperty(config), "env"); + + assertThrows(IllegalArgumentException.class, () -> new RangerPluginContext(config)); + } + + public static class NoArgTokenSupplier implements Supplier { + @Override + public String get() { + return "no-arg-token"; + } + } + + public static class ConfigTokenSupplier implements Supplier { + public ConfigTokenSupplier(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"; + } + } + + public static class FailingTokenSupplier implements Supplier { + public FailingTokenSupplier() { + 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 tokenSupplierProperty(RangerPluginConfig config) { + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultTokenSupplier.JWT_SUPPLIER; + } + + private String jwtSourceProperty(RangerPluginConfig config) { + return config.getPropertyPrefix() + ".policy.rest.client" + DefaultTokenSupplier.JWT_SOURCE; + } +} 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 2f5f6ca221..a8395ec78f 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<>();