diff options
Diffstat (limited to 'java')
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> | 
