Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import javax.management.remote.JMXConnectorServer
import javax.management.remote.JMXConnectorServerFactory
import javax.management.remote.JMXServiceURL
import javax.management.remote.rmi.RMIConnectorServer
import javax.net.ssl.SSLContext
import javax.rmi.ssl.SslRMIClientSocketFactory
import javax.rmi.ssl.SslRMIServerSocketFactory

Expand Down Expand Up @@ -53,6 +54,23 @@ class JmxServerConnectorFactory extends AbstractFactory {

private static final List SUPPORTED_PROTOCOLS = ["rmi", "jrmp", "jmxmp"]

// Restrict the SSL server socket factory to modern TLS versions rather than relying on
// the JVM default protocol set (which may still enable TLS 1.0/1.1 on older/misconfigured JREs).
// Intersect with the protocols the running JDK actually supports so we never request an
// unsupported protocol (which the factory would reject), e.g. TLS 1.3 on a JDK without it.
private static final String[] ENABLED_TLS_PROTOCOLS = pinnedTlsProtocols()

private static String[] pinnedTlsProtocols() {
Set<String> preferred = ['TLSv1.3', 'TLSv1.2']
try {
Set<String> supported = SSLContext.getDefault().supportedSSLParameters.protocols
Set<String> usable = preferred.intersect(supported)
return (usable ?: preferred) as String[]
} catch (Exception ignored) {
return preferred as String[]
}
}

/**
* Creates a server connector for the supplied connection settings.
*
Expand Down Expand Up @@ -140,14 +158,14 @@ class JmxServerConnectorFactory extends AbstractFactory {
env.put("com.sun.management.jmxremote.access.file", aFile)

// SSL connection
def ssl = props.remove("com.sun.management.jmxremote. ssl") ?: props.remove("sslEnabled")
def ssl = props.remove("com.sun.management.jmxremote.ssl") ?: props.remove("sslEnabled")
env.put("com.sun.management.jmxremote.ssl", ssl)

// config other rmi props
if (protocol == "rmi") {
if (ssl) {
def csf = props.remove(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE) ?: new SslRMIClientSocketFactory()
def ssf = props.remove(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE) ?: new SslRMIServerSocketFactory()
def ssf = props.remove(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE) ?: new SslRMIServerSocketFactory((String[]) null, ENABLED_TLS_PROTOCOLS, false)
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf)
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf)
}
Expand All @@ -158,6 +176,8 @@ class JmxServerConnectorFactory extends AbstractFactory {
}

props.clear()

return env
}

private JMXServiceURL generateServiceUrl(def protocol, def host, def port) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import javax.management.remote.JMXConnector
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL
import javax.management.remote.rmi.RMIConnectorServer
import javax.net.ssl.SSLContext
import javax.rmi.ssl.SslRMIClientSocketFactory
import javax.rmi.ssl.SslRMIServerSocketFactory

@ExtendWith(CgroupV2NpeMitigationExtension)
class JmxServerConnectorFactoryTest {
Expand Down Expand Up @@ -70,4 +73,79 @@ class JmxServerConnectorFactoryTest {
result.stop()
}

// GROOVY-12119: connector properties were silently discarded because the property-building
// method ended in props.clear() (a void call) and so implicitly returned null instead of the env map.
@Test
void testConnectorPropertiesAreReturned_Groovy12119() {
def factory = new JmxServerConnectorFactory()
def env = factory.confiConnectorProperties('rmi', rmi.port, [authenticate: false])

assert env != null : 'connector environment map must not be discarded'
// supplied/derived entries are present
assert env.containsKey('com.sun.management.jmxremote.authenticate')
}

// GROOVY-12119: when SSL is requested the env map must carry the SSL socket factories
@Test
void testConnectorPropertiesApplySsl_Groovy12119() {
def factory = new JmxServerConnectorFactory()
def env = factory.confiConnectorProperties('rmi', rmi.port, [sslEnabled: true])

assert env != null
assert env['com.sun.management.jmxremote.ssl']
assert env[RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE] instanceof SslRMIServerSocketFactory
assert env[RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE] instanceof SslRMIClientSocketFactory
}

// GROOVY-12119: without SSL no socket factories should be added (but env is still returned)
@Test
void testConnectorPropertiesWithoutSsl_Groovy12119() {
def factory = new JmxServerConnectorFactory()
def env = factory.confiConnectorProperties('rmi', rmi.port, [authenticate: false])

assert env != null
assert !env.containsKey(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE)
assert !env.containsKey(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE)
}

// GROOVY-12119: an SSL-enabled connector is built and started without error (end-to-end smoke test)
@Test
void testJmxServerConnectorWithSsl_Groovy12119() {
RMIConnectorServer result = builder.serverConnector(port: rmi.port, properties: [sslEnabled: true])

assert result
result.start()
assert result.isActive()
result.stop()
}

// GROOVY-12119: the canonical 'com.sun.management.jmxremote.ssl' property key is recognised
// (previously the key literal contained a stray space so the standard key never matched)
@Test
void testConnectorRecognizesCanonicalSslKey_Groovy12119() {
def factory = new JmxServerConnectorFactory()
def env = factory.confiConnectorProperties('rmi', rmi.port, ['com.sun.management.jmxremote.ssl': true])

assert env != null
assert env['com.sun.management.jmxremote.ssl']
assert env[RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE] instanceof SslRMIServerSocketFactory
}

// GROOVY-12119: the SSL server socket factory is pinned to modern TLS rather than the JVM default set,
// but only to protocols the running JDK actually supports (so old JDKs without TLS 1.3 are not locked out)
@Test
void testSslServerSocketFactoryRestrictsProtocols_Groovy12119() {
def factory = new JmxServerConnectorFactory()
def env = factory.confiConnectorProperties('rmi', rmi.port, [sslEnabled: true])

SslRMIServerSocketFactory ssf = (SslRMIServerSocketFactory) env[RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE]
def enabled = ssf.enabledProtocols as Set
def supported = SSLContext.getDefault().supportedSSLParameters.protocols as Set

assert !enabled.isEmpty()
assert enabled.every { it in supported } // never requests an unsupported protocol -> no lockout
assert enabled.every { it in ['TLSv1.3', 'TLSv1.2'] } // modern TLS only, no legacy 1.0/1.1
assert 'TLSv1.2' in enabled // always present on JDK 8+
}

}
Loading