diff options
author | Bhupendra Bhusman Bhardwaj <bhupendrab@apache.org> | 2007-04-06 13:32:56 +0000 |
---|---|---|
committer | Bhupendra Bhusman Bhardwaj <bhupendrab@apache.org> | 2007-04-06 13:32:56 +0000 |
commit | 53e4b49b9d9e6dd48321cbb39d09971b56f8dea2 (patch) | |
tree | 424c97e6fcc743164133c760c8f0b58931b7c903 | |
parent | 9d4509f2acd38b2668f0dc81cc49e18ec4c7d540 (diff) | |
download | qpid-python-53e4b49b9d9e6dd48321cbb39d09971b56f8dea2.tar.gz |
QPID-444 : Enabling the Qpid to use SASL. jmxmp can be plugged into for SASL. Can be configured to use security.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2@526157 13f79535-47bb-0310-9956-ffa450edef68
9 files changed, 482 insertions, 5 deletions
diff --git a/java/broker/distribution/src/main/assembly/broker-bin.xml b/java/broker/distribution/src/main/assembly/broker-bin.xml index bc2a956c54..4b32630771 100644 --- a/java/broker/distribution/src/main/assembly/broker-bin.xml +++ b/java/broker/distribution/src/main/assembly/broker-bin.xml @@ -78,6 +78,12 @@ <fileMode>420</fileMode> </file> <file> + <source>../etc/jmxremote.access</source> + <outputDirectory>qpid-${qpid.version}/etc</outputDirectory> + <destName>jmxremote.access</destName> + <fileMode>420</fileMode> + </file> + <file> <source>../etc/log4j.xml</source> <outputDirectory>qpid-${qpid.version}/etc</outputDirectory> <destName>log4j.xml</destName> diff --git a/java/broker/etc/jmxremote.access b/java/broker/etc/jmxremote.access new file mode 100644 index 0000000000..0f17f420cf --- /dev/null +++ b/java/broker/etc/jmxremote.access @@ -0,0 +1,4 @@ +#guest=admin +guest=readonly +#user=readwrite +#admin=admin diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java index c89529f2a3..71dd9beef7 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java @@ -21,23 +21,141 @@ package org.apache.qpid.server.management; import java.lang.management.ManagementFactory; +import java.util.Map; +import java.util.HashMap; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.io.IOException; import javax.management.JMException; import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.MBeanServerForwarder; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.AuthorizeCallback; import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +/** + * This class starts up an MBeanserver. If out of the box agent is being used then there are no security features + * implemented. + * To use the security features like user authentication, turn off the jmx options in the "QPID_OPTS" env variable and + * use JMXMP connector server. If JMXMP connector is not available, then the standard JMXConnector will be used, which + * again doesn't have user authentication. + */ public class JMXManagedObjectRegistry implements ManagedObjectRegistry { private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); private final MBeanServer _mbeanServer; + private Registry _rmiRegistry; + private JMXServiceURL _jmxURL; - public JMXManagedObjectRegistry() + public JMXManagedObjectRegistry() throws AMQException { _log.info("Initialising managed object registry using platform MBean server"); - // we use the platform MBean server currently but this must be changed or at least be configuurable - _mbeanServer = ManagementFactory.getPlatformMBeanServer(); + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + + // Retrieve the config parameters + boolean platformServer = appRegistry.getConfiguration().getBoolean("management.platform-mbeanserver", true); + boolean security = appRegistry.getConfiguration().getBoolean("management.security-enabled", true); + int port = appRegistry.getConfiguration().getInt("management.jmxport", 8999); + + _mbeanServer = platformServer ? ManagementFactory.getPlatformMBeanServer() + : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN); + + // Check if the "QPID_OPTS" is set to use Out of the Box JMXAgent + if (areOutOfTheBoxJMXOptionsSet()) + { + _log.info("JMX: Using the out of the box JMX Agent"); + return; + } + + try + { + if (security) + { + // For SASL using JMXMP + _jmxURL = new JMXServiceURL("jmxmp", null, port); + + Map env = new HashMap(); + env.put("jmx.remote.profiles", "SASL/PLAIN"); + //env.put("jmx.remote.profiles", "SASL/CRAM-MD5"); + + Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases(); + Map.Entry<String, PrincipalDatabase> entry = map.entrySet().iterator().next(); + + // Callback handler used by the PLAIN SASL server mechanism to perform user authentication + /* + PlainInitialiser plainInitialiser = new PlainInitialiser(); + plainInitialiser.initialise(entry.getValue()); + env.put("jmx.remote.sasl.callback.handler", plainInitialiser.getCallbackHandler()); + */ + + env.put("jmx.remote.sasl.callback.handler", new UserCallbackHandler(entry.getValue())); + + // Enable the SSL security and server authentication + /* + SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); + SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); + env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); + env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf); + */ + + try + { + JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, env, _mbeanServer); + MBeanInvocationHandlerImpl.initialise(); + MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); + cs.setMBeanServerForwarder(mbsf); + cs.start(); + } + catch (java.net.MalformedURLException urlException) + { + // When JMXMPConnector is not available + // java.net.MalformedURLException: Unsupported protocol: jmxmp + startJMXConnectorServer(port); + } + } + else + { + startJMXConnectorServer(port); + } + } + catch (Exception ex) + { + _log.error("Error in initialising Managed Object Registry." + ex.getMessage()); + ex.printStackTrace(); + } + + } + + /** + * Starts up an RMIRegistry at configured port and attaches a JMXConnectorServer to it. + * @param port + * @throws IOException + */ + private void startJMXConnectorServer(int port) throws IOException + { + startRMIRegistry(port); + _jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi"); + JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, null, _mbeanServer); + cs.start(); } public void registerObject(ManagedObject managedObject) throws JMException @@ -50,4 +168,100 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry _mbeanServer.unregisterMBean(managedObject.getObjectName()); } + /** + * Checks is the "QPID_OPTS" env variable is set to use the out of the box JMXAgent. + * @return + */ + private boolean areOutOfTheBoxJMXOptionsSet() + { + if (System.getProperty("com.sun.management.jmxremote") != null) + { + return true; + } + if (System.getProperty("com.sun.management.jmxremote.port") != null) + { + return true; + } + + return false; + } + + /** + * Starts the rmi registry at given port + * @param port + * @throws RemoteException + */ + private void startRMIRegistry(int port) throws RemoteException + { + System.setProperty("java.rmi.server.randomIDs", "true"); + _rmiRegistry = LocateRegistry.createRegistry(port); + } + + // stops the RMIRegistry, if it was running and bound to a port + public void close() throws RemoteException + { + if (_rmiRegistry != null) + { + // Stopping the RMI registry + UnicastRemoteObject.unexportObject(_rmiRegistry, true); + } + } + + /** + * This class is used for SASL enabled JMXConnector for performing user authentication. + */ + private class UserCallbackHandler implements CallbackHandler + { + private final PrincipalDatabase _principalDatabase; + + protected UserCallbackHandler(PrincipalDatabase database) + { + _principalDatabase = database; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + // Retrieve callbacks + NameCallback ncb = null; + PasswordCallback pcb = null; + for (int i = 0; i < callbacks.length; i++) + { + if (callbacks[i] instanceof NameCallback) + { + ncb = (NameCallback) callbacks[i]; + } + else if (callbacks[i] instanceof PasswordCallback) + { + pcb = (PasswordCallback) callbacks[i]; + } + else if (callbacks[i] instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callbacks[i]).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + + boolean authorized = false; + // Process retrieval of password; can get password if username is available in NameCallback + if (ncb != null && pcb != null) + { + String username = ncb.getDefaultName(); + try + { + authorized =_principalDatabase.verifyPassword(new UsernamePrincipal(username), pcb.getPassword()); + } + catch (AccountNotFoundException e) + { + throw new IOException("User not authorized. " + e); + } + } + if (!authorized) + { + throw new IOException("User not authorized."); + } + } + } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java new file mode 100644 index 0000000000..288cc3a7cc --- /dev/null +++ b/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java @@ -0,0 +1,228 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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.qpid.server.management; + +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import javax.management.remote.MBeanServerForwarder; +import javax.management.remote.JMXPrincipal; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.JMException; +import javax.security.auth.Subject; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.Principal; +import java.security.AccessControlContext; +import java.util.Set; +import java.util.Properties; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.io.FileInputStream; + +/** + * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. + * This implements the logic for allowing the users to invoke MBean operations and implements the + * restrictions for readOnly, readWrite and admin users. + */ +public class MBeanInvocationHandlerImpl implements InvocationHandler +{ + private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class); + + private final static String ADMIN = "admin"; + private final static String READWRITE="readwrite"; + private final static String READONLY = "readonly"; + private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate"; + private static final String DEFAULT_PERMISSIONS_FILE = "etc" + File.separator + "jmxremote.access"; + private MBeanServer mbs; + private final static Properties _userRoles = new Properties(); + + public static MBeanServerForwarder newProxyInstance() + { + final InvocationHandler handler = new MBeanInvocationHandlerImpl(); + final Class[] interfaces = new Class[]{MBeanServerForwarder.class}; + + Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler); + return MBeanServerForwarder.class.cast(proxy); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + final String methodName = method.getName(); + + if (methodName.equals("getMBeanServer")) + { + return mbs; + } + + if (methodName.equals("setMBeanServer")) + { + if (args[0] == null) + { + throw new IllegalArgumentException("Null MBeanServer"); + } + if (mbs != null) + { + throw new IllegalArgumentException("MBeanServer object already initialized"); + } + mbs = (MBeanServer) args[0]; + return null; + } + + // Retrieve Subject from current AccessControlContext + AccessControlContext acc = AccessController.getContext(); + Subject subject = Subject.getSubject(acc); + + // Allow operations performed locally on behalf of the connector server itself + if (subject == null) + { + return method.invoke(mbs, args); + } + + if (args == null || DELEGATE.equals(args[0])) + { + return method.invoke(mbs, args); + } + + // Restrict access to "createMBean" and "unregisterMBean" to any user + if (methodName.equals("createMBean") || methodName.equals("unregisterMBean")) + { + throw new SecurityException("Access denied"); + } + + // Retrieve JMXPrincipal from Subject + Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class); + if (principals == null || principals.isEmpty()) + { + throw new SecurityException("Access denied"); + } + + Principal principal = principals.iterator().next(); + String identity = principal.getName(); + + // Following users can perform any operation other than "createMBean" and "unregisterMBean" + if (isAdmin(identity) || isAllowedToModify(identity)) + { + return method.invoke(mbs, args); + } + + // These users can only call "getAttribute" on the MBeanServerDelegate MBean + // Here we can add other fine grained permissions like specific method for a particular mbean + if (isReadOnlyUser(identity) && isReadOnlyMethod(method, args)) + { + return method.invoke(mbs, args); + } + + throw new SecurityException("Access denied"); + } + + // Initialises the user roles + protected static void initialise() throws AMQException + { + final String QpidHome = System.getProperty("QPID_HOME"); + String fileName = QpidHome + File.separator + DEFAULT_PERMISSIONS_FILE; + try + { + FileInputStream in = new FileInputStream(fileName); + _userRoles.load(in); + in.close(); + } + catch (IOException ex) + { + _logger.error("Error in loading JMX User permissions." + ex.getMessage()); + //throw new AMQException("Error in loading JMX User permissions", ex); + } + } + + private boolean isAdmin(String userName) + { + if (ADMIN.equals(_userRoles.getProperty(userName))) + { + return true; + } + return false; + } + + private boolean isAllowedToModify(String userName) + { + if (READWRITE.equals(_userRoles.getProperty(userName))) + { + return true; + } + return false; + } + + private boolean isReadOnlyUser(String userName) + { + if (READONLY.equals(_userRoles.getProperty(userName))) + { + return true; + } + return false; + } + + private boolean isReadOnlyMethod(Method method, Object[] args) + { + String methodName = method.getName(); + if (methodName.equals("queryMBeans") || + methodName.equals("getDefaultDomain") || + methodName.equals("getMBeanInfo") || + methodName.equals("getAttribute") || + methodName.equals("getAttributes")) + { + return true; + } + + if (args[0] instanceof ObjectName) + { + String mbeanMethod = (args.length > 1) ? (String) args[1] : null; + if (mbeanMethod == null) + return false; + + try + { + MBeanInfo mbeanInfo = mbs.getMBeanInfo((ObjectName)args[0]); + if (mbeanInfo != null) + { + MBeanOperationInfo[] opInfos = mbeanInfo.getOperations(); + for (MBeanOperationInfo opInfo : opInfos) + { + if (opInfo.getName().equals(mbeanMethod) && (opInfo.getImpact() == MBeanOperationInfo.INFO)) + { + return true; + } + } + } + } + catch (JMException ex) + { + ex.printStackTrace(); + } + } + + return false; + } +} diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java index 32298f05e3..006a535707 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java @@ -21,6 +21,7 @@ package org.apache.qpid.server.management; import javax.management.JMException; +import java.rmi.RemoteException; /** * Handles the registration (and unregistration and so on) of managed objects. @@ -39,4 +40,6 @@ public interface ManagedObjectRegistry void registerObject(ManagedObject managedObject) throws JMException; void unregisterObject(ManagedObject managedObject) throws JMException; + + void close() throws RemoteException; } diff --git a/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java index 5b86543ea6..9e3995a427 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java @@ -24,6 +24,8 @@ import javax.management.JMException; import org.apache.log4j.Logger; +import java.rmi.RemoteException; + /** * This managed object registry does not actually register MBeans. This can be used in tests when management is * not required or when management has been disabled. @@ -45,4 +47,9 @@ public class NoopManagedObjectRegistry implements ManagedObjectRegistry public void unregisterObject(ManagedObject managedObject) throws JMException { } + + public void close() throws RemoteException + { + + } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java index 14a8063aee..8806ac0516 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -168,6 +168,12 @@ public abstract class ApplicationRegistry implements IApplicationRegistry { virtualHost.close(); } + + // close the rmi registry(if any) started for management + if (getInstance().getManagedObjectRegistry() != null) + { + getInstance().getManagedObjectRegistry().close(); + } } public Configuration getConfiguration() diff --git a/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java index 739ed9db42..9e942d4d8c 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java +++ b/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java @@ -42,6 +42,7 @@ import org.apache.qpid.server.security.access.AccessManager; import org.apache.qpid.server.security.access.AccessManagerImpl; import org.apache.qpid.server.virtualhost.VirtualHost; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.AMQException; public class ConfigurationFileApplicationRegistry extends ApplicationRegistry { @@ -102,7 +103,6 @@ public class ConfigurationFileApplicationRegistry extends ApplicationRegistry public void initialise() throws Exception { - initialiseManagedObjectRegistry(); _virtualHostRegistry = new VirtualHostRegistry(); _accessManager = new AccessManagerImpl("default", _configuration); @@ -111,7 +111,10 @@ public class ConfigurationFileApplicationRegistry extends ApplicationRegistry _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null); + initialiseManagedObjectRegistry(); + initialiseVirtualHosts(); + } private void initialiseVirtualHosts() throws Exception @@ -123,7 +126,7 @@ public class ConfigurationFileApplicationRegistry extends ApplicationRegistry } } - private void initialiseManagedObjectRegistry() + private void initialiseManagedObjectRegistry() throws AMQException { ManagementConfiguration config = getConfiguredObject(ManagementConfiguration.class); if (config.enabled) diff --git a/java/distribution/src/main/assembly/bin.xml b/java/distribution/src/main/assembly/bin.xml index 88a8a68f55..7ec325b00c 100644 --- a/java/distribution/src/main/assembly/bin.xml +++ b/java/distribution/src/main/assembly/bin.xml @@ -83,6 +83,12 @@ <fileMode>420</fileMode> </file> <file> + <source>../broker/etc/jmxremote.access</source> + <outputDirectory>qpid-${qpid.version}/etc</outputDirectory> + <destName>jmxremote.access</destName> + <fileMode>420</fileMode> + </file> + <file> <source>../broker/etc/transient_config.xml</source> <outputDirectory>qpid-${qpid.version}/etc</outputDirectory> <destName>transient_config.xml</destName> |