Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions agents-audit/dest-solr/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
<artifactId>ranger-audit-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-api</artifactId>
<version>${solr.version}</version>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

package org.apache.ranger.plugin.util;

import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.slf4j.Logger;
Expand All @@ -30,6 +29,9 @@
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

/**
* Comprehensive Jersey client utility for Ranger components.
*
Expand Down Expand Up @@ -67,6 +69,12 @@ public class RangerJersey2ClientBuilder {
// Configuration constants
private static final String DISABLE_AUTO_DISCOVERY_PROPERTY = "jersey.config.disableAutoDiscovery";
private static final String PROVIDER_SCANNING_RECURSIVE_PROPERTY = "jersey.config.server.provider.scanning.recursive";
private static final String JERSEY_JACKSON_FEATURE_CLASS = "org.glassfish.jersey.jackson.JacksonFeature";
private static final String[] JACKSON_JSON_PROVIDER_CLASSES = {
"org.glassfish.jersey.jackson.internal.DefaultJacksonJaxbJsonProvider",
"com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider",
};
private static final String RANGER_PLUGIN_CLASSLOADER_CLASS = "org.apache.ranger.plugin.classloader.RangerPluginClassLoader";
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 5000;
private static final int DEFAULT_READ_TIMEOUT_MS = 30000;

Expand All @@ -75,6 +83,18 @@ private RangerJersey2ClientBuilder() {
// Utility class - no instances
}

public static ClientConfig newClientConfig() {
ClassLoader jerseyCl = resolveJerseyClassLoader();

try {
Class<?> configClass = Class.forName("org.glassfish.jersey.client.ClientConfig", true, jerseyCl);

return ClientConfig.class.cast(configClass.getDeclaredConstructor().newInstance());
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to create ClientConfig using classloader " + jerseyCl, e);
}
}

/**
* Creates a standard Jersey client with MOXy prevention and default timeouts.
*
Expand All @@ -92,23 +112,21 @@ public static Client createStandardClient() {
* @return A configured Jersey client safe from MOXy interference
*/
public static Client createClient(int connectTimeoutMs, int readTimeoutMs) {
LOG.debug("Creating standard Jersey client with timeouts: connect={}ms, read={}ms", connectTimeoutMs, readTimeoutMs);

ClientConfig config = new ClientConfig();
applyAntiMoxyConfiguration(config);
return runWithJerseyClassLoader(() -> {
LOG.debug("Creating standard Jersey client with timeouts: connect={}ms, read={}ms", connectTimeoutMs, readTimeoutMs);

// Set timeouts
config.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs);
config.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);
ClientConfig config = newClientConfig();
applyAntiMoxyConfiguration(config);

// Create client with configuration
Client client = ClientBuilder.newClient(config);
config.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs);
config.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);

// Validate configuration
validateAntiMoxyConfiguration(config);
Client client = ClientBuilder.newClient(config);
validateAntiMoxyConfiguration(config);

LOG.debug("Successfully created standard Jersey client");
return client;
LOG.debug("Successfully created standard Jersey client");
return client;
});
}

/**
Expand All @@ -121,37 +139,33 @@ public static Client createClient(int connectTimeoutMs, int readTimeoutMs) {
* @return A configured secure Jersey client safe from MOXy interference
*/
public static Client createSecureClient(SSLContext sslContext, HostnameVerifier hostnameVerifier, int connectTimeoutMs, int readTimeoutMs) {
LOG.debug("Creating secure Jersey client with SSL - connect={}ms, read={}ms", connectTimeoutMs, readTimeoutMs);
return runWithJerseyClassLoader(() -> {
LOG.debug("Creating secure Jersey client with SSL - connect={}ms, read={}ms", connectTimeoutMs, readTimeoutMs);

ClientConfig config = new ClientConfig();
applyAntiMoxyConfiguration(config);
ClientConfig config = newClientConfig();
applyAntiMoxyConfiguration(config);

// Set timeouts
config.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs);
config.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);
config.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs);
config.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);

// Create builder with configuration
ClientBuilder builder = ClientBuilder.newBuilder().withConfig(config);
ClientBuilder builder = ClientBuilder.newBuilder().withConfig(config);

// Configure SSL if provided
if (sslContext != null) {
builder.sslContext(sslContext);
LOG.debug("Applied custom SSL context");
}

if (hostnameVerifier != null) {
builder.hostnameVerifier(hostnameVerifier);
LOG.debug("Applied custom hostname verifier");
}
if (sslContext != null) {
builder.sslContext(sslContext);
LOG.debug("Applied custom SSL context");
}

// Build client
Client client = builder.build();
if (hostnameVerifier != null) {
builder.hostnameVerifier(hostnameVerifier);
LOG.debug("Applied custom hostname verifier");
}

// Validate configuration
validateAntiMoxyConfiguration(config);
Client client = builder.build();
validateAntiMoxyConfiguration(config);

LOG.debug("Successfully created secure Jersey client");
return client;
LOG.debug("Successfully created secure Jersey client");
return client;
});
}

// ========== DROP-IN REPLACEMENTS for unsafe ClientBuilder patterns ==========
Expand Down Expand Up @@ -257,37 +271,80 @@ public SafeClientBuilder withConfig(ClientConfig config) {
* @return A configured Jersey client safe from MOXy interference
*/
public Client build() {
if (clientConfig != null) {
// Apply anti-MOXy configuration to the provided config
applyAntiMoxyConfiguration(clientConfig);
return runWithJerseyClassLoader(() -> {
if (clientConfig != null) {
applyAntiMoxyConfiguration(clientConfig);

// Set timeouts if not already configured
if (!clientConfig.getProperties().containsKey(ClientProperties.CONNECT_TIMEOUT)) {
clientConfig.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs);
}
if (!clientConfig.getProperties().containsKey(ClientProperties.READ_TIMEOUT)) {
clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);
}
if (!clientConfig.getProperties().containsKey(ClientProperties.CONNECT_TIMEOUT)) {
clientConfig.property(ClientProperties.CONNECT_TIMEOUT, connectTimeoutMs);
}
if (!clientConfig.getProperties().containsKey(ClientProperties.READ_TIMEOUT)) {
clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);
}

// Create ClientBuilder with MOXy-safe configuration
ClientBuilder builder = ClientBuilder.newBuilder().withConfig(clientConfig);
ClientBuilder builder = ClientBuilder.newBuilder().withConfig(clientConfig);

if (sslContext != null) {
builder.sslContext(sslContext);
}
if (hostnameVerifier != null) {
builder.hostnameVerifier(hostnameVerifier);
}
if (sslContext != null) {
builder.sslContext(sslContext);
}
if (hostnameVerifier != null) {
builder.hostnameVerifier(hostnameVerifier);
}

// Validate configuration before building
validateAntiMoxyConfiguration(clientConfig);
validateAntiMoxyConfiguration(clientConfig);

return builder.build();
}

return builder.build();
} else {
// Use the standard secure client creation method
return createSecureClient(sslContext, hostnameVerifier, connectTimeoutMs, readTimeoutMs);
});
}
}

/**
* Runs Jersey client construction with the host (e.g. Solr webapp) classloader when the
* Ranger plugin classloader is active, so Jersey types are not split across loaders.
*/
public static <T> T runWithJerseyClassLoader(Callable<T> action) {
ClassLoader jerseyCl = resolveJerseyClassLoader();
ClassLoader previous = Thread.currentThread().getContextClassLoader();

try {
Thread.currentThread().setContextClassLoader(jerseyCl);
return action.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Failed to build Jersey client using classloader " + jerseyCl, e);
} finally {
Thread.currentThread().setContextClassLoader(previous);
}
}

static ClassLoader resolveJerseyClassLoader() {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();

if (tccl != null && RANGER_PLUGIN_CLASSLOADER_CLASS.equals(tccl.getClass().getName())) {
try {
Method getPrev = tccl.getClass().getMethod("getPrevActiveClassLoader");
ClassLoader hostCl = (ClassLoader) getPrev.invoke(tccl);

if (hostCl != null) {
LOG.debug("Using host classloader from RangerPluginClassLoader: {}", hostCl);
return hostCl;
}
} catch (ReflectiveOperationException e) {
LOG.debug("Could not resolve host classloader from RangerPluginClassLoader", e);
}
}

ClassLoader fromJerseyApi = ClientConfig.class.getClassLoader();

if (fromJerseyApi != null) {
return fromJerseyApi;
}

return tccl != null ? tccl : RangerJersey2ClientBuilder.class.getClassLoader();
}

// ========== VALIDATION AND CONFIGURATION METHODS ==========
Expand Down Expand Up @@ -317,11 +374,9 @@ public static ClientConfig applyAntiMoxyConfiguration(ClientConfig config) {

LOG.debug("Applying anti-MOXy configuration to ClientConfig");

// 1. Explicitly register Jackson JSON provider with high priority
// Note: JacksonJaxbJsonProvider should be registered with @Priority(1) annotation
config.register(JacksonJaxbJsonProvider.class);
registerJacksonProvider(config);

// 2. Disable Jersey's auto-discovery to prevent MOXy from being found and registered
// Disable Jersey's auto-discovery to prevent MOXy from being found and registered
config.property(DISABLE_AUTO_DISCOVERY_PROPERTY, true);
config.property(PROVIDER_SCANNING_RECURSIVE_PROPERTY, false);

Expand Down Expand Up @@ -351,10 +406,7 @@ public static boolean validateAntiMoxyConfiguration(ClientConfig config) {
isValid = false;
}

// Check if Jackson is registered
boolean jacksonRegistered = config.getClasses().contains(JacksonJaxbJsonProvider.class) || config.getInstances().stream().anyMatch(instance -> instance instanceof JacksonJaxbJsonProvider);

if (!jacksonRegistered) {
if (!isJacksonProviderRegistered(config)) {
LOG.error("CRITICAL: Jackson JSON provider is not registered! Default JSON processing may fail.");
isValid = false;
}
Expand All @@ -367,4 +419,61 @@ public static boolean validateAntiMoxyConfiguration(ClientConfig config) {

return isValid;
}

private static void registerJacksonProvider(ClientConfig config) {
ClassLoader jerseyCl = config.getClass().getClassLoader();
Exception lastFailure = null;

try {
Class<?> featureClass = Class.forName(JERSEY_JACKSON_FEATURE_CLASS, true, jerseyCl);

config.register(featureClass);
LOG.debug("Registered {} from classloader {}", JERSEY_JACKSON_FEATURE_CLASS, jerseyCl);
return;
} catch (ReflectiveOperationException e) {
lastFailure = e;
LOG.debug("Jersey Jackson feature not available from {}: {}", jerseyCl, e.toString());
}

for (String providerClassName : JACKSON_JSON_PROVIDER_CLASSES) {
try {
Class<?> providerClass = Class.forName(providerClassName, true, jerseyCl);
Object provider = providerClass.getDeclaredConstructor().newInstance();

config.register(provider);
LOG.debug("Registered {} from classloader {}", providerClassName, jerseyCl);
return;
} catch (ReflectiveOperationException e) {
lastFailure = e;
LOG.debug("Jackson provider {} not available from {}: {}", providerClassName, jerseyCl, e.toString());
}
}

throw new IllegalStateException("Failed to register a Jackson JSON provider from classloader " + jerseyCl, lastFailure);
}

private static boolean isJacksonProviderRegistered(ClientConfig config) {
if (config.getClasses().stream().anyMatch(clazz -> JERSEY_JACKSON_FEATURE_CLASS.equals(clazz.getName()))) {
return true;
}

for (String providerClassName : JACKSON_JSON_PROVIDER_CLASSES) {
boolean registeredByClass = config.getClasses().stream()
.anyMatch(clazz -> providerClassName.equals(clazz.getName()));

if (registeredByClass) {
return true;
}

boolean registeredByInstance = config.getInstances().stream()
.anyMatch(instance -> providerClassName.equals(instance.getClass().getName()));

if (registeredByInstance) {
return true;
}
}

return config.getClasses().stream().anyMatch(clazz -> clazz.getName().contains("jersey.jackson"))
|| config.getInstances().stream().anyMatch(instance -> instance.getClass().getName().contains("jersey.jackson"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,12 @@ private Client getCookieAuthClient() {
}

private Client buildClient() {
return RangerJersey2ClientBuilder.runWithJerseyClassLoader(this::buildClientInternal);
}

private Client buildClientInternal() {
RangerJersey2ClientBuilder.SafeClientBuilder clientBuilder;
ClientConfig config = new ClientConfig();
ClientConfig config = RangerJersey2ClientBuilder.newClientConfig();

if (mIsSSL) {
try {
Expand Down
Loading
Loading