Java >> Java opplæring >  >> Java

SSL for JMX med RMI

Vi har en Java-applikasjon som har hatt en JConsole-forbindelse med passordautentisering en stund. For å forbedre sikkerheten til dette prøver vi å kryptere tilkoblingen fra JConsole til applikasjonen.

Frem til nå har vi lansert applikasjonen vår med følgende startkommando:

java -Dcom.sun.management.jmxremote 
     -Dcom.sun.management.jmxremote.port=1099 
     -Dcom.sun.management.jmxremote.rmi.port=1099 
     -Dcom.sun.management.jmxremote.authenticate=true 
     -Dcom.sun.management.jmxremote.password.file=jmx.password 
     -Dcom.sun.management.jmxremote.access.file=jmx.access 
     -Dcom.sun.management.jmxremote.ssl=false
     -jar MyApplication.jar

Med dette kan vi feilfritt få tilgang til JMX-metodene til MyApplication via både JConsole, jmxterm og andre Java-applikasjoner. I JConsole og jmxterm kan vi bruke både hostname:1099 og service:jmx:rmi:///jndi/rmi://hostname:1099/jmxrmi uten problemer. Fra Java-applikasjonene bruker vi alltid service:jmx:rmi:///jndi/rmi://hostname:1099/jmxrmi , igjen uten problemer. Vår applikasjon har ingen kodebasert oppsett av JMX-endepunktet (vi avslører noen metoder og attributter, men vi har ikke rørt registeret og socket-fabrikkene).

Nå prøver vi å sette opp SSL mellom applikasjonen vår og alle andre parter, etter www.cleantutorials.com/jconsole/jconsole-ssl-with-password-authentication. Ved å gjøre dette har vi et nøkkellager og truststore for både MyApplication og hvem klientforbindelsen til JMX-metodene er. Vi bruker

java -Dcom.sun.management.jmxremote 
     -Dcom.sun.management.jmxremote.port=1099 
     -Dcom.sun.management.jmxremote.rmi.port=1099 
     -Dcom.sun.management.jmxremote.authenticate=true 
     -Dcom.sun.management.jmxremote.password.file=jmx.password 
     -Dcom.sun.management.jmxremote.access.file=jmx.access 
     -Dcom.sun.management.jmxremote.ssl=true 
     -Dcom.sun.management.jmxremote.ssl.need.client.auth=true 
     -Dcom.sun.management.jmxremote.registry.ssl=true 
     -Djava.rmi.server.hostname=hostname 
     -Djavax.net.ssl.keyStore=server-jmx-keystore 
     -Djavax.net.ssl.keyStorePassword=password 
     -Djavax.net.ssl.trustStore=server-jmx-truststore 
     -Djavax.net.ssl.trustStorePassword=password 
     -jar MyApplication.jar

Etter dette svikter nesten alle tilkoblingene våre. Den eneste som lykkes er via JConsole (legger til klientnøkkellageret og truststores til lanseringskonfigurasjonen), og bruker bare hostname:1099 . Bruke adressen service:jmx:rmi:///jndi/rmi://hostname:1099/jmxrmi fungerer ikke lenger, ikke via JConsole, ikke via jmxterm, og ikke via andre applikasjoner.

Vi har prøvd en hvilken som helst kombinasjon av lanseringsinnstillinger vi kunne tenke oss, men ingenting vi finner noe sted ser ut til å fungere. Feilen vi ser når vi prøver å koble fra f.eks. jmxterm er:

java.rmi.ConnectIOException: non-JRMP server at remote endpoint

(Jeg kan gi hele stabelen om nødvendig).

Vi er litt i tvil om hvordan vi skal fortsette, hva vi kan gjøre for å få alle tilkoblinger som før fungerte til å fungere. Hva skal vi gjøre for å aktivere tilkobling med service:jmx:rmi:///jndi/rmi://hostname:1099/jmxrmi -lignende tilkoblingsstrenger via SSL?

Hvis det er relevant, bruker denne applikasjonen OpenJDK 11.0.5, andre applikasjoner der vi kanskje trenger dette å kjøre på OpenJDK 8.

Rediger

Ved å feilsøke både JConsole-klienten og backend-siden ser det ut til at protokollen som klienten prøver å etablere ikke er kjent i SSL-konteksten. På backend har vi følgende feil:

javax.net.ssl|DEBUG|20|RMI TCP Connection(1)|2021-12-28 10:04:04.265 CET|null:-1|Raw read (
  0000: 4A 52 4D 49 00
                JRMI.
)
javax.net.ssl|ERROR|20|RMI TCP Connection(1)|2021-12-28 10:04:04.267 CET|null:-1|Fatal (UNEXPECTED_MESSAGE): Unsupported or unrecognized SSL message (
"throwable" : {
  javax.net.ssl.SSLException: Unsupported or unrecognized SSL message
          at java.base/sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(Unknown Source)
          at java.base/sun.security.ssl.SSLSocketInputRecord.decode(Unknown Source)
          at java.base/sun.security.ssl.SSLTransport.decode(Unknown Source)
          at java.base/sun.security.ssl.SSLSocketImpl.decode(Unknown Source)
          at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(Unknown Source)
          at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
          at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(Unknown Source)
          at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(Unknown Source)
          at java.base/java.io.BufferedInputStream.fill(Unknown Source)
          at java.base/java.io.BufferedInputStream.read(Unknown Source)
          at java.base/java.io.DataInputStream.readInt(Unknown Source)
          at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
          at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
          at java.base/java.security.AccessController.doPrivileged(Native Method)
          at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
          at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
          at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
          at java.base/java.lang.Thread.run(Unknown Source)}
)

Deretter lukker backend tilkoblingen.

Basert på noen veiledninger på nettet, skal det være mulig å få SSL-tilkoblingen til å fungere ved å bruke den tjenestebaserte URL-adressen, men vi kan ikke få den til å fungere.

Svar

Etter et langt søk, med mye feilsøking, prøving og feiling, kom vi til den konklusjonen at det ikke finnes noen out-of-the-box-løsning i Spring (Boot) for å aktivere SSL med et RMI-register og en JMX-tilkoblingsserver. Dette måtte konfigureres manuelt. Vi brukte følgende Spring-konfigurasjonsklasse som gjorde susen:

@Configuration
@EnableMBeanExport
public class JMXConfig {

    private static final Log LOG = LogFactory.getLog(JMXConfig .class);

    @Value("${jmx.registry.port:1098}")
    private Integer registryPort;
    @Value("${jmx.rmi.port:1099}")
    private Integer rmiPort;

    @Bean
    public RmiRegistryFactoryBean rmiRegistry() {
        final RmiRegistryFactoryBean rmiRegistryFactoryBean = new RmiRegistryFactoryBean();
        rmiRegistryFactoryBean.setPort(rmiPort);
        rmiRegistryFactoryBean.setAlwaysCreate(true);

        LOG.info("Creating RMI registry on port " + rmiRegistryFactoryBean.getPort());
        return rmiRegistryFactoryBean;
    }

    @Bean
    @DependsOn("rmiRegistry")
    public ConnectorServerFactoryBean connectorServerFactoryBean() throws MalformedObjectNameException {
        String rmiHost = getHost();
        String serviceURL = serviceURL(rmiHost);
        LOG.info("Creating JMX connection for URL " + serviceURL);

        final ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean();
        connectorServerFactoryBean.setObjectName("connector:name=rmi");
        connectorServerFactoryBean.setEnvironmentMap(createRmiEnvironment(rmiHost));
        connectorServerFactoryBean.setServiceUrl(serviceURL);
        return connectorServerFactoryBean;
    }

    private String getHost() {
        try {
            InetAddress localMachine = InetAddress.getLocalHost();
            return localMachine.getCanonicalHostName();
        } catch (UnknownHostException e) {
            LOG.warn("Unable to get hostname, using localhost", e);
            return "localhost";
        }
    }

    private String serviceURL(String rmiHost) {
        return format("service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi", rmiHost, registryPort, rmiHost, rmiPort);
    }

    private Map<String, Object> createRmiEnvironment(String rmiHost) {
        final Map<String, Object> rmiEnvironment = new HashMap<>();
        rmiEnvironment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        rmiEnvironment.put(Context.PROVIDER_URL, "rmi://" + rmiHost + ":" + rmiPort);
        rmiEnvironment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());
        rmiEnvironment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new SslRMIServerSocketFactory());
        return rmiEnvironment;
    }
}

Dette aktiverer SSL ved å bruke tilkoblingsdetaljene service:jmx:rmi:///jndi/rmi://hostname:1099/jmxrmi . For å få det til å fungere, må du legge til et nøkkellager/passord til backend, og et truststore/passord til frontend (som i veiledningen).


Java Tag