001 /*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 *
020 */
021 package org.apache.qpid.server.management;
022
023 import org.apache.commons.configuration.ConfigurationException;
024 import org.apache.log4j.Logger;
025 import org.apache.qpid.AMQException;
026 import org.apache.qpid.server.registry.ApplicationRegistry;
027 import org.apache.qpid.server.registry.IApplicationRegistry;
028 import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase;
029 import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase;
030 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
031 import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator;
032 import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser;
033 import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser;
034
035 import javax.management.InstanceNotFoundException;
036 import javax.management.JMException;
037 import javax.management.MBeanRegistrationException;
038 import javax.management.MBeanServer;
039 import javax.management.MBeanServerFactory;
040 import javax.management.ObjectName;
041 import javax.management.remote.JMXConnectorServer;
042 import javax.management.remote.JMXConnectorServerFactory;
043 import javax.management.remote.JMXServiceURL;
044 import javax.management.remote.MBeanServerForwarder;
045 import javax.management.remote.rmi.RMIConnectorServer;
046 import javax.management.remote.rmi.RMIJRMPServerImpl;
047 import javax.management.remote.rmi.RMIServerImpl;
048 import javax.rmi.ssl.SslRMIClientSocketFactory;
049 import javax.rmi.ssl.SslRMIServerSocketFactory;
050
051 import java.io.File;
052 import java.io.FileNotFoundException;
053 import java.io.IOException;
054 import java.lang.management.ManagementFactory;
055 import java.net.InetAddress;
056 import java.net.ServerSocket;
057 import java.net.Socket;
058 import java.rmi.AlreadyBoundException;
059 import java.rmi.RemoteException;
060 import java.rmi.registry.LocateRegistry;
061 import java.rmi.registry.Registry;
062 import java.rmi.server.RMIClientSocketFactory;
063 import java.rmi.server.RMIServerSocketFactory;
064 import java.rmi.server.UnicastRemoteObject;
065 import java.util.HashMap;
066 import java.util.Map;
067
068 /**
069 * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no
070 * security features implemented like user authentication and authorisation.
071 */
072 public class JMXManagedObjectRegistry implements ManagedObjectRegistry
073 {
074 private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class);
075 private static final Logger _startupLog = Logger.getLogger("Qpid.Broker");
076
077 public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport";
078 public static final int MANAGEMENT_PORT_DEFAULT = 8999;
079 public static final int PORT_EXPORT_OFFSET = 100;
080
081 private final MBeanServer _mbeanServer;
082 private Registry _rmiRegistry;
083
084
085 public JMXManagedObjectRegistry() throws AMQException
086 {
087 _log.info("Initialising managed object registry using platform MBean server");
088 IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
089
090 // Retrieve the config parameters
091 boolean platformServer = appRegistry.getConfiguration().getPlatformMbeanserver();
092
093 _mbeanServer =
094 platformServer ? ManagementFactory.getPlatformMBeanServer()
095 : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN);
096 }
097
098
099 public void start() throws IOException, ConfigurationException
100 {
101 //check if system properties are set to use the JVM's out-of-the-box JMXAgent
102 if (areOutOfTheBoxJMXOptionsSet())
103 {
104 _log.warn("JMX: Using the out of the box JMX Agent");
105 _startupLog.warn("JMX: Using the out of the box JMX Agent");
106 return;
107 }
108
109 IApplicationRegistry appRegistry = ApplicationRegistry.getInstance();
110
111 boolean jmxmpSecurity = appRegistry.getConfiguration().getManagementSecurityEnabled();
112 int port = appRegistry.getConfiguration().getJMXManagementPort();
113
114 //retrieve the Principal Database assigned to JMX authentication duties
115 String jmxDatabaseName = appRegistry.getConfiguration().getJMXPrincipalDatabase();
116 Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases();
117 PrincipalDatabase db = map.get(jmxDatabaseName);
118
119 final JMXConnectorServer cs;
120 HashMap<String,Object> env = new HashMap<String,Object>();
121
122 if (jmxmpSecurity)
123 {
124 // For SASL using JMXMP
125 JMXServiceURL jmxURL = new JMXServiceURL("jmxmp", null, port);
126
127 String saslType = null;
128 if (db instanceof Base64MD5PasswordFilePrincipalDatabase)
129 {
130 saslType = "SASL/CRAM-MD5";
131 env.put("jmx.remote.profiles", "SASL/CRAM-MD5");
132 CRAMMD5HashedInitialiser initialiser = new CRAMMD5HashedInitialiser();
133 initialiser.initialise(db);
134 env.put("jmx.remote.sasl.callback.handler", initialiser.getCallbackHandler());
135 }
136 else if (db instanceof PlainPasswordFilePrincipalDatabase)
137 {
138 saslType = "SASL/PLAIN";
139 PlainInitialiser initialiser = new PlainInitialiser();
140 initialiser.initialise(db);
141 env.put("jmx.remote.sasl.callback.handler", initialiser.getCallbackHandler());
142 env.put("jmx.remote.profiles", "SASL/PLAIN");
143 }
144
145 //workaround NPE generated from env map classloader issue when using Eclipse 3.4 to launch
146 env.put("jmx.remote.profile.provider.class.loader", this.getClass().getClassLoader());
147
148 _log.warn("Starting JMXMP based JMX ConnectorServer on port '" + port + "' with " + saslType);
149 _startupLog.warn("Starting JMXMP based JMX ConnectorServer on port '" + port + "' with " + saslType);
150
151 cs = JMXConnectorServerFactory.newJMXConnectorServer(jmxURL, env, _mbeanServer);
152 }
153 else
154 {
155 //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration
156 RMIClientSocketFactory csf;
157 RMIServerSocketFactory ssf;
158
159 //check ssl enabled option in config, default to true if option is not set
160 boolean sslEnabled = appRegistry.getConfiguration().getManagementSSLEnabled();
161
162 if (sslEnabled)
163 {
164 //set the SSL related system properties used by the SSL RMI socket factories to the values
165 //given in the configuration file, unless command line settings have already been specified
166 String keyStorePath;
167
168 if(System.getProperty("javax.net.ssl.keyStore") != null)
169 {
170 keyStorePath = System.getProperty("javax.net.ssl.keyStore");
171 }
172 else{
173 keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath();
174 }
175
176 //check the keystore path value is valid
177 if (keyStorePath == null)
178 {
179 throw new ConfigurationException("JMX management SSL keystore path not defined, " +
180 "unable to start SSL protected JMX ConnectorServer");
181 }
182 else
183 {
184 //ensure the system property is set
185 System.setProperty("javax.net.ssl.keyStore", keyStorePath);
186
187 //check the file is usable
188 File ksf = new File(keyStorePath);
189
190 if (!ksf.exists())
191 {
192 throw new FileNotFoundException("Cannot find JMX management SSL keystore file " + ksf);
193 }
194 if (!ksf.canRead())
195 {
196 throw new FileNotFoundException("Cannot read JMX management SSL keystore file: "
197 + ksf + ". Check permissions.");
198 }
199
200 _log.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath());
201 _startupLog.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath());
202 }
203
204 //check the key store password is set
205 if (System.getProperty("javax.net.ssl.keyStorePassword") == null)
206 {
207
208 if (appRegistry.getConfiguration().getManagementKeyStorePassword() == null)
209 {
210 throw new ConfigurationException("JMX management SSL keystore password not defined, " +
211 "unable to start requested SSL protected JMX server");
212 }
213 else
214 {
215 System.setProperty("javax.net.ssl.keyStorePassword",
216 appRegistry.getConfiguration().getManagementKeyStorePassword());
217 }
218 }
219
220 //create the SSL RMI socket factories
221 csf = new SslRMIClientSocketFactory();
222 ssf = new SslRMIServerSocketFactory();
223
224 _log.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" +
225 (port +PORT_EXPORT_OFFSET) + ") with SSL");
226 _startupLog.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" +
227 (port +PORT_EXPORT_OFFSET) + ") with SSL");
228 }
229 else
230 {
231 //Do not specify any specific RMI socket factories, resulting in use of the defaults.
232 csf = null;
233 ssf = null;
234
235 _log.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")");
236 _startupLog.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")");
237 }
238
239 //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server
240 RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator();
241 rmipa.setPrincipalDatabase(db);
242 env.put(JMXConnectorServer.AUTHENTICATOR, rmipa);
243
244 /*
245 * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub.
246 * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI.
247 * As a result, only binds made using the object reference will succeed, thus securing it from external change.
248 */
249 System.setProperty("java.rmi.server.randomIDs", "true");
250 _rmiRegistry = LocateRegistry.createRegistry(port, null, new CustomRMIServerSocketFactory());
251
252 /*
253 * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls
254 * to bind the ConnectorServer to the registry, which will now fail as for security we have
255 * locked it from any RMI based modifications, including our own. Instead, we will manually bind
256 * the RMIConnectorServer stub to the registry using its object reference, which will still succeed.
257 *
258 * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer
259 * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's.
260 */
261 final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(port+PORT_EXPORT_OFFSET, csf, ssf, env);
262 final String hostname = InetAddress.getLocalHost().getHostName();
263 final JMXServiceURL externalUrl = new JMXServiceURL(
264 "service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi");
265
266 final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET);
267 cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer)
268 {
269 @Override
270 public synchronized void start() throws IOException
271 {
272 try
273 {
274 //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent
275 _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub);
276 }
277 catch (AlreadyBoundException abe)
278 {
279 //key was already in use. shouldnt happen here as its a new registry, unbindable by normal means.
280
281 //IOExceptions are the only checked type throwable by the method, wrap and rethrow
282 IOException ioe = new IOException(abe.getMessage());
283 ioe.initCause(abe);
284 throw ioe;
285 }
286
287 //now do the normal tasks
288 super.start();
289 }
290
291 @Override
292 public JMXServiceURL getAddress()
293 {
294 //must return our pre-crafted url that includes the full details, inc JNDI details
295 return externalUrl;
296 }
297
298 };
299 }
300
301 //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer.
302 MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance();
303 cs.setMBeanServerForwarder(mbsf);
304 cs.start();
305 }
306
307 /*
308 * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry.
309 * Supplied to the registry at creation, this will prevent RMI-based operations on the
310 * registry such as attempting to bind a new object, thereby securing it from tampering.
311 * This is accomplished by always returning null when attempting to determine the address
312 * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc
313 * made using the object reference will not be affected and continue to operate normally.
314 */
315
316 private class CustomRMIServerSocketFactory implements RMIServerSocketFactory
317 {
318
319 public ServerSocket createServerSocket(int port) throws IOException
320 {
321 return new NoLocalAddressServerSocket(port);
322 }
323
324 private class NoLocalAddressServerSocket extends ServerSocket
325 {
326 NoLocalAddressServerSocket(int port) throws IOException
327 {
328 super(port);
329 }
330
331 @Override
332 public Socket accept() throws IOException
333 {
334 Socket s = new NoLocalAddressSocket();
335 super.implAccept(s);
336 return s;
337 }
338 }
339
340 private class NoLocalAddressSocket extends Socket
341 {
342 @Override
343 public InetAddress getInetAddress()
344 {
345 return null;
346 }
347 }
348 }
349
350
351 public void registerObject(ManagedObject managedObject) throws JMException
352 {
353 _mbeanServer.registerMBean(managedObject, managedObject.getObjectName());
354 }
355
356 public void unregisterObject(ManagedObject managedObject) throws JMException
357 {
358 _mbeanServer.unregisterMBean(managedObject.getObjectName());
359 }
360
361 // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent.
362 private boolean areOutOfTheBoxJMXOptionsSet()
363 {
364 if (System.getProperty("com.sun.management.jmxremote") != null)
365 {
366 return true;
367 }
368
369 if (System.getProperty("com.sun.management.jmxremote.port") != null)
370 {
371 return true;
372 }
373
374 return false;
375 }
376
377 // stops the RMIRegistry, if it was running and bound to a port
378 public void close() throws RemoteException
379 {
380 if (_rmiRegistry != null)
381 {
382 // Stopping the RMI registry
383 UnicastRemoteObject.unexportObject(_rmiRegistry, true);
384 }
385 for (ObjectName name : _mbeanServer.queryNames(null, null))
386 {
387 try
388 {
389 _mbeanServer.unregisterMBean(name);
390 }
391 catch (JMException e)
392 {
393 // Really shouldn't happen, but we'll ignore that...
394 }
395 }
396 }
397
398 }
|