diff --git a/.gitignore b/.gitignore index c2959e5e3..41854a317 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ pom.xml.releaseBackup pom.xml.versionsBackup release.properties dependency-reduced-pom.xml +.claude/ +.claude/ diff --git a/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxConfigurationConstants.java b/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxConfigurationConstants.java index 51c9acfc6..59f60535f 100644 --- a/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxConfigurationConstants.java +++ b/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxConfigurationConstants.java @@ -40,5 +40,12 @@ public class JmxConfigurationConstants { /** Property to activate remote SSL support (same as com.sun.management.jmxremote.ssl) */ public static final String PROP_REMOTE_SSL = "remote.ssl"; - + + /** Property to configure JEP 290 serial filter for remote JMX RMI. */ + public static final String PROP_REMOTE_SERIAL_FILTER_PATTERN = "remote.serial.filter.pattern"; + + /** Standard JMX connector environment key for the RMI deserialization filter pattern. */ + public static final String JMX_REMOTE_SERIAL_FILTER_PATTERN = + "jmx.remote.rmi.server.serial.filter.pattern"; + } diff --git a/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformation.java b/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformation.java index 5ea7f8f18..ce60b62f7 100644 --- a/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformation.java +++ b/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformation.java @@ -45,6 +45,9 @@ public class JmxInformation { /** Use remote SSL? */ private boolean remoteSSL; + + /** JEP 290 filter pattern used by remote JMX RMI deserialization. */ + private String remoteSerialFilterPattern; /** * The jmxUrl to connect to. @@ -114,6 +117,14 @@ public boolean isRemoteSSL() { public void setRemoteSSL(boolean remoteSSL) { this.remoteSSL = remoteSSL; } + + public String getRemoteSerialFilterPattern() { + return remoteSerialFilterPattern; + } + + public void setRemoteSerialFilterPattern(String remoteSerialFilterPattern) { + this.remoteSerialFilterPattern = remoteSerialFilterPattern; + } /** * Builds the JMX URL depending on the existence of RMI port. diff --git a/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformationFactory.java b/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformationFactory.java index fc45516e2..a33c5357c 100644 --- a/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformationFactory.java +++ b/modules/commons/src/main/java/org/apache/synapse/commons/jmx/JmxInformationFactory.java @@ -117,6 +117,12 @@ public static JmxInformation createJmxInformation(Properties properties, String } jmxInformation.setRemoteSSL(remoteSSL); + value = MiscellaneousUtil.getProperty(properties, + prefix + JmxConfigurationConstants.PROP_REMOTE_SERIAL_FILTER_PATTERN, null); + if (value != null && value.trim().length() > 0) { + jmxInformation.setRemoteSerialFilterPattern(value); + } + return jmxInformation; } diff --git a/modules/commons/src/test/java/org/apache/synapse/commons/jmx/JmxInformationFactoryTest.java b/modules/commons/src/test/java/org/apache/synapse/commons/jmx/JmxInformationFactoryTest.java new file mode 100644 index 000000000..3ec8579c0 --- /dev/null +++ b/modules/commons/src/test/java/org/apache/synapse/commons/jmx/JmxInformationFactoryTest.java @@ -0,0 +1,63 @@ +/* + * 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.synapse.commons.jmx; + +import junit.framework.TestCase; + +import java.util.Properties; + +public class JmxInformationFactoryTest extends TestCase { + + public void testSerialFilterPatternNotAppliedWhenUnset() { + Properties properties = new Properties(); + + JmxInformation info = JmxInformationFactory.createJmxInformation(properties, "localhost"); + + assertEquals(-1, info.getJndiPort()); + assertNull(info.getRemoteSerialFilterPattern()); + } + + public void testConfiguredJndiPortIsKept() { + Properties properties = new Properties(); + properties.setProperty("synapse.jmx.jndiPort", "0"); + + JmxInformation info = JmxInformationFactory.createJmxInformation(properties, "localhost"); + + assertEquals(0, info.getJndiPort()); + } + + public void testSynapseSerialFilterOverrideUsed() { + String customPattern = "maxdepth=10;java.lang.*;!*"; + Properties properties = new Properties(); + properties.setProperty("synapse.jmx.remote.serial.filter.pattern", customPattern); + + JmxInformation info = JmxInformationFactory.createJmxInformation(properties, "localhost"); + + assertEquals(customPattern, info.getRemoteSerialFilterPattern()); + } + + public void testBlankSerialFilterPatternIsIgnored() { + Properties properties = new Properties(); + properties.setProperty("synapse.jmx.remote.serial.filter.pattern", " "); + + JmxInformation info = JmxInformationFactory.createJmxInformation(properties, "localhost"); + + assertNull(info.getRemoteSerialFilterPattern()); + } +} diff --git a/modules/core/src/main/java/org/apache/synapse/JmxAdapter.java b/modules/core/src/main/java/org/apache/synapse/JmxAdapter.java index d54ee19c7..4ae48c304 100644 --- a/modules/core/src/main/java/org/apache/synapse/JmxAdapter.java +++ b/modules/core/src/main/java/org/apache/synapse/JmxAdapter.java @@ -25,6 +25,7 @@ import org.apache.synapse.commons.util.RMIRegistryController; import org.apache.synapse.commons.jmx.JmxInformation; import org.apache.synapse.commons.jmx.JmxSecretAuthenticator; +import org.apache.synapse.jmx.JmxSerializationFilterSupport; import javax.management.MBeanServer; import javax.management.remote.JMXConnectorServer; @@ -204,6 +205,17 @@ public void setJmxInformation(JmxInformation jmxInformation) { private Map createContextMap() { Map env = new HashMap(); + String remoteSerialFilterPattern = jmxInformation.getRemoteSerialFilterPattern(); + if (remoteSerialFilterPattern != null && remoteSerialFilterPattern.trim().length() > 0) { + // Deserialization limits are applied via JmxSerializationFilterSupport (JEP 415 factory), + // not via jmx.remote.rmi.server.serial.filter.pattern in the connector env. + JmxSerializationFilterSupport.configureForJmxPattern(remoteSerialFilterPattern, log); + if (log.isDebugEnabled()) { + log.debug("Configured JEP 290 deserialization filter for remote JMX: " + + remoteSerialFilterPattern); + } + } + if (jmxInformation.isAuthenticate()) { if (jmxInformation.getRemotePasswordFile() != null) { diff --git a/modules/core/src/main/java/org/apache/synapse/jmx/JmxSerializationFilterSupport.java b/modules/core/src/main/java/org/apache/synapse/jmx/JmxSerializationFilterSupport.java new file mode 100644 index 000000000..82b360f28 --- /dev/null +++ b/modules/core/src/main/java/org/apache/synapse/jmx/JmxSerializationFilterSupport.java @@ -0,0 +1,91 @@ +/* + * 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.synapse.jmx; + +import org.apache.commons.logging.Log; + +import java.io.ObjectInputFilter; +import java.util.function.BinaryOperator; + +/** + * Installs JEP 415 context-specific deserialization control for remote JMX without using + * {@code ObjectInputFilter.Config.setSerialFilter} for the entire JVM. + *

+ * Synapse targets Java 17+; the configured JMX pattern is merged with the stream filter only when + * the stack indicates deserialization for {@code javax.management.remote} / {@code com.sun.jmx.remote}. + */ +public final class JmxSerializationFilterSupport { + + private JmxSerializationFilterSupport() { + } + + /** + * Installs a {@link ObjectInputFilter.Config#setSerialFilterFactory serial filter factory} + * that merges the given pattern for JMX/RMI deserialization stacks. + * + * @param pattern non-empty filter pattern (same string as {@code synapse.jmx.remote.serial.filter.pattern}) + * @param log logger (typically {@link org.apache.synapse.JmxAdapter}) + */ + public static void configureForJmxPattern(String pattern, Log log) { + if (pattern == null || pattern.trim().isEmpty()) { + return; + } + try { + ObjectInputFilter jmxFilter = ObjectInputFilter.Config.createFilter(pattern); + BinaryOperator factory = (curr, next) -> { + ObjectInputFilter base = next != null ? next : curr; + if (base == null) { + base = ObjectInputFilter.Config.getSerialFilter(); + } + if (!isJmxRemoteDeserializationStack()) { + return base; + } + return ObjectInputFilter.merge(jmxFilter, base); + }; + ObjectInputFilter.Config.setSerialFilterFactory(factory); + log.info("Installed JEP 415 serial filter factory for JMX deserialization"); + if (log.isDebugEnabled()) { + log.debug("JEP 415 serial filter factory installed for JMX/RMI stacks"); + } + } catch (IllegalStateException e) { + log.warn("Serial filter factory already set; leaving existing factory in place: " + + e.getMessage()); + } catch (RuntimeException e) { + if (log.isDebugEnabled()) { + log.debug("JEP 415 serial filter factory failed: " + e.getMessage(), e); + } + log.warn("Could not install JEP 415 serial filter factory; check JDK configuration."); + } + } + + /** + * Detect JMX-over-RMI deserialization by stack frames. + */ + static boolean isJmxRemoteDeserializationStack() { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + for (int i = 0; i < trace.length && i < 40; i++) { + String name = trace[i].getClassName(); + if (name.startsWith("javax.management.remote.") + || name.startsWith("com.sun.jmx.remote")) { + return true; + } + } + return false; + } +} diff --git a/modules/core/src/test/java/org/apache/synapse/JmxAdapterContextMapTest.java b/modules/core/src/test/java/org/apache/synapse/JmxAdapterContextMapTest.java new file mode 100644 index 000000000..ae1b30487 --- /dev/null +++ b/modules/core/src/test/java/org/apache/synapse/JmxAdapterContextMapTest.java @@ -0,0 +1,76 @@ +/* + * 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.synapse; + +import junit.framework.TestCase; +import org.apache.synapse.commons.jmx.JmxConfigurationConstants; +import org.apache.synapse.commons.jmx.JmxInformation; +import org.apache.synapse.commons.jmx.JmxSecretAuthenticator; +import org.apache.synapse.securevault.secret.SecretInformation; + +import javax.management.remote.JMXConnectorServer; +import java.lang.reflect.Method; +import java.util.Map; + +public class JmxAdapterContextMapTest extends TestCase { + + @SuppressWarnings("unchecked") + private Map createContextMap(JmxInformation info) throws Exception { + JmxAdapter adapter = new JmxAdapter(info); + Method method = JmxAdapter.class.getDeclaredMethod("createContextMap"); + method.setAccessible(true); + return (Map) method.invoke(adapter); + } + + /** + * Serial filter pattern is applied via {@code JmxSerializationFilterSupport}, not the standard + * JMX connector env key {@code jmx.remote.rmi.server.serial.filter.pattern}. + */ + public void testContextMapDoesNotPutSerialFilterPatternInEnv() throws Exception { + String filterPattern = "maxdepth=5;java.lang.String;!*"; + JmxInformation info = new JmxInformation(); + info.setRemoteSerialFilterPattern(filterPattern); + + Map env = createContextMap(info); + + assertNull(env.get(JmxConfigurationConstants.JMX_REMOTE_SERIAL_FILTER_PATTERN)); + } + + public void testContextMapIncludesAuthAndSSLWhenEnabled() throws Exception { + String filterPattern = "maxdepth=5;java.lang.String;!*"; + JmxInformation info = new JmxInformation(); + info.setRemoteSerialFilterPattern(filterPattern); + info.setAuthenticate(true); + info.setRemoteAccessFile("conf/jmxremote.access"); + info.setRemoteSSL(true); + + SecretInformation secretInformation = new SecretInformation(); + secretInformation.setUser("admin"); + secretInformation.setAliasSecret("admin"); + info.setSecretInformation(secretInformation); + + Map env = createContextMap(info); + + assertNull(env.get(JmxConfigurationConstants.JMX_REMOTE_SERIAL_FILTER_PATTERN)); + assertTrue(env.get(JMXConnectorServer.AUTHENTICATOR) instanceof JmxSecretAuthenticator); + assertEquals("conf/jmxremote.access", env.get("jmx.remote.x.access.file")); + assertNotNull(env.get("jmx.remote.rmi.client.socket.factory")); + assertNotNull(env.get("jmx.remote.rmi.server.socket.factory")); + } +} diff --git a/modules/distribution/src/main/bin/synapse.sh b/modules/distribution/src/main/bin/synapse.sh index dad4a792f..459ddd73f 100644 --- a/modules/distribution/src/main/bin/synapse.sh +++ b/modules/distribution/src/main/bin/synapse.sh @@ -122,6 +122,11 @@ if $cygwin; then fi # endorsed dir SYNAPSE_ENDORSED=$SYNAPSE_HOME/lib/endorsed +ENDORSED_PROP= +# -Djava.endorsed.dirs is removed from Java 9+. +if [ "$jdk_16" -o "$jdk_17" -o "$jdk_18" ]; then + ENDORSED_PROP="-Djava.endorsed.dirs=$SYNAPSE_ENDORSED" +fi # synapse config SYNAPSE_XML=$SYNAPSE_HOME/repository/conf/synapse-config @@ -181,7 +186,7 @@ $JAVA_HOME/bin/java -server -Xms512M -Xmx512M \ $XDEBUG \ $TEMP_PROPS \ -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XMLGrammarCachingConfiguration \ - -Djava.endorsed.dirs=$SYNAPSE_ENDORSED \ + $ENDORSED_PROP \ -Djava.io.tmpdir=$SYNAPSE_HOME/work/temp/synapse \ -classpath $SYNAPSE_CLASSPATH \ org.apache.synapse.SynapseServer \ diff --git a/pom.xml b/pom.xml index a40ed4fc1..7dfcf7f54 100644 --- a/pom.xml +++ b/pom.xml @@ -355,7 +355,7 @@ org.apache.felix maven-bundle-plugin - 2.3.6 + 5.1.9 org.apache.maven.plugins @@ -375,7 +375,7 @@ org.apache.maven.plugins maven-war-plugin - 3.3.2 + 3.4.0 org.codehaus.mojo diff --git a/repository/conf/synapse.properties b/repository/conf/synapse.properties index 576adc15f..e236fbd8c 100644 --- a/repository/conf/synapse.properties +++ b/repository/conf/synapse.properties @@ -115,8 +115,8 @@ ################################################################################ # JMX Configuration ################################################################################ -# Default is to autodetect free port starting at 1099; change it to meet your deployment requirements! -synapse.jmx.jndiPort=0 +# JMX is disabled by default; set to -1 to disable JMX; set this to 0 to autodetect free port starting at 1099; change it to a fixed port to meet your deployment requirements. +synapse.jmx.jndiPort=-1 # By default rmi port will be detected automatically, change it to a fixed port to meet your deployment requirements #synapse.jmx.rmiPort=1101 # By default the hostname will be detected, but you can force to use another network interface @@ -129,6 +129,11 @@ synapse.jmx.jndiPort=0 #synapse.jmx.password=admin # Optionally you may want to specify the location of an remote access file to restrict access #synapse.jmx.remote.access.file= +# Optional deserialization filter for remote JMX. +# Applies only when JMX is enabled (set synapse.jmx.jndiPort to a value other than -1). +# Configure a JEP 415/JEP 290-style deserialization filter pattern as needed; remove/comment out +# synapse.jmx.remote.serial.filter.pattern to disable this additional JMX deserialization filter. +synapse.jmx.remote.serial.filter.pattern=maxdepth=20;maxrefs=1000;maxbytes=262144 ################################################################################################# # Proxy Settings For URL Connections, these are used when synapse retrieves resources from URLs