Fixes #1517 - Review JMX's ConnectorServer.
Introduced possibility to connect via TLS. Updated the documentation.
This commit is contained in:
parent
de16eba903
commit
f0d2e2b764
|
@ -17,93 +17,93 @@
|
||||||
[[using-jmx]]
|
[[using-jmx]]
|
||||||
=== Using JMX with Jetty
|
=== Using JMX with Jetty
|
||||||
|
|
||||||
Jetty JMX integration uses the platform MBean server implementation that Java VM provides.
|
Jetty's architecture is based on POJO components (see xref:basic-architecture[]).
|
||||||
The integration is based on the `ObjectMBean` implementation of `DynamicMBean`.
|
These components are organized in a tree and each component may have a lifecycle
|
||||||
This implementation allows you to wrap an arbitrary POJO in an MBean and annotate it appropriately to expose it via JMX.
|
that spans the `Server` lifetime, or a web application lifetime, or even shorter
|
||||||
See xref:jetty-jmx-annotations[].
|
lifetimes such as that of a TCP connection.
|
||||||
|
|
||||||
The `MBeanContainer` implementation of the `Container.Listener` interface coordinates creation of MBeans.
|
Every time a component is added or removed from the component tree, an event is
|
||||||
The Jetty Server and it's components use a link:{JDURL}/org/eclipse/jetty/util/component/Container.html[Container] to maintain a containment tree of components and to support notification of changes to that tree.
|
emitted, and link:{JDURL}/org/eclipse/jetty/util/component/Container.html[`Container.Listener`]
|
||||||
The `MBeanContainer` class listens for Container events and creates and destroys MBeans as required to wrap all Jetty components.
|
implementations can listen to those events and perform additional actions.
|
||||||
|
|
||||||
You can access the MBeans that Jetty publishes both through built-in Java VM connector via JConsole or JMC, or by registering a remote JMX connector and using a remote JMX agent to monitor Jetty.
|
One such `Container.Listener` is `MBeanContainer` that uses `ObjectMBean` to
|
||||||
|
create an MBean from an arbitrary POJO, and register/unregister the MBean to/from
|
||||||
|
the platform `MBeanServer`.
|
||||||
|
|
||||||
|
Jetty components are annotated with <<jetty-jmx-annotations,JMX annotations>>
|
||||||
|
and provide specific JMX details so that `ObjectMBean` can build a more
|
||||||
|
precise representation of the JMX metadata associated with the component POJO.
|
||||||
|
|
||||||
|
Therefore, when a component is added to the component tree, `MBeanContainer` is
|
||||||
|
notified, it creates the MBean from the component POJO and registers it to
|
||||||
|
the `MBeanServer`.
|
||||||
|
Similarly, when a component is removed from the tree, `MBeanContainer` is
|
||||||
|
notified, and unregisters the MBean from the `MBeanServer`.
|
||||||
|
|
||||||
|
The Jetty MBeans can be accessed via any JMX console such as Java Mission Control
|
||||||
|
(JMC), VisualVM, JConsole or others.
|
||||||
|
|
||||||
[[configuring-jmx]]
|
[[configuring-jmx]]
|
||||||
==== Configuring JMX
|
==== Configuring JMX
|
||||||
|
|
||||||
This guide describes how to initialize and configure the Jetty JMX integration.
|
This guide describes the various ways to initialize and configure the Jetty JMX integration.
|
||||||
|
Configuring the Jetty JMX integration only registers the Jetty MBeans into the platform
|
||||||
|
`MBeanServer`, and therefore the MBeans can only be accessed locally (from the same machine),
|
||||||
|
not from remote machines.
|
||||||
|
|
||||||
To monitor an application using JMX, perform the following steps:
|
This means that this configuration is enough for development, where you have easy access
|
||||||
|
(with graphical user interface) to the machine where Jetty runs, but it is typically not
|
||||||
* Configure the application to instantiate an MBean container.
|
enough when the machine Jetty where runs is remote, or only accessible via SSH or otherwise
|
||||||
* Instrument objects to be MBeans.
|
without graphical user interface support.
|
||||||
* Provide access for JMX agents to MBeans.
|
In these cases, you have to enable <<jmx-remote-access,JMX remote access>>.
|
||||||
|
|
||||||
[[accessing-jetty-mbeans]]
|
|
||||||
===== Using JConsole to Access Jetty MBeans
|
|
||||||
|
|
||||||
The simplest way to access the MBeans that Jetty publishes is to use the http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html[JConsole utility] the Java Virtual Machine supplies.
|
|
||||||
See xref:jetty-jconsole[] for instructions on how to configure JVM for use with JConsole or JMC.
|
|
||||||
|
|
||||||
To access Jetty MBeans via JConsole or JMC, you must:
|
|
||||||
|
|
||||||
* Enable the registration of Jetty MBeans into the platform MBeanServer.
|
|
||||||
* Enable a `JMXConnectorServer` so that JConsole/JMC can connect and visualize the MBeans.
|
|
||||||
|
|
||||||
[[registering-jetty-mbeans]]
|
|
||||||
===== Registering Jetty MBeans
|
|
||||||
|
|
||||||
Configuring Jetty JMX integration differs for standalone and embedded Jetty.
|
|
||||||
|
|
||||||
[[jmx-standalone-jetty]]
|
[[jmx-standalone-jetty]]
|
||||||
====== Standalone Jetty
|
===== Standalone Jetty Server
|
||||||
|
|
||||||
JMX is not enabled by default in the Jetty distribution.
|
JMX is not enabled by default in the Jetty distribution.
|
||||||
To enable JMX in the Jetty distribution, run the following, where `{$jetty.home}` is the directory where you have the Jetty distribution located (see link:#startup-base-and-home[the documentation for Jetty base vs. home examples]):
|
To enable JMX in the Jetty distribution run the following, where `{$jetty.home}`
|
||||||
|
is the directory where you have the Jetty distribution installed, and
|
||||||
|
`${jetty.base}` is the directory where you have your Jetty configuration
|
||||||
|
(see link:#startup-base-and-home[the documentation for Jetty base vs. home examples]):
|
||||||
|
|
||||||
[source, screen, subs="{sub-order}"]
|
[source, screen, subs="{sub-order}"]
|
||||||
....
|
----
|
||||||
|
$ cd ${jetty.base}
|
||||||
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx
|
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx
|
||||||
....
|
----
|
||||||
|
|
||||||
Running the above command will append the available configurable elements of the JMX module to the `{$jetty.base}/start.ini` file.
|
Running the above command will append the available configurable elements of the `jmx` module
|
||||||
If you are managing separate ini files for your modules in the distribution, use `--add-to-start.d=jmx` instead.
|
to the `{$jetty.base}/start.ini` file, or create the `${jetty.base}/start.d/jmx.ini` file.
|
||||||
|
|
||||||
If you wish to add remote access for JMX, you will also need to enable the JMX-Remote module:
|
|
||||||
|
|
||||||
[source, screen, subs="{sub-order}"]
|
|
||||||
....
|
|
||||||
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx-remote
|
|
||||||
....
|
|
||||||
|
|
||||||
[[jmx-embedded-jetty]]
|
[[jmx-embedded-jetty]]
|
||||||
====== Embedded Jetty
|
===== Embedded Jetty Server
|
||||||
|
|
||||||
When running Jetty embedded into an application, create and configure an MBeanContainer instance as follows:
|
When running Jetty embedded into an application, create and configure an `MBeanContainer`
|
||||||
|
instance as follows:
|
||||||
|
|
||||||
[source, java]
|
[source, java]
|
||||||
----
|
----
|
||||||
|
|
||||||
Server server = new Server();
|
Server server = new Server();
|
||||||
|
|
||||||
// Setup JMX
|
// Setup JMX.
|
||||||
MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
||||||
server.addEventListener(mbContainer);
|
server.addBean(mbeanContainer);
|
||||||
server.addBean(mbContainer);
|
|
||||||
|
|
||||||
// Add loggers MBean to server (will be picked up by MBeanContainer above)
|
// Export the loggers as MBeans.
|
||||||
server.addBean(Log.getLog());
|
server.addBean(Log.getLog());
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
Notice that Jetty creates the `MBeanContainer` immediately after creating the Server, and immediately after registering it as an `EventListener` of the Server object (which is also a Container object).
|
Because logging is initialized prior to the `MBeanContainer` (even before the `Server` itself),
|
||||||
|
it is necessary to register the logger manually via `server.addBean()` so that the loggers may
|
||||||
|
show up in the JMX tree as MBeans.
|
||||||
|
|
||||||
Because logging is initialized prior to the `MBeanContainer` (even before the Server itself), it is necessary to register the logger manually via `server.addBean()` so that the loggers may show up in the JMX tree.
|
[[jmx-jetty-maven-plugin]]
|
||||||
|
|
||||||
[[jmx-using-jetty-maven-plugin]]
|
|
||||||
===== Using the Jetty Maven Plugin with JMX
|
===== Using the Jetty Maven Plugin with JMX
|
||||||
|
|
||||||
If you are using the link:#jetty-maven-plugin[Jetty Maven plugin] you should copy the `/etc/jetty-jmx.xml` file into your webapp project somewhere, such as `/src/etc,` then add a `<jettyconfig>` element to the plugin `<configuration>`:
|
If you are using the link:#jetty-maven-plugin[Jetty Maven plugin] you should copy the
|
||||||
|
`${jetty.home}/etc/jetty-jmx.xml` file into your webapp project somewhere, such as
|
||||||
|
`src/main/config/etc/`, then add a
|
||||||
|
`<jettyXml>` element to the `<configuration>` element of the Jetty Maven Plugin:
|
||||||
|
|
||||||
[source, xml, subs="{sub-order}"]
|
[source, xml, subs="{sub-order}"]
|
||||||
----
|
----
|
||||||
|
@ -113,21 +113,153 @@ If you are using the link:#jetty-maven-plugin[Jetty Maven plugin] you should cop
|
||||||
<version>{VERSION}</version>
|
<version>{VERSION}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<scanintervalseconds>10</scanintervalseconds>
|
<scanintervalseconds>10</scanintervalseconds>
|
||||||
<jettyXml>src/etc/jetty-jmx.xml</jettyXml>
|
<jettyXml>src/main/config/etc/jetty-jmx.xml</jettyXml>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[accessing-jetty-mbeans]]
|
||||||
|
==== Using JConsole or Java Mission Control to Access Jetty MBeans
|
||||||
|
|
||||||
[[enabling-jmxconnectorserver-for-remote-access]]
|
The simplest way to access the MBeans that Jetty publishes is to use
|
||||||
==== Enabling JMXConnectorServer for Remote Access
|
<<jetty-jconsole,Java Mission Control (JMC) or JConsole>>.
|
||||||
|
|
||||||
There are two ways of enabling remote connectivity so that JConsole or JMC can connect to visualize MBeans.
|
Both these tools can connect to local or remote JVMs to display the MBeans.
|
||||||
|
|
||||||
|
For local access, you just need to start JConsole or JMC and then choose
|
||||||
|
from their user interface the local JVM you want to connect to.
|
||||||
|
|
||||||
|
For remote access, you need first to enable <<jmx-remote-access,JMX remote access>>
|
||||||
|
in Jetty.
|
||||||
|
|
||||||
|
[[jmx-remote-access]]
|
||||||
|
==== Enabling JMX Remote Access
|
||||||
|
|
||||||
|
There are two ways of enabling remote connectivity so that JConsole or JMC can connect
|
||||||
|
to the remote JVM to visualize MBeans.
|
||||||
|
|
||||||
* Use the `com.sun.management.jmxremote` system property on the command line.
|
* Use the `com.sun.management.jmxremote` system property on the command line.
|
||||||
Unfortunately, this solution does not work well with firewalls and is not flexible.
|
Unfortunately, this solution does not work well with firewalls and is not flexible.
|
||||||
* Use Jetty's `ConnectorServer` class.
|
* Use Jetty's `jmx-remote` module or - equivalently - the `ConnectorServer` class.
|
||||||
To enable use of this class, uncomment the correspondent portion in `/etc/jetty-jmx.xml,` like this:
|
|
||||||
|
`ConnectorServer` will use by default RMI to allow connection from remote clients,
|
||||||
|
and it is a wrapper around the standard JDK class `JMXConnectorServer`, which is
|
||||||
|
the class that provides remote access to JMX clients.
|
||||||
|
|
||||||
|
Connecting to the remote JVM is a two step process:
|
||||||
|
|
||||||
|
* First, the client will connect to the RMI _registry_ to download the RMI stub for
|
||||||
|
the `JMXConnectorServer`; this RMI stub contains the IP address and port to connect
|
||||||
|
to the RMI server, i.e. the remote `JMXConnectorServer`.
|
||||||
|
* Second, the client uses the RMI stub to connect to the RMI _server_ (i.e. the
|
||||||
|
remote `JMXConnectorServer`) typically on an address and port that may be different
|
||||||
|
from the RMI registry address and port.
|
||||||
|
|
||||||
|
The configuration for the RMI registry and the RMI server is specified by a `JMXServiceURL`.
|
||||||
|
The string format of an RMI `JMXServiceURL` is:
|
||||||
|
|
||||||
|
[source, screen, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
service:jmx:rmi://<rmi_server_host>:<rmi_server_port>/jndi/rmi://<rmi_registry_host>:<rmi_registry_port>/jmxrmi
|
||||||
|
----
|
||||||
|
|
||||||
|
Default values are:
|
||||||
|
|
||||||
|
[source, screen, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
rmi_server_host = localhost
|
||||||
|
rmi_server_port = 1099
|
||||||
|
rmi_registry_host = localhost
|
||||||
|
rmi_registry_port = 1099
|
||||||
|
----
|
||||||
|
|
||||||
|
With the default configuration, only clients that are local to the server machine can connect
|
||||||
|
to the RMI registry and RMI server - this is done for security reasons.
|
||||||
|
With this configuration it would still be possible to access the MBeans from remote using
|
||||||
|
a <<jmx-remote-access-ssh-tunnel,SSH tunnel>>.
|
||||||
|
|
||||||
|
By specifying an appropriate `JMXServiceURL`, you can fine tune the network interfaces the
|
||||||
|
RMI registry and the RMI server bind to, and the ports that the RMI registry and the RMI server
|
||||||
|
listen to.
|
||||||
|
The RMI server and RMI registry hosts and ports can be the same (as in the default configuration)
|
||||||
|
because RMI is able to multiplex traffic arriving to a port to multiple RMI objects.
|
||||||
|
|
||||||
|
If you need to allow JMX remote access through a firewall, you must open both the RMI registry
|
||||||
|
and the RMI server ports.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
[source, screen, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
service:jmx:rmi:///jndi/rmi:///jmxrmi
|
||||||
|
rmi_server_host = any address
|
||||||
|
rmi_server_port = randomly chosen
|
||||||
|
rmi_registry_host = any address
|
||||||
|
rmi_registry_port = 1099
|
||||||
|
|
||||||
|
service:jmx:rmi://localhost:1100/jndi/rmi://localhost:1099/jmxrmi
|
||||||
|
rmi_server_host = loopback address
|
||||||
|
rmi_server_port = 1100
|
||||||
|
rmi_registry_host = loopback address
|
||||||
|
rmi_registry_port = 1099
|
||||||
|
----
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
When `ConnectorServer` is started, its RMI stub is exported to the RMI registry.
|
||||||
|
The RMI stub contains the IP address and port to connect to the RMI object, but
|
||||||
|
the IP address is typically the machine host name, not the host specified in the
|
||||||
|
`JMXServiceURL`.
|
||||||
|
|
||||||
|
To control the IP address stored in the RMI stub you need to set the system
|
||||||
|
property `java.rmi.server.hostname` with the desired value.
|
||||||
|
This is especially important when binding the RMI server host to the loopback
|
||||||
|
address for security reasons. See also
|
||||||
|
<<jmx-remote-access-ssh-tunnel,JMX Remote Access via SSH Tunnel>>.
|
||||||
|
====
|
||||||
|
|
||||||
|
===== Enabling JMX Remote Access in Standalone Jetty Server
|
||||||
|
|
||||||
|
Similarly to <<jmx-standalone-jetty,enabling JMX in a standalone Jetty server>>, you
|
||||||
|
enable the `jmx-remote` module:
|
||||||
|
|
||||||
|
[source, screen, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
$ cd ${jetty.base}
|
||||||
|
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx-remote
|
||||||
|
----
|
||||||
|
|
||||||
|
===== Enabling JMX Remote Access in Embedded Jetty
|
||||||
|
|
||||||
|
When running Jetty embedded into an application, create and configure a `ConnectorServer`:
|
||||||
|
|
||||||
|
[source, java, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
Server server = new Server();
|
||||||
|
|
||||||
|
// Setup JMX
|
||||||
|
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
||||||
|
server.addBean(mbeanContainer);
|
||||||
|
|
||||||
|
// Setup ConnectorServer
|
||||||
|
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1999, "/jndi/rmi:///jmxrmi");
|
||||||
|
ConnectorServer jmxServer = new ConnectorServer(jmxURL, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
|
||||||
|
server.addBean(jmxServer);
|
||||||
|
----
|
||||||
|
|
||||||
|
The `JMXServiceURL` above specifies that the RMI server binds to the wildcard address
|
||||||
|
on port 1999, while the RMI registry binds to the wildcard address on port 1099 (the
|
||||||
|
default RMI registry port).
|
||||||
|
|
||||||
|
[[jmx-remote-access-authorization]]
|
||||||
|
===== JMX Remote Access Authorization
|
||||||
|
|
||||||
|
The standard `JMXConnectorServer` provides several options to authorize access.
|
||||||
|
For a complete guide to controlling authentication and authorization in JMX, see
|
||||||
|
https://blogs.oracle.com/lmalventosa/entry/jmx_authentication_authorization[Authentication and Authorization in JMX RMI connectors].
|
||||||
|
|
||||||
|
To authorize access to the `JMXConnectorServer` you can use this configuration,
|
||||||
|
where the `jmx.password` and `jmx.access` files have the format specified in the blog entry above:
|
||||||
|
|
||||||
[source, xml, subs="{sub-order}"]
|
[source, xml, subs="{sub-order}"]
|
||||||
----
|
----
|
||||||
|
@ -136,50 +268,22 @@ To enable use of this class, uncomment the correspondent portion in `/etc/jetty-
|
||||||
<New class="javax.management.remote.JMXServiceURL">
|
<New class="javax.management.remote.JMXServiceURL">
|
||||||
<Arg type="java.lang.String">rmi</Arg>
|
<Arg type="java.lang.String">rmi</Arg>
|
||||||
<Arg type="java.lang.String" />
|
<Arg type="java.lang.String" />
|
||||||
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
|
<Arg type="java.lang.Integer">1099</Arg>
|
||||||
<Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
|
<Arg type="java.lang.String">/jndi/rmi:///jmxrmi</Arg>
|
||||||
</New>
|
|
||||||
</Arg>
|
|
||||||
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
|
||||||
<Call name="start" />
|
|
||||||
</New>
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
This configuration snippet starts an `RMIRegistry` and a `JMXConnectorServer` both on port 1099 (by default), so that firewalls should open just that one port to allow connections from JConsole or JMC.
|
|
||||||
|
|
||||||
[[securing-remote-access]]
|
|
||||||
==== Securing Remote Access
|
|
||||||
|
|
||||||
`JMXConnectorServer` several options to restrict access.
|
|
||||||
For a complete guide to controlling authentication and authorization in JMX, see https://blogs.oracle.com/lmalventosa/entry/jmx_authentication_authorization[Authentication and Authorization in JMX RMI connectors] in Luis-Miguel Alventosa's blog.
|
|
||||||
|
|
||||||
To restrict access to the `JMXConnectorServer`, you can use this configuration, where the `jmx.password` and `jmx.access` files have the format specified in the blog entry above:
|
|
||||||
|
|
||||||
[source, xml, subs="{sub-order}"]
|
|
||||||
----
|
|
||||||
|
|
||||||
<New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
|
|
||||||
<Arg>
|
|
||||||
<New class="javax.management.remote.JMXServiceURL">
|
|
||||||
<Arg type="java.lang.String">rmi</Arg>
|
|
||||||
<Arg type="java.lang.String" />
|
|
||||||
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
|
|
||||||
<Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
|
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
<Arg>
|
<Arg>
|
||||||
<Map>
|
<Map>
|
||||||
<Entry>
|
<Entry>
|
||||||
<Item>jmx.remote.x.password.file</Item>
|
<Item>jmx.remote.x.access.file</Item>
|
||||||
<Item>
|
<Item>
|
||||||
<New class="java.lang.String"><Arg><Property name="jetty.home" default="." />/resources/jmx.password</Arg></New>
|
<New class="java.lang.String"><Arg><Property name="jetty.base" default="." />/resources/jmx.access</Arg></New>
|
||||||
</Item>
|
</Item>
|
||||||
</Entry>
|
</Entry>
|
||||||
<Entry>
|
<Entry>
|
||||||
<Item>jmx.remote.x.access.file</Item>
|
<Item>jmx.remote.x.password.file</Item>
|
||||||
<Item>
|
<Item>
|
||||||
<New class="java.lang.String"><Arg><Property name="jetty.home" default="." />/resources/jmx.access</Arg></New>
|
<New class="java.lang.String"><Arg><Property name="jetty.base" default="." />/resources/jmx.password</Arg></New>
|
||||||
</Item>
|
</Item>
|
||||||
</Entry>
|
</Entry>
|
||||||
</Map>
|
</Map>
|
||||||
|
@ -187,17 +291,141 @@ To restrict access to the `JMXConnectorServer`, you can use this configuration,
|
||||||
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
||||||
<Call name="start" />
|
<Call name="start" />
|
||||||
</New>
|
</New>
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
[[custom-monitor-applcation]]
|
Similarly, in code:
|
||||||
==== Custom Monitor Application
|
|
||||||
|
|
||||||
Using the JMX API, you can also write a custom application to monitor your Jetty server.
|
[source, java, subs="{sub-order}"]
|
||||||
To allow this application to connect to your Jetty server, you need to uncomment the last section of the `/etc/jetty-jmx.xml` configuration file and optionally modify the endpoint name.
|
----
|
||||||
Doing so creates a JMX HTTP connector and registers a JMX URL that outputs to the `Stderr` log.
|
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
|
||||||
|
Map<String, Object> env = new HashMap<>();
|
||||||
|
env.put("jmx.remote.x.access.file", "resources/jmx.access");
|
||||||
|
env.put("jmx.remote.x.password.file", "resources/jmx.password");
|
||||||
|
ConnectorServer jmxServer = new ConnectorServer(jmxURL, env, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
|
||||||
|
jmxServer.start();
|
||||||
|
----
|
||||||
|
|
||||||
You should provide the URL that appears in the log to your monitor application in order to create an `MBeanServerConnection.`
|
Calling `ConnectorServer.start()` may be explicit as in the examples above,
|
||||||
You can use the same URL to connect to your Jetty instance from a remote machine using JConsole or JMC.
|
or can be skipped when adding the `ConnectorServer` as a bean to the `Server`,
|
||||||
See the link:{GITBROWSEURL}/jetty-jmx/src/main/config/etc/jetty-jmx.xml[configuration file] for more details.
|
so that starting the `Server` will also start the `ConnectorServer`.
|
||||||
|
|
||||||
|
===== Securing JMX Remote Access with TLS
|
||||||
|
|
||||||
|
The JMX communication via RMI happens by default in clear-text.
|
||||||
|
|
||||||
|
It is possible to configure the `ConnectorServer` with a `SslContextFactory` so
|
||||||
|
that the JMX communication via RMI is encrypted:
|
||||||
|
|
||||||
|
[source, xml, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
<New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
|
||||||
|
<Arg>
|
||||||
|
<New class="javax.management.remote.JMXServiceURL">
|
||||||
|
<Arg type="java.lang.String">rmi</Arg>
|
||||||
|
<Arg type="java.lang.String" />
|
||||||
|
<Arg type="java.lang.Integer">1099</Arg>
|
||||||
|
<Arg type="java.lang.String">/jndi/rmi:///jmxrmi</Arg>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
<Arg />
|
||||||
|
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
||||||
|
<Arg><Ref refid="sslContextFactory" /></Arg>
|
||||||
|
</New>
|
||||||
|
----
|
||||||
|
|
||||||
|
Similarly, in code:
|
||||||
|
|
||||||
|
[source, java, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||||
|
sslContextFactory.setKeyStorePath();
|
||||||
|
sslContextFactory.setKeyStorePassword("secret");
|
||||||
|
|
||||||
|
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
|
||||||
|
ConnectorServer jmxServer = new ConnectorServer(jmxURL, null, "org.eclipse.jetty.jmx:name=rmiconnectorserver", sslContextFactory);
|
||||||
|
----
|
||||||
|
|
||||||
|
It is possible to use the same `SslContextFactory` used to configure the
|
||||||
|
Jetty `ServerConnector` that supports TLS for the HTTP protocol.
|
||||||
|
This is used in the XML example above: the `SslContextFactory` configured
|
||||||
|
for the TLS `ServerConnector` is registered with an id of `sslContextFactory`
|
||||||
|
which is referenced in the XML via the `Ref` element.
|
||||||
|
|
||||||
|
The keystore must contain a valid certificate signed by a Certification Authority.
|
||||||
|
|
||||||
|
The RMI mechanic is the usual one: the RMI client (typically a monitoring console)
|
||||||
|
will connect first to the RMI registry (using TLS), download the RMI server stub
|
||||||
|
that contains the address and port of the RMI server to connect to, then connect
|
||||||
|
to the RMI server (using TLS).
|
||||||
|
|
||||||
|
This also mean that if the RMI registry and the RMI server are on different hosts,
|
||||||
|
the RMI client must have available the cryptographic material to validate both
|
||||||
|
hosts.
|
||||||
|
|
||||||
|
Having certificates signed by a Certification Authority simplifies by a lot the
|
||||||
|
configuration needed to get the JMX communication over TLS working properly.
|
||||||
|
|
||||||
|
If that is not the case (for example the certificate is self-signed), then you
|
||||||
|
need to specify the required system properties that allow RMI (especially when
|
||||||
|
acting as an RMI client) to retrieve the cryptographic material necessary to
|
||||||
|
establish the TLS connection.
|
||||||
|
|
||||||
|
For example, trying to connect using the JDK standard `JMXConnector` with both
|
||||||
|
the RMI server and the RMI registry to `domain.com`:
|
||||||
|
|
||||||
|
[source, java, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
// System properties necessary for an RMI client to trust a self-signed certificate.
|
||||||
|
System.setProperty("javax.net.ssl.trustStore", "/path/to/trustStore");
|
||||||
|
System.setProperty("javax.net.ssl.trustStorePassword", "secret");
|
||||||
|
|
||||||
|
JMXServiceURL jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://domain.com:1100/jmxrmi")
|
||||||
|
Map<String, Object> clientEnv = new HashMap<>();
|
||||||
|
// Required to connect to the RMI registry via TLS.
|
||||||
|
clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());
|
||||||
|
try (JMXConnector client = JMXConnectorFactory.connect(jmxURL, clientEnv))
|
||||||
|
{
|
||||||
|
Set<ObjectName> names = client.getMBeanServerConnection().queryNames(null, null);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Similarly, to launch JMC:
|
||||||
|
|
||||||
|
[source, java, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
$ jmc -vmargs -Djavax.net.ssl.trustStore=/path/to/trustStore -Djavax.net.ssl.trustStorePassword=secret
|
||||||
|
----
|
||||||
|
|
||||||
|
Note that these system properties are required when launching the `ConnectorServer` too,
|
||||||
|
on the server, because it acts as an RMI client with respect to the RMI registry.
|
||||||
|
|
||||||
|
[[jmx-remote-access-ssh-tunnel]]
|
||||||
|
===== JMX Remote Access with Port Forwarding via SSH Tunnel
|
||||||
|
|
||||||
|
You can access JMX MBeans on a remote machine when the RMI ports are not open,
|
||||||
|
for example because of firewall policies, but you have SSH access to the machine
|
||||||
|
using local port forwarding via a SSH tunnel.
|
||||||
|
|
||||||
|
In this case you want to configure the `ConnectorServer` with a `JMXServiceURL`
|
||||||
|
that binds the RMI server and the RMI registry to the loopback interface only:
|
||||||
|
`service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi`.
|
||||||
|
|
||||||
|
Then you setup the local port forwarding with the SSH tunnel:
|
||||||
|
|
||||||
|
[source, screen, subs="{sub-order}"]
|
||||||
|
----
|
||||||
|
$ ssh -L 1099:localhost:1099 <user>@<machine_host>
|
||||||
|
----
|
||||||
|
|
||||||
|
Now you can use JConsole or JMC to connect to `localhost:1099` on your local
|
||||||
|
computer. The traffic will be forwarded to `machine_host` and when there,
|
||||||
|
SSH will forward the traffic to `localhost:1099`, which is exactly where
|
||||||
|
the `ConnectorServer` listens.
|
||||||
|
|
||||||
|
When you configure `ConnectorServer` in this way, you must set the system
|
||||||
|
property `-Djava.rmi.server.hostname=localhost`, on the server.
|
||||||
|
|
||||||
|
This is required because when the RMI server is exported, its address and
|
||||||
|
port are stored in the RMI stub. You want the address in the RMI stub to be
|
||||||
|
`localhost` so that when the RMI stub is downloaded to the remote client,
|
||||||
|
the RMI communication will go through the SSH tunnel.
|
||||||
|
|
|
@ -13,14 +13,14 @@
|
||||||
</Call>
|
</Call>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Add a remote JMX connector. The parameters of the constructor
|
<!-- Adds a remote JMXConnectorServer. The parameters of the constructor
|
||||||
below specify the JMX service URL, and the object name string for the
|
below specify the JMXServiceURL, and the ObjectName string for the
|
||||||
connector server bean. The parameters of the JMXServiceURL constructor
|
JMXConnectorServer. The parameters of the JMXServiceURL constructor
|
||||||
specify the protocol that clients will use to connect to the remote JMX
|
specify the protocol that clients will use to connect to the remote JMX
|
||||||
connector (RMI), the hostname of the server (local hostname), port number
|
connector (rmi), the hostname and port number of the RMI server, and the
|
||||||
(automatically assigned), and the URL path. Note that URL path contains
|
URL path. Note that URL path contains the RMI registry hostname and port
|
||||||
the RMI registry hostname and port number, that may need to be modified
|
number. Modify the port numbers if you need to comply with the firewall
|
||||||
in order to comply with the firewall requirements.
|
requirements.
|
||||||
-->
|
-->
|
||||||
<Call name="addBean">
|
<Call name="addBean">
|
||||||
<Arg>
|
<Arg>
|
||||||
|
@ -28,9 +28,9 @@
|
||||||
<Arg>
|
<Arg>
|
||||||
<New class="javax.management.remote.JMXServiceURL">
|
<New class="javax.management.remote.JMXServiceURL">
|
||||||
<Arg type="java.lang.String">rmi</Arg>
|
<Arg type="java.lang.String">rmi</Arg>
|
||||||
<Arg type="java.lang.String"><Property name="jetty.jmxremote.rmihost" deprecated="jetty.jmxrmihost" default="localhost"/></Arg>
|
<Arg type="java.lang.String"><Property name="jetty.jmxremote.rmiserverhost" deprecated="jetty.jmxremote.rmihost,jetty.jmxrmihost" default="localhost"/></Arg>
|
||||||
<Arg type="java.lang.Integer"><Property name="jetty.jmxremote.rmiport" deprecated="jetty.jmxrmiport" default="1099"/></Arg>
|
<Arg type="java.lang.Integer"><Property name="jetty.jmxremote.rmiserverport" deprecated="jetty.jmxremote.rmiport,jetty.jmxrmiport" default="1099"/></Arg>
|
||||||
<Arg type="java.lang.String">/jndi/rmi://<Property name="jetty.jmxremote.rmihost" deprecated="jetty.jmxrmihost" default="localhost"/>:<Property name="jetty.jmxremote.rmiport" deprecated="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
|
<Arg type="java.lang.String">/jndi/rmi://<Property name="jetty.jmxremote.rmiregistryhost" deprecated="jetty.jmxremote.rmihost,jetty.jmxrmihost" default="localhost"/>:<Property name="jetty.jmxremote.rmiregistryport" deprecated="jetty.jmxremote.rmiport,jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
<!-- Get the platform mbean server -->
|
<!-- Get the platform MBeanServer -->
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
<Call id="MBeanServer" class="java.lang.management.ManagementFactory"
|
<Call id="MBeanServer" class="java.lang.management.ManagementFactory"
|
||||||
name="getPlatformMBeanServer" />
|
name="getPlatformMBeanServer" />
|
||||||
|
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
<!-- Initialize the Jetty MBean container -->
|
<!-- Initialize the Jetty MBeanContainer -->
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
<Call name="addBean">
|
<Call name="addBean">
|
||||||
<Arg>
|
<Arg>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<!-- Add the static log -->
|
<!-- Add the static log -->
|
||||||
<Call name="addBean">
|
<Call name="addBean">
|
||||||
<Arg>
|
<Arg>
|
||||||
<New class="org.eclipse.jetty.util.log.Log" />
|
<Get class="org.eclipse.jetty.util.log.Log" name="Log" />
|
||||||
</Arg>
|
</Arg>
|
||||||
</Call>
|
</Call>
|
||||||
</Configure>
|
</Configure>
|
||||||
|
|
|
@ -8,8 +8,14 @@ jmx
|
||||||
etc/jetty-jmx-remote.xml
|
etc/jetty-jmx-remote.xml
|
||||||
|
|
||||||
[ini-template]
|
[ini-template]
|
||||||
## The host/address to bind RMI to
|
## The host/address to bind the RMI server to.
|
||||||
# jetty.jmxremote.rmihost=localhost
|
# jetty.jmxremote.rmiserverhost=localhost
|
||||||
|
|
||||||
## The port RMI listens to
|
## The port the RMI server listens to (0 means a random port is chosen).
|
||||||
# jetty.jmxremote.rmiport=1099
|
# jetty.jmxremote.rmiserverport=1099
|
||||||
|
|
||||||
|
## The host/address to bind the RMI registry to.
|
||||||
|
# jetty.jmxremote.rmiregistryhost=localhost
|
||||||
|
|
||||||
|
## The port the RMI registry listens to.
|
||||||
|
# jetty.jmxremote.rmiregistryport=1099
|
||||||
|
|
|
@ -23,12 +23,16 @@ import java.lang.management.ManagementFactory;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.rmi.registry.LocateRegistry;
|
import java.rmi.registry.LocateRegistry;
|
||||||
import java.rmi.registry.Registry;
|
import java.rmi.registry.Registry;
|
||||||
|
import java.rmi.server.RMIClientSocketFactory;
|
||||||
import java.rmi.server.RMIServerSocketFactory;
|
import java.rmi.server.RMIServerSocketFactory;
|
||||||
import java.rmi.server.UnicastRemoteObject;
|
import java.rmi.server.UnicastRemoteObject;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.IntConsumer;
|
||||||
|
|
||||||
import javax.management.MBeanServer;
|
import javax.management.MBeanServer;
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
|
@ -36,26 +40,35 @@ import javax.management.remote.JMXConnectorServer;
|
||||||
import javax.management.remote.JMXConnectorServerFactory;
|
import javax.management.remote.JMXConnectorServerFactory;
|
||||||
import javax.management.remote.JMXServiceURL;
|
import javax.management.remote.JMXServiceURL;
|
||||||
import javax.management.remote.rmi.RMIConnectorServer;
|
import javax.management.remote.rmi.RMIConnectorServer;
|
||||||
|
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.HostPort;
|
import org.eclipse.jetty.util.HostPort;
|
||||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.ShutdownThread;
|
import org.eclipse.jetty.util.thread.ShutdownThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AbstractLifeCycle wrapper for JMXConnectorServer
|
* <p>LifeCycle wrapper for JMXConnectorServer.</p>
|
||||||
|
* <p>This class provides the following facilities:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>participates in the {@code Server} lifecycle</li>
|
||||||
|
* <li>starts the RMI registry if not there already</li>
|
||||||
|
* <li>allows to bind the RMI registry and the RMI server to the loopback interface</li>
|
||||||
|
* <li>makes it easy to use TLS for the JMX communication</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class ConnectorServer extends AbstractLifeCycle
|
public class ConnectorServer extends AbstractLifeCycle
|
||||||
{
|
{
|
||||||
|
public static final String RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE = "com.sun.jndi.rmi.factory.socket";
|
||||||
private static final Logger LOG = Log.getLogger(ConnectorServer.class);
|
private static final Logger LOG = Log.getLogger(ConnectorServer.class);
|
||||||
|
|
||||||
private JMXServiceURL _jmxURL;
|
private JMXServiceURL _jmxURL;
|
||||||
private final Map<String, Object> _environment;
|
private final Map<String, Object> _environment;
|
||||||
private final String _objectName;
|
private final String _objectName;
|
||||||
private String _registryHost;
|
private final SslContextFactory _sslContextFactory;
|
||||||
private int _registryPort;
|
private int _registryPort;
|
||||||
private String _rmiHost;
|
|
||||||
private int _rmiPort;
|
private int _rmiPort;
|
||||||
private JMXConnectorServer _connectorServer;
|
private JMXConnectorServer _connectorServer;
|
||||||
private Registry _registry;
|
private Registry _registry;
|
||||||
|
@ -82,10 +95,16 @@ public class ConnectorServer extends AbstractLifeCycle
|
||||||
* @param name object name string to be assigned to ConnectorServer bean
|
* @param name object name string to be assigned to ConnectorServer bean
|
||||||
*/
|
*/
|
||||||
public ConnectorServer(JMXServiceURL svcUrl, Map<String, ?> environment, String name)
|
public ConnectorServer(JMXServiceURL svcUrl, Map<String, ?> environment, String name)
|
||||||
|
{
|
||||||
|
this(svcUrl, environment, name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectorServer(JMXServiceURL svcUrl, Map<String, ?> environment, String name, SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
this._jmxURL = svcUrl;
|
this._jmxURL = svcUrl;
|
||||||
this._environment = environment == null ? new HashMap<>() : new HashMap<>(environment);
|
this._environment = environment == null ? new HashMap<>() : new HashMap<>(environment);
|
||||||
this._objectName = name;
|
this._objectName = name;
|
||||||
|
this._sslContextFactory = sslContextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JMXServiceURL getAddress()
|
public JMXServiceURL getAddress()
|
||||||
|
@ -100,20 +119,29 @@ public class ConnectorServer extends AbstractLifeCycle
|
||||||
if (rmi)
|
if (rmi)
|
||||||
{
|
{
|
||||||
if (!_environment.containsKey(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE))
|
if (!_environment.containsKey(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE))
|
||||||
_environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new JMXRMIServerSocketFactory(false));
|
_environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new JMXRMIServerSocketFactory(_jmxURL.getHost(), port -> _rmiPort = port));
|
||||||
|
if (_sslContextFactory != null)
|
||||||
|
{
|
||||||
|
SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
|
||||||
|
if (!_environment.containsKey(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE))
|
||||||
|
_environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
|
||||||
|
if (!_environment.containsKey(RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE))
|
||||||
|
_environment.put(RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String urlPath = _jmxURL.getURLPath();
|
String urlPath = _jmxURL.getURLPath();
|
||||||
String jndiRMI = "/jndi/rmi://";
|
String jndiRMI = "/jndi/rmi://";
|
||||||
boolean registry = urlPath.startsWith(jndiRMI);
|
if (urlPath.startsWith(jndiRMI))
|
||||||
if (registry)
|
|
||||||
{
|
{
|
||||||
int startIndex = jndiRMI.length();
|
int startIndex = jndiRMI.length();
|
||||||
int endIndex = urlPath.indexOf('/', startIndex);
|
int endIndex = urlPath.indexOf('/', startIndex);
|
||||||
HostPort hostPort = new HostPort(urlPath.substring(startIndex, endIndex));
|
HostPort hostPort = new HostPort(urlPath.substring(startIndex, endIndex));
|
||||||
_registryHost = hostPort.getHost();
|
String registryHost = startRegistry(hostPort);
|
||||||
startRegistry(hostPort);
|
// If the RMI registry was already started, use the existing port.
|
||||||
urlPath = jndiRMI + _registryHost + ":" + _registryPort + urlPath.substring(endIndex);
|
if (_registryPort == 0)
|
||||||
|
_registryPort = hostPort.getPort();
|
||||||
|
urlPath = jndiRMI + registryHost + ":" + _registryPort + urlPath.substring(endIndex);
|
||||||
// Rebuild JMXServiceURL to use it for the creation of the JMXConnectorServer.
|
// Rebuild JMXServiceURL to use it for the creation of the JMXConnectorServer.
|
||||||
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), _jmxURL.getHost(), _jmxURL.getPort(), urlPath);
|
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), _jmxURL.getHost(), _jmxURL.getPort(), urlPath);
|
||||||
}
|
}
|
||||||
|
@ -122,14 +150,15 @@ public class ConnectorServer extends AbstractLifeCycle
|
||||||
_connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, _environment, mbeanServer);
|
_connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, _environment, mbeanServer);
|
||||||
mbeanServer.registerMBean(_connectorServer, new ObjectName(_objectName));
|
mbeanServer.registerMBean(_connectorServer, new ObjectName(_objectName));
|
||||||
_connectorServer.start();
|
_connectorServer.start();
|
||||||
|
String rmiHost = normalizeHost(_jmxURL.getHost());
|
||||||
|
// If _rmiPort is still zero, it's using the same port as the RMI registry.
|
||||||
|
if (_rmiPort == 0)
|
||||||
|
_rmiPort = _registryPort;
|
||||||
|
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), rmiHost, _rmiPort, urlPath);
|
||||||
|
|
||||||
ShutdownThread.register(0, this);
|
ShutdownThread.register(0, this);
|
||||||
|
|
||||||
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(),
|
LOG.info("JMX URL: {}", _jmxURL);
|
||||||
_rmiHost != null ? _rmiHost : _jmxURL.getHost(),
|
|
||||||
_rmiPort > 0 ? _rmiPort : _jmxURL.getPort(),
|
|
||||||
urlPath);
|
|
||||||
|
|
||||||
LOG.info("JMX Remote URL: {}", _jmxURL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -142,7 +171,7 @@ public class ConnectorServer extends AbstractLifeCycle
|
||||||
stopRegistry();
|
stopRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startRegistry(HostPort hostPort) throws Exception
|
private String startRegistry(HostPort hostPort) throws Exception
|
||||||
{
|
{
|
||||||
String host = hostPort.getHost();
|
String host = hostPort.getHost();
|
||||||
int port = hostPort.getPort(1099);
|
int port = hostPort.getPort(1099);
|
||||||
|
@ -151,14 +180,23 @@ public class ConnectorServer extends AbstractLifeCycle
|
||||||
{
|
{
|
||||||
// Check if a local registry is already running.
|
// Check if a local registry is already running.
|
||||||
LocateRegistry.getRegistry(host, port).list();
|
LocateRegistry.getRegistry(host, port).list();
|
||||||
return;
|
return normalizeHost(host);
|
||||||
}
|
}
|
||||||
catch (Throwable ex)
|
catch (Throwable ex)
|
||||||
{
|
{
|
||||||
LOG.ignore(ex);
|
LOG.ignore(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
_registry = LocateRegistry.createRegistry(port, null, new JMXRMIServerSocketFactory(true));
|
RMIClientSocketFactory csf = _sslContextFactory == null ? null : new SslRMIClientSocketFactory();
|
||||||
|
RMIServerSocketFactory ssf = new JMXRMIServerSocketFactory(host, p -> _registryPort = p);
|
||||||
|
_registry = LocateRegistry.createRegistry(port, csf, ssf);
|
||||||
|
|
||||||
|
return normalizeHost(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeHost(String host) throws UnknownHostException
|
||||||
|
{
|
||||||
|
return host == null || host.isEmpty() ? InetAddress.getLocalHost().getHostName() : host;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopRegistry()
|
private void stopRegistry()
|
||||||
|
@ -180,54 +218,56 @@ public class ConnectorServer extends AbstractLifeCycle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class JMXRMIServerSocketFactory implements RMIServerSocketFactory
|
private class JMXRMIServerSocketFactory implements RMIServerSocketFactory
|
||||||
{
|
{
|
||||||
private boolean registry;
|
private final String _host;
|
||||||
|
private final IntConsumer _portConsumer;
|
||||||
|
|
||||||
private JMXRMIServerSocketFactory(boolean registry)
|
private JMXRMIServerSocketFactory(String host, IntConsumer portConsumer)
|
||||||
{
|
{
|
||||||
this.registry = registry;
|
this._host = host;
|
||||||
|
this._portConsumer = portConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServerSocket createServerSocket(int port) throws IOException
|
public ServerSocket createServerSocket(int port) throws IOException
|
||||||
{
|
{
|
||||||
if (registry)
|
InetAddress address = _host == null || _host.isEmpty() ? null : InetAddress.getByName(_host);
|
||||||
|
ServerSocket server = createServerSocket(address, port);
|
||||||
|
_portConsumer.accept(server.getLocalPort());
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerSocket createServerSocket(InetAddress address, int port) throws IOException
|
||||||
|
{
|
||||||
|
// A null address binds to the wildcard address.
|
||||||
|
if (_sslContextFactory == null)
|
||||||
{
|
{
|
||||||
InetAddress address;
|
|
||||||
if (_registryHost == null || _registryHost.isEmpty())
|
|
||||||
{
|
|
||||||
_registryHost = InetAddress.getLocalHost().getHostName();
|
|
||||||
address = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
address = InetAddress.getByName(_registryHost);
|
|
||||||
}
|
|
||||||
ServerSocket server = new ServerSocket();
|
ServerSocket server = new ServerSocket();
|
||||||
server.bind(new InetSocketAddress(address, port));
|
server.bind(new InetSocketAddress(address, port));
|
||||||
_registryPort = server.getLocalPort();
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
InetAddress address;
|
return _sslContextFactory.newSslServerSocket(address == null ? null : address.getHostName(), port, 0);
|
||||||
_rmiHost = _jmxURL.getHost();
|
|
||||||
if (_rmiHost == null || _rmiHost.isEmpty())
|
|
||||||
{
|
|
||||||
_rmiHost = InetAddress.getLocalHost().getHostName();
|
|
||||||
address = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
address = InetAddress.getByName(_rmiHost);
|
|
||||||
}
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(new InetSocketAddress(address, port));
|
|
||||||
_rmiPort = server.getLocalPort();
|
|
||||||
return server;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return _host != null ? _host.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
JMXRMIServerSocketFactory that = (JMXRMIServerSocketFactory)obj;
|
||||||
|
return Objects.equals(_host, that._host);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,16 @@ import java.net.ConnectException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.management.remote.JMXConnector;
|
||||||
|
import javax.management.remote.JMXConnectorFactory;
|
||||||
import javax.management.remote.JMXServiceURL;
|
import javax.management.remote.JMXServiceURL;
|
||||||
|
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
@ -71,12 +78,23 @@ public class ConnectorServerTest
|
||||||
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
|
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
|
||||||
connectorServer.start();
|
connectorServer.start();
|
||||||
|
|
||||||
InetAddress localHost = InetAddress.getLocalHost();
|
// Verify that I can connect to the RMI registry using a non-loopback address.
|
||||||
if (!localHost.isLoopbackAddress())
|
new Socket(InetAddress.getLocalHost(), 1099).close();
|
||||||
{
|
// Verify that I can connect to the RMI registry using the loopback address.
|
||||||
// Verify that I can connect to the RMIRegistry using a non-loopback address.
|
new Socket(InetAddress.getLoopbackAddress(), 1099).close();
|
||||||
new Socket(localHost, 1099).close();
|
}
|
||||||
}
|
|
||||||
|
@Test
|
||||||
|
public void testNoRegistryHostNonDefaultRegistryPort() throws Exception
|
||||||
|
{
|
||||||
|
int registryPort = 1299;
|
||||||
|
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:" + registryPort + "/jmxrmi"), objectName);
|
||||||
|
connectorServer.start();
|
||||||
|
|
||||||
|
// Verify that I can connect to the RMI registry using a non-loopback address.
|
||||||
|
new Socket(InetAddress.getLocalHost(), registryPort).close();
|
||||||
|
// Verify that I can connect to the RMI registry using the loopback address.
|
||||||
|
new Socket(InetAddress.getLoopbackAddress(), registryPort).close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -85,12 +103,10 @@ public class ConnectorServerTest
|
||||||
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
|
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
|
||||||
connectorServer.start();
|
connectorServer.start();
|
||||||
|
|
||||||
InetAddress localHost = InetAddress.getLocalHost();
|
// Verify that I can connect to the RMI server using a non-loopback address.
|
||||||
if (!localHost.isLoopbackAddress())
|
new Socket(InetAddress.getLocalHost(), connectorServer.getAddress().getPort()).close();
|
||||||
{
|
// Verify that I can connect to the RMI server using the loopback address.
|
||||||
// Verify that I can connect to the RMI server using a non-loopback address.
|
new Socket(InetAddress.getLoopbackAddress(), connectorServer.getAddress().getPort()).close();
|
||||||
new Socket(localHost, connectorServer.getAddress().getPort()).close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -160,4 +176,59 @@ public class ConnectorServerTest
|
||||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
new Socket(loopback, port).close();
|
new Socket(loopback, port).close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRMIServerAndRMIRegistryOnSameHostAndSamePort() throws Exception
|
||||||
|
{
|
||||||
|
// RMI can multiplex connections on the same address and port for different
|
||||||
|
// RMI objects, in this case the RMI registry and the RMI server. In this
|
||||||
|
// case, the RMIServerSocketFactory will be invoked only once.
|
||||||
|
// The case with different address and same port is already covered by TCP,
|
||||||
|
// that can listen to 192.168.0.1:1099 and 127.0.0.1:1099 without problems.
|
||||||
|
|
||||||
|
String host = "localhost";
|
||||||
|
int port = 1399;
|
||||||
|
connectorServer = new ConnectorServer(new JMXServiceURL("rmi", host, port, "/jndi/rmi://" + host + ":" + port + "/jmxrmi"), objectName);
|
||||||
|
connectorServer.start();
|
||||||
|
|
||||||
|
JMXServiceURL address = connectorServer.getAddress();
|
||||||
|
Assert.assertEquals(port, address.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJMXOverTLS() throws Exception
|
||||||
|
{
|
||||||
|
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||||
|
String keyStorePath = MavenTestingUtils.getTestResourcePath("keystore.jks").toString();
|
||||||
|
String keyStorePassword = "storepwd";
|
||||||
|
sslContextFactory.setKeyStorePath(keyStorePath);
|
||||||
|
sslContextFactory.setKeyStorePassword(keyStorePassword);
|
||||||
|
sslContextFactory.start();
|
||||||
|
|
||||||
|
// The RMIClientSocketFactory is stored within the RMI stub.
|
||||||
|
// When using TLS, the stub is deserialized in a possibly different
|
||||||
|
// JVM that does not have access to the server keystore, and there
|
||||||
|
// is no way to provide TLS configuration during the deserialization
|
||||||
|
// of the stub. Therefore the client must provide system properties
|
||||||
|
// to specify the TLS configuration. For this test it needs the
|
||||||
|
// trustStore because the server certificate is self-signed.
|
||||||
|
// The server needs to contact the RMI registry and therefore also
|
||||||
|
// needs these system properties.
|
||||||
|
System.setProperty("javax.net.ssl.trustStore", keyStorePath);
|
||||||
|
System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword);
|
||||||
|
|
||||||
|
connectorServer = new ConnectorServer(new JMXServiceURL("rmi", null, 1100, "/jndi/rmi://localhost:1100/jmxrmi"), null, objectName, sslContextFactory);
|
||||||
|
connectorServer.start();
|
||||||
|
|
||||||
|
// The client needs to talk TLS to the RMI registry to download
|
||||||
|
// the RMI server stub, and this is independent from JMX.
|
||||||
|
// The RMI server stub then contains the SslRMIClientSocketFactory
|
||||||
|
// needed to talk to the RMI server.
|
||||||
|
Map<String, Object> clientEnv = new HashMap<>();
|
||||||
|
clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());
|
||||||
|
try (JMXConnector client = JMXConnectorFactory.connect(connectorServer.getAddress(), clientEnv))
|
||||||
|
{
|
||||||
|
client.getMBeanServerConnection().queryNames(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue