diff options
| author | Alex Rudyy <orudyy@apache.org> | 2013-02-19 09:35:28 +0000 |
|---|---|---|
| committer | Alex Rudyy <orudyy@apache.org> | 2013-02-19 09:35:28 +0000 |
| commit | a973713561140fe7395368ae53def8f7edfa18a3 (patch) | |
| tree | 7bda80afada592df681fb73908400e7a189f015f /qpid/java/broker-plugins | |
| parent | 1b0f1d06188e73e9440dc1789c28ee65e24d539d (diff) | |
| download | qpid-python-a973713561140fe7395368ae53def8f7edfa18a3.tar.gz | |
QPID-4390: Introduce a configuration store in java broker allowing runtime modifications and replace existing xml file configuration with json configuration store
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1447646 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker-plugins')
31 files changed, 1268 insertions, 822 deletions
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java index 44c48523e2..f87374ac80 100644 --- a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java +++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java @@ -22,8 +22,6 @@ package org.apache.qpid.server.security.access.config; import java.io.File; -import org.apache.commons.configuration.ConfigurationException; - public abstract class AbstractConfiguration implements ConfigurationFile { private File _file; @@ -39,7 +37,7 @@ public abstract class AbstractConfiguration implements ConfigurationFile return _file; } - public RuleSet load() throws ConfigurationException + public RuleSet load() { _config = new RuleSet(); return _config; diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java index 8b1a00259b..966c32e24e 100644 --- a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java +++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java @@ -22,7 +22,7 @@ package org.apache.qpid.server.security.access.config; import java.io.File; -import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.IllegalConfigurationException; public interface ConfigurationFile { @@ -33,19 +33,17 @@ public interface ConfigurationFile /** * Load this configuration file's contents into a {@link RuleSet}. - * - * @throws ConfigurationException if the configuration file has errors. + * @throws IllegalConfigurationException if the configuration file has errors. * @throws IllegalArgumentException if individual tokens cannot be parsed. */ - RuleSet load() throws ConfigurationException; + RuleSet load() throws IllegalConfigurationException; /** * Reload this configuration file's contents. - * - * @throws ConfigurationException if the configuration file has errors. + * @throws IllegalConfigurationException if the configuration file has errors. * @throws IllegalArgumentException if individual tokens cannot be parsed. */ - RuleSet reload() throws ConfigurationException; + RuleSet reload() throws IllegalConfigurationException; RuleSet getConfiguration(); diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java index 86f8ca3217..ab309c54ce 100644 --- a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java +++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java @@ -32,9 +32,9 @@ import java.util.List; import java.util.Map; import java.util.Stack; -import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.security.access.ObjectType; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.access.Permission; @@ -73,7 +73,7 @@ public class PlainConfiguration extends AbstractConfiguration } @Override - public RuleSet load() throws ConfigurationException + public RuleSet load() { RuleSet ruleSet = super.load(); @@ -127,7 +127,7 @@ public class PlainConfiguration extends AbstractConfiguration stack.removeElementAt(0); if (stack.isEmpty()) { - throw new ConfigurationException(String.format(NOT_ENOUGH_TOKENS_MSG, getLine())); + throw new IllegalConfigurationException(String.format(NOT_ENOUGH_TOKENS_MSG, getLine())); } // check for and parse optional initial number for ACL lines @@ -148,7 +148,7 @@ public class PlainConfiguration extends AbstractConfiguration { if(StringUtils.equalsIgnoreCase("GROUP", first)) { - throw new ConfigurationException(String.format("GROUP keyword not supported. Groups should defined via a Group Provider, not in the ACL file.", getLine())); + throw new IllegalConfigurationException(String.format("GROUP keyword not supported. Groups should defined via a Group Provider, not in the ACL file.", getLine())); } else if (StringUtils.equalsIgnoreCase(CONFIG, first)) { @@ -156,12 +156,12 @@ public class PlainConfiguration extends AbstractConfiguration } else { - throw new ConfigurationException(String.format(UNRECOGNISED_INITIAL_MSG, first, getLine())); + throw new IllegalConfigurationException(String.format(UNRECOGNISED_INITIAL_MSG, first, getLine())); } } else { - throw new ConfigurationException(String.format(NUMBER_NOT_ALLOWED_MSG, first, getLine())); + throw new IllegalConfigurationException(String.format(NUMBER_NOT_ALLOWED_MSG, first, getLine())); } // reset stack, start next line @@ -183,7 +183,7 @@ public class PlainConfiguration extends AbstractConfiguration } // invalid location for continuation character (add one to line beacuse we ate the EOL) - throw new ConfigurationException(String.format(PREMATURE_CONTINUATION_MSG, getLine() + 1)); + throw new IllegalConfigurationException(String.format(PREMATURE_CONTINUATION_MSG, getLine() + 1)); } else if (_st.ttype == '\'' || _st.ttype == '"') { @@ -198,20 +198,20 @@ public class PlainConfiguration extends AbstractConfiguration if (!stack.isEmpty()) { - throw new ConfigurationException(String.format(PREMATURE_EOF_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PREMATURE_EOF_MSG, getLine())); } } catch (IllegalArgumentException iae) { - throw new ConfigurationException(String.format(PARSE_TOKEN_FAILED_MSG, getLine()), iae); + throw new IllegalConfigurationException(String.format(PARSE_TOKEN_FAILED_MSG, getLine()), iae); } catch (FileNotFoundException fnfe) { - throw new ConfigurationException(String.format(CONFIG_NOT_FOUND_MSG, file.getName()), fnfe); + throw new IllegalConfigurationException(String.format(CONFIG_NOT_FOUND_MSG, file.getName()), fnfe); } catch (IOException ioe) { - throw new ConfigurationException(String.format(CANNOT_LOAD_MSG, file.getName()), ioe); + throw new IllegalConfigurationException(String.format(CANNOT_LOAD_MSG, file.getName()), ioe); } finally { @@ -223,7 +223,7 @@ public class PlainConfiguration extends AbstractConfiguration } catch (IOException e) { - throw new ConfigurationException(String.format(CANNOT_CLOSE_MSG, file.getName()), e); + throw new IllegalConfigurationException(String.format(CANNOT_CLOSE_MSG, file.getName()), e); } } } @@ -232,11 +232,11 @@ public class PlainConfiguration extends AbstractConfiguration return ruleSet; } - private void parseAcl(Integer number, List<String> args) throws ConfigurationException + private void parseAcl(Integer number, List<String> args) { if (args.size() < 3) { - throw new ConfigurationException(String.format(NOT_ENOUGH_ACL_MSG, getLine())); + throw new IllegalConfigurationException(String.format(NOT_ENOUGH_ACL_MSG, getLine())); } Permission permission = Permission.parse(args.get(0)); @@ -245,7 +245,7 @@ public class PlainConfiguration extends AbstractConfiguration if (number != null && !getConfiguration().isValidNumber(number)) { - throw new ConfigurationException(String.format(BAD_ACL_RULE_NUMBER_MSG, getLine())); + throw new IllegalConfigurationException(String.format(BAD_ACL_RULE_NUMBER_MSG, getLine())); } if (args.size() == 3) @@ -261,11 +261,11 @@ public class PlainConfiguration extends AbstractConfiguration } } - private void parseConfig(List<String> args) throws ConfigurationException + private void parseConfig(List<String> args) { if (args.size() < 3) { - throw new ConfigurationException(String.format(NOT_ENOUGH_CONFIG_MSG, getLine())); + throw new IllegalConfigurationException(String.format(NOT_ENOUGH_CONFIG_MSG, getLine())); } Map<String, Boolean> properties = toPluginProperties(args); @@ -273,7 +273,7 @@ public class PlainConfiguration extends AbstractConfiguration getConfiguration().configure(properties); } - private AclRulePredicates toRulePredicates(List<String> args) throws ConfigurationException + private AclRulePredicates toRulePredicates(List<String> args) { AclRulePredicates predicates = new AclRulePredicates(); Iterator<String> i = args.iterator(); @@ -282,15 +282,15 @@ public class PlainConfiguration extends AbstractConfiguration String key = i.next(); if (!i.hasNext()) { - throw new ConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine())); } if (!"=".equals(i.next())) { - throw new ConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine())); } if (!i.hasNext()) { - throw new ConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine())); } String value = i.next(); @@ -300,7 +300,7 @@ public class PlainConfiguration extends AbstractConfiguration } /** Converts a {@link List} of "name", "=", "value" tokens into a {@link Map}. */ - protected Map<String, Boolean> toPluginProperties(List<String> args) throws ConfigurationException + protected Map<String, Boolean> toPluginProperties(List<String> args) { Map<String, Boolean> properties = new HashMap<String, Boolean>(); Iterator<String> i = args.iterator(); @@ -309,15 +309,15 @@ public class PlainConfiguration extends AbstractConfiguration String key = i.next().toLowerCase(); if (!i.hasNext()) { - throw new ConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine())); } if (!"=".equals(i.next())) { - throw new ConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine())); } if (!i.hasNext()) { - throw new ConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine())); + throw new IllegalConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine())); } // parse property value and save diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControl.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControl.java index 19b9135ea6..6f7885da94 100644 --- a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControl.java +++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControl.java @@ -45,7 +45,7 @@ public class DefaultAccessControl implements AccessControl private RuleSet _ruleSet; - public DefaultAccessControl(String fileName) throws ConfigurationException + public DefaultAccessControl(String fileName) { if (_logger.isDebugEnabled()) { diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactory.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactory.java index 38ea61357e..a3d7823caf 100644 --- a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactory.java +++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactory.java @@ -20,28 +20,40 @@ */ package org.apache.qpid.server.security.access.plugins; -import org.apache.commons.configuration.Configuration; -import org.apache.commons.configuration.ConfigurationException; +import java.io.File; +import java.util.Map; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.plugin.AccessControlFactory; import org.apache.qpid.server.security.AccessControl; public class DefaultAccessControlFactory implements AccessControlFactory { - public AccessControl createInstance(Configuration securityConfiguration) - { - String aclConfiguration = securityConfiguration.getString("acl"); - if(aclConfiguration == null) - { - return null; - } + public static final String ATTRIBUTE_ACL_FILE = "aclFile"; - try - { - return new DefaultAccessControl(aclConfiguration); - } - catch (ConfigurationException e) + public AccessControl createInstance(Map<String, Object> aclConfiguration) + { + if (aclConfiguration != null) { - throw new RuntimeException("caught exception during instance creation", e); + Object aclFile = aclConfiguration.get(ATTRIBUTE_ACL_FILE); + if (aclFile != null) + { + if (aclFile instanceof String) + { + String aclPath = (String) aclFile; + if (!new File(aclPath).exists()) + { + throw new IllegalConfigurationException("ACL file '" + aclPath + "' is not found"); + } + return new DefaultAccessControl(aclPath); + } + else + { + throw new IllegalConfigurationException("Expected '" + ATTRIBUTE_ACL_FILE + "' attribute value of type String but was " + aclFile.getClass() + + ": " + aclFile); + } + } } + return null; } } diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java index 21d8ff4400..cbfc9003c8 100644 --- a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java +++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/config/PlainConfigurationTest.java @@ -26,7 +26,7 @@ import java.util.Map; import junit.framework.TestCase; -import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.security.access.ObjectProperties; import org.apache.qpid.server.security.access.ObjectProperties.Property; import org.apache.qpid.server.security.access.ObjectType; @@ -73,7 +73,7 @@ public class PlainConfigurationTest extends TestCase fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.CONFIG_NOT_FOUND_MSG, "doesnotexist"), ce.getMessage()); assertTrue(ce.getCause() instanceof FileNotFoundException); @@ -87,7 +87,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("ACL ALLOW ALL \\ ALL"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.PREMATURE_CONTINUATION_MSG, 1), ce.getMessage()); } @@ -100,7 +100,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("ACL unparsed ALL ALL"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.PARSE_TOKEN_FAILED_MSG, 1), ce.getMessage()); assertTrue(ce.getCause() instanceof IllegalArgumentException); @@ -115,7 +115,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("ACL ALLOW"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_ACL_MSG, 1), ce.getMessage()); } @@ -128,7 +128,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("CONFIG"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_TOKENS_MSG, 1), ce.getMessage()); } @@ -141,7 +141,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("INVALID"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_TOKENS_MSG, 1), ce.getMessage()); } @@ -154,7 +154,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("ACL ALLOW adk CREATE QUEUE name"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.PROPERTY_KEY_ONLY_MSG, 1), ce.getMessage()); } @@ -167,7 +167,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("ACL ALLOW adk CREATE QUEUE name test"); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.PROPERTY_NO_EQUALS_MSG, 1), ce.getMessage()); } @@ -180,7 +180,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("ACL ALLOW adk CREATE QUEUE name ="); fail("fail"); } - catch (ConfigurationException ce) + catch (IllegalConfigurationException ce) { assertEquals(String.format(PlainConfiguration.PROPERTY_NO_VALUE_MSG, 1), ce.getMessage()); } @@ -432,7 +432,7 @@ public class PlainConfigurationTest extends TestCase writeACLConfig("GROUP group1 bob alice"); fail("Expected exception not thrown"); } - catch(ConfigurationException e) + catch(IllegalConfigurationException e) { assertTrue(e.getMessage().contains("GROUP keyword not supported")); } diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactoryTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactoryTest.java new file mode 100644 index 0000000000..ca1f19098f --- /dev/null +++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/DefaultAccessControlFactoryTest.java @@ -0,0 +1,69 @@ +package org.apache.qpid.server.security.access.plugins; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.security.AccessControl; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; + +public class DefaultAccessControlFactoryTest extends QpidTestCase +{ + public void testCreateInstanceWhenAclFileIsNotPresent() + { + DefaultAccessControlFactory factory = new DefaultAccessControlFactory(); + Map<String, Object> attributes = new HashMap<String, Object>(); + AccessControl acl = factory.createInstance(attributes); + assertNull("ACL was created without a configuration file", acl); + } + + public void testCreateInstanceWhenAclFileIsSpecified() + { + File aclFile = TestFileUtils.createTempFile(this, ".acl", "ACL ALLOW all all"); + DefaultAccessControlFactory factory = new DefaultAccessControlFactory(); + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(DefaultAccessControlFactory.ATTRIBUTE_ACL_FILE, aclFile.getAbsolutePath()); + AccessControl acl = factory.createInstance(attributes); + + assertNotNull("ACL was not created from acl file: " + aclFile.getAbsolutePath(), acl); + } + + public void testCreateInstanceWhenAclFileIsSpecifiedButDoesNotExist() + { + File aclFile = new File(TMP_FOLDER, "my-non-existing-acl-" + System.currentTimeMillis()); + assertFalse("ACL file " + aclFile.getAbsolutePath() + " actually exists but should not", aclFile.exists()); + DefaultAccessControlFactory factory = new DefaultAccessControlFactory(); + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(DefaultAccessControlFactory.ATTRIBUTE_ACL_FILE, aclFile.getAbsolutePath()); + try + { + factory.createInstance(attributes); + fail("It should not be possible to create ACL from non existing file"); + } + catch (IllegalConfigurationException e) + { + assertTrue("Unexpected exception message", Pattern.matches("ACL file '.*' is not found", e.getMessage())); + } + } + + public void testCreateInstanceWhenAclFileIsSpecifiedAsNonString() + { + DefaultAccessControlFactory factory = new DefaultAccessControlFactory(); + Map<String, Object> attributes = new HashMap<String, Object>(); + Integer aclFile = new Integer(0); + attributes.put(DefaultAccessControlFactory.ATTRIBUTE_ACL_FILE, aclFile); + try + { + factory.createInstance(attributes); + fail("It should not be possible to create ACL from Integer"); + } + catch (IllegalConfigurationException e) + { + assertEquals("Unexpected exception message", "Expected '" + DefaultAccessControlFactory.ATTRIBUTE_ACL_FILE + + "' attribute value of type String but was " + Integer.class + ": " + aclFile, e.getMessage()); + } + } +} diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java index f10b308e24..59dbc6e530 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java @@ -21,15 +21,20 @@ package org.apache.qpid.server.management.plugin; import java.io.File; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; -import org.apache.commons.configuration.ConfigurationException; import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; import org.apache.qpid.server.management.plugin.servlet.DefinedFileServlet; import org.apache.qpid.server.management.plugin.servlet.FileServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.AbstractServlet; import org.apache.qpid.server.management.plugin.servlet.rest.LogRecordsServlet; import org.apache.qpid.server.management.plugin.servlet.rest.LogoutServlet; import org.apache.qpid.server.management.plugin.servlet.rest.MessageContentServlet; @@ -46,13 +51,18 @@ import org.apache.qpid.server.model.Exchange; import org.apache.qpid.server.model.Group; import org.apache.qpid.server.model.GroupMember; import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.Session; -import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.User; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; +import org.apache.qpid.server.plugin.PluginFactory; +import org.apache.qpid.server.util.MapValueConverter; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.SessionManager; @@ -62,98 +72,186 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.ssl.SslContextFactory; -public class HttpManagement implements ManagementPlugin +public class HttpManagement extends AbstractPluginAdapter { private final Logger _logger = Logger.getLogger(HttpManagement.class); + // 10 minutes by default + public static final int DEFAULT_TIMEOUT_IN_SECONDS = 60 * 10; + public static final boolean DEFAULT_HTTP_BASIC_AUTHENTICATION_ENABLED = false; + public static final boolean DEFAULT_HTTPS_BASIC_AUTHENTICATION_ENABLED = true; + public static final boolean DEFAULT_HTTP_SASL_AUTHENTICATION_ENABLED = true; + public static final boolean DEFAULT_HTTPS_SASL_AUTHENTICATION_ENABLED = true; + public static final String DEFAULT_NAME = "httpManagement"; + + public static final String TIME_OUT = "sessionTimeout"; + public static final String HTTP_BASIC_AUTHENTICATION_ENABLED = "httpBasicAuthenticationEnabled"; + public static final String HTTPS_BASIC_AUTHENTICATION_ENABLED = "httpsBasicAuthenticationEnabled"; + public static final String HTTP_SASL_AUTHENTICATION_ENABLED = "httpSaslAuthenticationEnabled"; + public static final String HTTPS_SASL_AUTHENTICATION_ENABLED = "httpsSaslAuthenticationEnabled"; + + public static final String PLUGIN_TYPE = "MANAGEMENT-HTTP"; + + @SuppressWarnings("serial") + private static final Collection<String> AVAILABLE_ATTRIBUTES = Collections.unmodifiableSet(new HashSet<String>(Plugin.AVAILABLE_ATTRIBUTES) + {{ + add(HTTP_BASIC_AUTHENTICATION_ENABLED); + add(HTTPS_BASIC_AUTHENTICATION_ENABLED); + add(HTTP_SASL_AUTHENTICATION_ENABLED); + add(HTTPS_SASL_AUTHENTICATION_ENABLED); + add(TIME_OUT); + add(PluginFactory.PLUGIN_TYPE); + }}); + public static final String ENTRY_POINT_PATH = "/management"; private static final String OPERATIONAL_LOGGING_NAME = "Web"; - private final Broker _broker; - private final Collection<Server> _servers = new ArrayList<Server>(); + @SuppressWarnings("serial") + public static final Map<String, Object> DEFAULTS = Collections.unmodifiableMap(new HashMap<String, Object>() + {{ + put(HTTP_BASIC_AUTHENTICATION_ENABLED, DEFAULT_HTTP_BASIC_AUTHENTICATION_ENABLED); + put(HTTPS_BASIC_AUTHENTICATION_ENABLED, DEFAULT_HTTPS_BASIC_AUTHENTICATION_ENABLED); + put(HTTP_SASL_AUTHENTICATION_ENABLED, DEFAULT_HTTP_SASL_AUTHENTICATION_ENABLED); + put(HTTPS_SASL_AUTHENTICATION_ENABLED, DEFAULT_HTTPS_SASL_AUTHENTICATION_ENABLED); + put(TIME_OUT, DEFAULT_TIMEOUT_IN_SECONDS); + put(NAME, DEFAULT_NAME); + }}); + + @SuppressWarnings("serial") + private static final Map<String, Class<?>> ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap<String, Class<?>>(){{ + put(HTTP_BASIC_AUTHENTICATION_ENABLED, Boolean.class); + put(HTTPS_BASIC_AUTHENTICATION_ENABLED, Boolean.class); + put(HTTP_SASL_AUTHENTICATION_ENABLED, Boolean.class); + put(HTTPS_SASL_AUTHENTICATION_ENABLED, Boolean.class); + put(NAME, String.class); + put(TIME_OUT, Integer.class); + put(PluginFactory.PLUGIN_TYPE, String.class); + }}); + + private final Broker _broker; - private final String _keyStorePassword; - private final String _keyStorePath; - private final int _sessionTimeout; + private Server _server; - public HttpManagement(Broker broker, String keyStorePath, String keyStorePassword, int sessionTimeout) throws ConfigurationException + public HttpManagement(UUID id, Broker broker, Map<String, Object> attributes) { + super(id, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), broker.getTaskExecutor()); _broker = broker; - _keyStorePassword = keyStorePassword; - _keyStorePath = keyStorePath; - _sessionTimeout = sessionTimeout; + addParent(Broker.class, broker); + } - Collection<Port> ports = broker.getPorts(); - int httpPort = -1, httpsPort = -1; - for (Port port : ports) + @Override + protected boolean setState(State currentState, State desiredState) + { + if(desiredState == State.ACTIVE) { - if (port.getProtocols().contains(Protocol.HTTP)) - { - if (port.getTransports().contains(Transport.TCP)) - { - httpPort = port.getPort(); - } - } - if (port.getProtocols().contains(Protocol.HTTPS)) - { - if (port.getTransports().contains(Transport.SSL)) - { - httpsPort = port.getPort(); - } - } + start(); + return true; + } + else if(desiredState == State.STOPPED) + { + stop(); + return true; } + return false; + } + + private void start() + { + CurrentActor.get().message(ManagementConsoleMessages.STARTUP(OPERATIONAL_LOGGING_NAME)); - if (httpPort != -1 || httpsPort != -1) + Collection<Port> httpPorts = getHttpPorts(_broker.getPorts()); + _server = createServer(httpPorts); + try { - _servers.add(createServer(httpPort, httpsPort)); - if (_logger.isDebugEnabled()) - { - _logger.debug(_servers.size() + " server(s) defined"); - } + _server.start(); + logOperationalListenMessages(_server); } - else + catch (Exception e) { - if (_logger.isInfoEnabled()) + throw new RuntimeException("Failed to start http management on ports " + httpPorts); + } + + CurrentActor.get().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); + } + + private void stop() + { + if (_server != null) + { + try { - _logger.info("Cannot create web server as neither HTTP nor HTTPS port specified"); + _server.stop(); + logOperationalShutdownMessage(_server); + } + catch (Exception e) + { + throw new RuntimeException("Failed to stop http management on port " + getHttpPorts(_broker.getPorts())); } } + + CurrentActor.get().message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); + } + + /** Added for testing purposes */ + Broker getBroker() + { + return _broker; + } + + /** Added for testing purposes */ + int getSessionTimeout() + { + return (Integer)getAttribute(TIME_OUT); + } + + private boolean isManagementHttp(Port port) + { + return port.getProtocols().contains(Protocol.HTTP) || port.getProtocols().contains(Protocol.HTTPS); } @SuppressWarnings("unchecked") - private Server createServer(int port, int sslPort) throws ConfigurationException + private Server createServer(Collection<Port> ports) { if (_logger.isInfoEnabled()) { - _logger.info("Starting up web server on" + (port == -1 ? "" : " HTTP port " + port) - + (sslPort == -1 ? "" : " HTTPS port " + sslPort)); + _logger.info("Starting up web server on " + ports); } Server server = new Server(); - - if (port != -1) + for (Port port : ports) { - SelectChannelConnector connector = new SelectChannelConnector(); - connector.setPort(port); - if (sslPort != -1) + final Collection<Protocol> protocols = port.getProtocols(); + Connector connector = null; + + //TODO: what to do if protocol HTTP and transport SSL? + if (protocols.contains(Protocol.HTTP)) { - connector.setConfidentialPort(sslPort); + connector = new SelectChannelConnector(); } - server.addConnector(connector); - } - - if (sslPort != -1) - { - checkKeyStorePath(_keyStorePath); + else if (protocols.contains(Protocol.HTTPS)) + { + KeyStore keyStore = _broker.getDefaultKeyStore(); + if (keyStore == null) + { + throw new IllegalConfigurationException("Key store is not configured. Cannot start management on HTTPS port without keystore"); + } + String keyStorePath = (String)keyStore.getAttribute(KeyStore.PATH); + String keyStorePassword = keyStore.getPassword(); + validateKeystoreParameters(keyStorePath, keyStorePassword); - SslContextFactory factory = new SslContextFactory(); - factory.setKeyStorePath(_keyStorePath); - factory.setKeyStorePassword(_keyStorePassword); + SslContextFactory factory = new SslContextFactory(); + factory.setKeyStorePath(keyStorePath); + factory.setKeyStorePassword(keyStorePassword); - SslSocketConnector connector = new SslSocketConnector(factory); - connector.setPort(sslPort); + connector = new SslSocketConnector(factory); + } + else + { + throw new IllegalArgumentException("Unexpected protocol " + protocols); + } + connector.setPort(port.getPort()); server.addConnector(connector); } @@ -161,6 +259,10 @@ public class HttpManagement implements ManagementPlugin root.setContextPath("/"); server.setHandler(root); + // set servlet context attributes for broker and configuration + root.getServletContext().setAttribute(AbstractServlet.ATTR_BROKER, _broker); + root.getServletContext().setAttribute(AbstractServlet.ATTR_MANAGEMENT, this); + addRestServlet(root, "broker"); addRestServlet(root, "virtualhost", VirtualHost.class); addRestServlet(root, "authenticationprovider", AuthenticationProvider.class); @@ -175,13 +277,13 @@ public class HttpManagement implements ManagementPlugin addRestServlet(root, "port", Port.class); addRestServlet(root, "session", VirtualHost.class, Connection.class, Session.class); - root.addServlet(new ServletHolder(new StructureServlet(_broker)), "/rest/structure"); - root.addServlet(new ServletHolder(new MessageServlet(_broker)), "/rest/message/*"); - root.addServlet(new ServletHolder(new MessageContentServlet(_broker)), "/rest/message-content/*"); + root.addServlet(new ServletHolder(new StructureServlet()), "/rest/structure"); + root.addServlet(new ServletHolder(new MessageServlet()), "/rest/message/*"); + root.addServlet(new ServletHolder(new MessageContentServlet()), "/rest/message-content/*"); - root.addServlet(new ServletHolder(new LogRecordsServlet(_broker)), "/rest/logrecords"); + root.addServlet(new ServletHolder(new LogRecordsServlet()), "/rest/logrecords"); - root.addServlet(new ServletHolder(new SaslServlet(_broker)), "/rest/sasl"); + root.addServlet(new ServletHolder(new SaslServlet()), "/rest/sasl"); root.addServlet(new ServletHolder(new DefinedFileServlet("index.html")), ENTRY_POINT_PATH); root.addServlet(new ServletHolder(new LogoutServlet()), "/logout"); @@ -199,61 +301,34 @@ public class HttpManagement implements ManagementPlugin final SessionManager sessionManager = root.getSessionHandler().getSessionManager(); - sessionManager.setMaxInactiveInterval(_sessionTimeout); + sessionManager.setMaxInactiveInterval((Integer)getAttribute(TIME_OUT)); return server; } private void addRestServlet(ServletContextHandler root, String name, Class<? extends ConfiguredObject>... hierarchy) { - root.addServlet(new ServletHolder(new RestServlet(_broker, hierarchy)), "/rest/" + name + "/*"); + root.addServlet(new ServletHolder(new RestServlet(hierarchy)), "/rest/" + name + "/*"); } - @Override - public void start() throws Exception + private void validateKeystoreParameters(String keyStorePath, String password) { - CurrentActor.get().message(ManagementConsoleMessages.STARTUP(OPERATIONAL_LOGGING_NAME)); - - for (Server server : _servers) + if (keyStorePath == null) { - server.start(); - - logOperationalListenMessages(server); + throw new RuntimeException("Management SSL keystore path not defined, unable to start SSL protected HTTP connector"); } - - CurrentActor.get().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); - } - - @Override - public void stop() throws Exception - { - for (Server server : _servers) + if (password == null) { - logOperationalShutdownMessage(server); - - server.stop(); + throw new RuntimeException("Management SSL keystore password, unable to start SSL protected HTTP connector"); } - - CurrentActor.get().message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); - } - - private void checkKeyStorePath(String keyStorePath) throws ConfigurationException - { - if (keyStorePath == null) + File ksf = new File(keyStorePath); + if (!ksf.exists()) { - throw new ConfigurationException("Management SSL keystore path not defined, unable to start SSL protected HTTP connector"); + throw new RuntimeException("Cannot find management SSL keystore file: " + ksf); } - else + if (!ksf.canRead()) { - File ksf = new File(keyStorePath); - if (!ksf.exists()) - { - throw new ConfigurationException("Cannot find management SSL keystore file: " + ksf); - } - if (!ksf.canRead()) - { - throw new ConfigurationException("Cannot read management SSL keystore file: " + ksf + ". Check permissions."); - } + throw new RuntimeException("Cannot read management SSL keystore file: " + ksf + ". Check permissions."); } } @@ -288,27 +363,50 @@ public class HttpManagement implements ManagementPlugin return connector instanceof SslSocketConnector ? "HTTPS" : "HTTP"; } - /** Added for testing purposes */ - Broker getBroker() + private Collection<Port> getHttpPorts(Collection<Port> ports) { - return _broker; + Collection<Port> httpPorts = new HashSet<Port>(); + for (Port port : ports) + { + if (isManagementHttp(port)) + { + httpPorts.add(port); + } + } + return httpPorts; } - /** Added for testing purposes */ - String getKeyStorePassword() + + @Override + public String getName() { - return _keyStorePassword; + return (String)getAttribute(NAME); } - /** Added for testing purposes */ - String getKeyStorePath() + @Override + public Collection<String> getAttributeNames() { - return _keyStorePath; + return Collections.unmodifiableCollection(AVAILABLE_ATTRIBUTES); } - /** Added for testing purposes */ - int getSessionTimeout() + public boolean isHttpsSaslAuthenticationEnabled() { - return _sessionTimeout; + return (Boolean)getAttribute(HTTPS_SASL_AUTHENTICATION_ENABLED); } + + public boolean isHttpSaslAuthenticationEnabled() + { + return (Boolean)getAttribute(HTTP_SASL_AUTHENTICATION_ENABLED); + } + + public boolean isHttpsBasicAuthenticationEnabled() + { + return (Boolean)getAttribute(HTTPS_BASIC_AUTHENTICATION_ENABLED); + } + + public boolean isHttpBasicAuthenticationEnabled() + { + return (Boolean)getAttribute(HTTP_BASIC_AUTHENTICATION_ENABLED); + } + } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementFactory.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementFactory.java index 452d52d598..ccf5373234 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementFactory.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementFactory.java @@ -18,37 +18,24 @@ */ package org.apache.qpid.server.management.plugin; -import org.apache.commons.configuration.ConfigurationException; -import org.apache.log4j.Logger; -import org.apache.qpid.server.configuration.ServerConfiguration; +import java.util.Map; +import java.util.UUID; + import org.apache.qpid.server.model.Broker; -import org.apache.qpid.server.plugin.ManagementFactory; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.plugin.PluginFactory; -public class HttpManagementFactory implements ManagementFactory +public class HttpManagementFactory implements PluginFactory { - private static final Logger LOGGER = Logger.getLogger(HttpManagementFactory.class); @Override - public HttpManagement createInstance(ServerConfiguration configuration, Broker broker) + public Plugin createInstance(UUID id, Map<String, Object> attributes, Broker broker) { - - if (!configuration.getHTTPManagementEnabled() && !configuration.getHTTPSManagementEnabled()) + if (!HttpManagement.PLUGIN_TYPE.equals(attributes.get(PLUGIN_TYPE))) { - LOGGER.info("HttpManagement is disabled"); return null; } - try - { - return new HttpManagement( - broker, - configuration.getManagementKeyStorePath(), - configuration.getManagementKeyStorePassword(), - configuration.getHTTPManagementSessionTimeout()); - } - catch (ConfigurationException e) - { - throw new RuntimeException(e); - } + return new HttpManagement(id, broker, attributes); } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java index 2d15b8a1d9..689bdb50d8 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java @@ -28,6 +28,8 @@ import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import javax.security.auth.Subject; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -40,9 +42,9 @@ import org.apache.qpid.server.logging.LogActor; import org.apache.qpid.server.logging.RootMessageLogger; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.HttpManagementActor; +import org.apache.qpid.server.management.plugin.HttpManagement; import org.apache.qpid.server.management.plugin.session.LoginLogoutReporter; import org.apache.qpid.server.model.Broker; -import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; @@ -53,25 +55,38 @@ public abstract class AbstractServlet extends HttpServlet { private static final Logger LOGGER = Logger.getLogger(AbstractServlet.class); + /** + * Servlet context attribute holding a reference to a broker instance + */ + public static final String ATTR_BROKER = "Qpid.broker"; + + /** + * Servlet context attribute holding a reference to plugin configuration + */ + public static final String ATTR_MANAGEMENT = "Qpid.management"; + private static final String ATTR_LOGIN_LOGOUT_REPORTER = "AbstractServlet.loginLogoutReporter"; private static final String ATTR_SUBJECT = "AbstractServlet.subject"; private static final String ATTR_LOG_ACTOR = "AbstractServlet.logActor"; - private final Broker _broker; - + private Broker _broker; private RootMessageLogger _rootLogger; + private HttpManagement _httpManagement; protected AbstractServlet() { super(); - _broker = ApplicationRegistry.getInstance().getBroker(); - _rootLogger = ApplicationRegistry.getInstance().getRootMessageLogger(); } - protected AbstractServlet(Broker broker) + @Override + public void init() throws ServletException { - _broker = broker; - _rootLogger = ApplicationRegistry.getInstance().getRootMessageLogger(); + ServletConfig servletConfig = getServletConfig(); + ServletContext servletContext = servletConfig.getServletContext(); + _broker = (Broker)servletContext.getAttribute(ATTR_BROKER); + _rootLogger = _broker.getRootMessageLogger(); + _httpManagement = (HttpManagement)servletContext.getAttribute(ATTR_MANAGEMENT); + super.init(); } @Override @@ -263,7 +278,7 @@ public abstract class AbstractServlet extends HttpServlet return subject; } - SubjectCreator subjectCreator = ApplicationRegistry.getInstance().getSubjectCreator(getSocketAddress(request)); + SubjectCreator subjectCreator = getSubjectCreator(request); subject = authenticate(request, subjectCreator); if (subject != null) { @@ -293,7 +308,7 @@ public abstract class AbstractServlet extends HttpServlet @Override public Void run() throws Exception { - boolean allowed = ApplicationRegistry.getInstance().getSecurityManager().accessManagement(); + boolean allowed = getSecurityManager().accessManagement(); if (!allowed) { throw new AccessControlException("User is not authorised for management"); @@ -382,8 +397,8 @@ public abstract class AbstractServlet extends HttpServlet private boolean isBasicAuthSupported(HttpServletRequest req) { - return req.isSecure() ? ApplicationRegistry.getInstance().getConfiguration().getHTTPSManagementBasicAuth() - : ApplicationRegistry.getInstance().getConfiguration().getHTTPManagementBasicAuth(); + return req.isSecure() ? _httpManagement.isHttpsBasicAuthenticationEnabled() + : _httpManagement.isHttpBasicAuthenticationEnabled(); } private HttpManagementActor getLogActorAndCacheInSession(HttpServletRequest req) @@ -441,5 +456,18 @@ public abstract class AbstractServlet extends HttpServlet return new HttpManagementActor(_rootLogger, request.getRemoteAddr(), request.getRemotePort()); } + protected HttpManagement getManagement() + { + return _httpManagement; + } + protected SecurityManager getSecurityManager() + { + return _broker.getSecurityManager(); + } + + protected SubjectCreator getSubjectCreator(HttpServletRequest request) + { + return _broker.getSubjectCreator(getSocketAddress(request)); + } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java index 04eda2a787..f2cf5d7734 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java @@ -26,8 +26,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.qpid.server.logging.LogRecorder; -import org.apache.qpid.server.model.Broker; -import org.apache.qpid.server.registry.ApplicationRegistry; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; @@ -35,12 +33,7 @@ public class LogRecordsServlet extends AbstractServlet { public LogRecordsServlet() { - super(ApplicationRegistry.getInstance().getBroker()); - } - - public LogRecordsServlet(Broker broker) - { - super(broker); + super(); } @Override @@ -53,10 +46,10 @@ public class LogRecordsServlet extends AbstractServlet response.setHeader("Pragma","no-cache"); response.setDateHeader ("Expires", 0); - ApplicationRegistry applicationRegistry = (ApplicationRegistry) ApplicationRegistry.getInstance(); List<Map<String,Object>> logRecords = new ArrayList<Map<String, Object>>(); - for(LogRecorder.Record record : applicationRegistry.getLogRecorder()) + LogRecorder logRecorder = getBroker().getLogRecorder(); + for(LogRecorder.Record record : logRecorder) { logRecords.add(logRecordToObject(record)); } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java index ae794472bf..d61c48bb2c 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java @@ -29,7 +29,6 @@ import javax.servlet.http.HttpServletResponse; import org.apache.qpid.server.message.MessageReference; import org.apache.qpid.server.message.ServerMessage; -import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.queue.QueueEntry; @@ -42,11 +41,6 @@ public class MessageContentServlet extends AbstractServlet super(); } - public MessageContentServlet(Broker broker) - { - super(broker); - } - @Override protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java index 4bbb43be70..49e0c2b1bf 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java @@ -34,13 +34,10 @@ import org.apache.log4j.Logger; import org.apache.qpid.server.message.AMQMessageHeader; import org.apache.qpid.server.message.MessageReference; import org.apache.qpid.server.message.ServerMessage; -import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.queue.QueueEntryVisitor; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.registry.IApplicationRegistry; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.subscription.Subscription; @@ -56,11 +53,6 @@ public class MessageServlet extends AbstractServlet super(); } - public MessageServlet(Broker broker) - { - super(broker); - } - @Override protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { @@ -422,7 +414,7 @@ public class MessageServlet extends AbstractServlet // FIXME: added temporary authorization check until we introduce management layer // and review current ACL rules to have common rules for all management interfaces String methodName = isMoveTransaction? "moveMessages":"copyMessages"; - if (isQueueUpdateMethodAuthorized(methodName, vhost.getName())) + if (isQueueUpdateMethodAuthorized(methodName, vhost)) { final Queue destinationQueue = getQueueFromVirtualHost(destQueueName, vhost); final List messageIds = new ArrayList((List) providedObject.get("messages")); @@ -466,7 +458,7 @@ public class MessageServlet extends AbstractServlet // FIXME: added temporary authorization check until we introduce management layer // and review current ACL rules to have common rules for all management interfaces - if (isQueueUpdateMethodAuthorized("deleteMessages", vhost.getName())) + if (isQueueUpdateMethodAuthorized("deleteMessages", vhost)) { vhost.executeTransaction(new DeleteTransaction(sourceQueue, messageIds)); response.setStatus(HttpServletResponse.SC_OK); @@ -478,25 +470,10 @@ public class MessageServlet extends AbstractServlet } - private boolean isQueueUpdateMethodAuthorized(String methodName, String virtualHost) + private boolean isQueueUpdateMethodAuthorized(String methodName, VirtualHost host) { - SecurityManager securityManager = getSecurityManager(virtualHost); + SecurityManager securityManager = host.getSecurityManager(); return securityManager.authoriseMethod(Operation.UPDATE, "VirtualHost.Queue", methodName); } - private SecurityManager getSecurityManager(String virtualHost) - { - IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); - SecurityManager security; - if (virtualHost == null) - { - security = appRegistry.getSecurityManager(); - } - else - { - security = appRegistry.getVirtualHostRegistry().getVirtualHost(virtualHost).getSecurityManager(); - } - return security; - } - } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java index 203fa66ff9..3fab26cde5 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -47,29 +47,29 @@ public class RestServlet extends AbstractServlet private Class<? extends ConfiguredObject>[] _hierarchy; - private volatile boolean initializationRequired = false; - private final ConfiguredObjectToMapConverter _objectConverter = new ConfiguredObjectToMapConverter(); + private final boolean _hierarchyInitializationRequired; public RestServlet() { super(); - initializationRequired = true; + _hierarchyInitializationRequired = true; } - public RestServlet(Broker broker, Class<? extends ConfiguredObject>... hierarchy) + public RestServlet(Class<? extends ConfiguredObject>... hierarchy) { - super(broker); + super(); _hierarchy = hierarchy; + _hierarchyInitializationRequired = false; } @Override public void init() throws ServletException { - if (initializationRequired) + super.init(); + if (_hierarchyInitializationRequired) { doInitialization(); - initializationRequired = false; } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java index f8c8b52023..069132af1e 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java @@ -25,8 +25,7 @@ import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; import org.apache.log4j.Logger; -import org.apache.qpid.server.model.Broker; -import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.management.plugin.HttpManagement; import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; @@ -48,6 +47,7 @@ import java.util.Random; public class SaslServlet extends AbstractServlet { + private static final Logger LOGGER = Logger.getLogger(SaslServlet.class); private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @@ -57,17 +57,11 @@ public class SaslServlet extends AbstractServlet private static final String ATTR_EXPIRY = "SaslServlet.Expiry"; private static final long SASL_EXCHANGE_EXPIRY = 1000L; - public SaslServlet() { super(); } - public SaslServlet(Broker broker) - { - super(broker); - } - protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException @@ -82,7 +76,7 @@ public class SaslServlet extends AbstractServlet HttpSession session = request.getSession(); getRandom(session); - SubjectCreator subjectCreator = ApplicationRegistry.getInstance().getSubjectCreator(getSocketAddress(request)); + SubjectCreator subjectCreator = getSubjectCreator(request); String[] mechanisms = subjectCreator.getMechanisms().split(" "); Map<String, Object> outputObject = new LinkedHashMap<String, Object>(); @@ -140,7 +134,7 @@ public class SaslServlet extends AbstractServlet String id = request.getParameter("id"); String saslResponse = request.getParameter("response"); - SubjectCreator subjectCreator = ApplicationRegistry.getInstance().getSubjectCreator(getSocketAddress(request)); + SubjectCreator subjectCreator = getSubjectCreator(request); if(mechanism != null) { @@ -202,13 +196,14 @@ public class SaslServlet extends AbstractServlet private void checkSaslAuthEnabled(HttpServletRequest request) { boolean saslAuthEnabled; + HttpManagement management = getManagement(); if (request.isSecure()) { - saslAuthEnabled = ApplicationRegistry.getInstance().getConfiguration().getHTTPSManagementSaslAuthEnabled(); + saslAuthEnabled = management.isHttpsSaslAuthenticationEnabled(); } else { - saslAuthEnabled = ApplicationRegistry.getInstance().getConfiguration().getHTTPManagementSaslAuthEnabled(); + saslAuthEnabled = management.isHttpSaslAuthenticationEnabled(); } if (!saslAuthEnabled) diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java index 5f553beb26..40d3c02768 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java @@ -41,11 +41,6 @@ public class StructureServlet extends AbstractServlet super(); } - public StructureServlet(Broker broker) - { - super(broker); - } - @Override protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { diff --git a/qpid/java/broker-plugins/management-http/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ManagementFactory b/qpid/java/broker-plugins/management-http/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PluginFactory index 7ffb9a9013..7ffb9a9013 100644 --- a/qpid/java/broker-plugins/management-http/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ManagementFactory +++ b/qpid/java/broker-plugins/management-http/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PluginFactory diff --git a/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementFactoryTest.java b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementFactoryTest.java index fedd9b88d8..bb4c46826c 100644 --- a/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementFactoryTest.java +++ b/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/HttpManagementFactoryTest.java @@ -19,64 +19,42 @@ package org.apache.qpid.server.management.plugin; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import org.apache.qpid.server.configuration.ServerConfiguration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.plugin.PluginFactory; import org.apache.qpid.test.utils.QpidTestCase; public class HttpManagementFactoryTest extends QpidTestCase { - private static final String KEY_STORE_PASSWORD = "keyStorePassword"; - private static final String KEY_STORE_PATH = "keyStorePath"; private static final int SESSION_TIMEOUT = 3600; - private HttpManagementFactory _managementFactory = new HttpManagementFactory(); - private ServerConfiguration _configuration = mock(ServerConfiguration.class); + private PluginFactory _pluginFactory = new HttpManagementFactory(); + private Map<String, Object> _attributes = new HashMap<String, Object>(); private Broker _broker = mock(Broker.class); + private UUID _id = UUID.randomUUID(); - public void testNoHttpManagementConfigured() throws Exception + public void testCreateInstanceReturnsNullWhenPluginTypeMissing() throws Exception { - ManagementPlugin management = _managementFactory.createInstance(_configuration, _broker); - assertNull(management); + assertNull(_pluginFactory.createInstance(_id, _attributes, _broker)); } - - public void testHttpTransportConfigured() throws Exception + public void testCreateInstanceReturnsNullWhenPluginTypeNotHttp() { - when(_configuration.getHTTPManagementEnabled()).thenReturn(true); - when(_configuration.getHTTPSManagementEnabled()).thenReturn(false); - - when(_configuration.getManagementKeyStorePassword()).thenReturn(null); - when(_configuration.getManagementKeyStorePath()).thenReturn(null); - - when(_configuration.getHTTPManagementSessionTimeout()).thenReturn(SESSION_TIMEOUT); - - HttpManagement management = _managementFactory.createInstance(_configuration, _broker); - - assertNotNull(management); - assertEquals(_broker, management.getBroker()); - assertNull(management.getKeyStorePassword()); - assertNull(management.getKeyStorePath()); - assertEquals(SESSION_TIMEOUT, management.getSessionTimeout()); - + _attributes.put(PluginFactory.PLUGIN_TYPE, "notHttp"); + assertNull(_pluginFactory.createInstance(_id, _attributes, _broker)); } - public void testHttpsTransportConfigured() throws Exception + public void testCreateInstance() throws Exception { - when(_configuration.getHTTPManagementEnabled()).thenReturn(false); - when(_configuration.getHTTPSManagementEnabled()).thenReturn(true); - - when(_configuration.getManagementKeyStorePassword()).thenReturn(KEY_STORE_PASSWORD); - when(_configuration.getManagementKeyStorePath()).thenReturn(KEY_STORE_PATH); - - when(_configuration.getHTTPManagementSessionTimeout()).thenReturn(SESSION_TIMEOUT); + _attributes.put(PluginFactory.PLUGIN_TYPE, HttpManagement.PLUGIN_TYPE); + _attributes.put(HttpManagement.TIME_OUT, SESSION_TIMEOUT); - HttpManagement management = _managementFactory.createInstance(_configuration, _broker); + HttpManagement management = (HttpManagement) _pluginFactory.createInstance(_id, _attributes, _broker); - assertNotNull(management); assertEquals(_broker, management.getBroker()); - assertEquals(KEY_STORE_PASSWORD, management.getKeyStorePassword()); - assertEquals(KEY_STORE_PATH, management.getKeyStorePath()); assertEquals(SESSION_TIMEOUT, management.getSessionTimeout()); } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/CustomRMIServerSocketFactory.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/CustomRMIServerSocketFactory.java new file mode 100644 index 0000000000..b7aab78e45 --- /dev/null +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/CustomRMIServerSocketFactory.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jmx; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.server.RMIServerSocketFactory; + +/** + * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry. + * Supplied to the registry at creation, this will prevent RMI-based operations on the + * registry such as attempting to bind a new object, thereby securing it from tampering. + * This is accomplished by always returning null when attempting to determine the address + * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc + * made using the object reference will not be affected and continue to operate normally. + */ +class CustomRMIServerSocketFactory implements RMIServerSocketFactory +{ + + public ServerSocket createServerSocket(int port) throws IOException + { + return new NoLocalAddressServerSocket(port); + } + + private static class NoLocalAddressServerSocket extends ServerSocket + { + NoLocalAddressServerSocket(int port) throws IOException + { + super(port); + } + + @Override + public Socket accept() throws IOException + { + Socket s = new NoLocalAddressSocket(); + super.implAccept(s); + return s; + } + } + + private static class NoLocalAddressSocket extends Socket + { + @Override + public InetAddress getInetAddress() + { + return null; + } + } +}
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java index cf7a83a54e..a045683de1 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java @@ -20,56 +20,44 @@ */ package org.apache.qpid.server.jmx; -import org.apache.commons.configuration.ConfigurationException; import org.apache.log4j.Logger; -import org.apache.qpid.AMQException; -import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; - -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator; + import javax.management.JMException; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; -import javax.management.Notification; -import javax.management.NotificationFilterSupport; -import javax.management.NotificationListener; import javax.management.ObjectName; -import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXServiceURL; import javax.management.remote.MBeanServerForwarder; -import javax.management.remote.rmi.RMIConnection; import javax.management.remote.rmi.RMIConnectorServer; -import javax.management.remote.rmi.RMIJRMPServerImpl; -import javax.management.remote.rmi.RMIServerImpl; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; -import javax.security.auth.Subject; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.management.ManagementFactory; -import java.lang.reflect.Proxy; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; import java.net.UnknownHostException; import java.rmi.AlreadyBoundException; import java.rmi.NoSuchObjectException; import java.rmi.NotBoundException; +import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no @@ -82,34 +70,32 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry private static final String OPERATIONAL_LOGGING_NAME = "JMX"; private final MBeanServer _mbeanServer; + private JMXConnectorServer _cs; private Registry _rmiRegistry; - private boolean _useCustomSocketFactory; - private final int _jmxPortRegistryServer; - private final int _jmxPortConnectorServer; + private final Broker _broker; + private final Port _registryPort; + private final Port _connectorPort; - private final ServerConfiguration _serverConfiguration; - - public JMXManagedObjectRegistry(ServerConfiguration serverConfiguration) throws AMQException + public JMXManagedObjectRegistry( + Broker broker, + Port connectorPort, Port registryPort, + JMXManagement jmxManagement) { - _log.info("Initialising managed object registry using platform MBean server"); - - _serverConfiguration = serverConfiguration; + _broker = broker; + _registryPort = registryPort; + _connectorPort = connectorPort; - // Retrieve the config parameters - _useCustomSocketFactory = _serverConfiguration.getUseCustomRMISocketFactory(); - boolean platformServer = _serverConfiguration.getPlatformMbeanserver(); + boolean usePlatformServer = (Boolean)jmxManagement.getAttribute(JMXManagement.USE_PLATFORM_MBEAN_SERVER); _mbeanServer = - platformServer ? ManagementFactory.getPlatformMBeanServer() + usePlatformServer ? ManagementFactory.getPlatformMBeanServer() : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN); + } - _jmxPortRegistryServer = _serverConfiguration.getJMXPortRegistryServer(); - _jmxPortConnectorServer = _serverConfiguration.getJMXConnectorServerPort(); - } - - public void start() throws IOException, ConfigurationException + @Override + public void start() throws IOException { CurrentActor.get().message(ManagementConsoleMessages.STARTUP(OPERATIONAL_LOGGING_NAME)); @@ -117,59 +103,31 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry if (areOutOfTheBoxJMXOptionsSet()) { CurrentActor.get().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); - return; } + else + { + startRegistryAndConnector(); + } + } - //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration + private void startRegistryAndConnector() throws IOException + { + //Socket factories for the RMIConnectorServer, either default or SSL depending on configuration RMIClientSocketFactory csf; RMIServerSocketFactory ssf; - //check ssl enabled option in config, default to true if option is not set - boolean sslEnabled = _serverConfiguration.getManagementSSLEnabled(); + //check ssl enabled option on connector port (note we don't provide ssl for registry server at + //moment). + boolean connectorSslEnabled = _connectorPort.getTransports().contains(Transport.SSL); - if (sslEnabled) + if (connectorSslEnabled) { - //set the SSL related system properties used by the SSL RMI socket factories to the values - //given in the configuration file - String keyStorePath = _serverConfiguration.getManagementKeyStorePath(); + String keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); - //check the keystore path value is valid - if (keyStorePath == null) - { - throw new ConfigurationException("JMX management SSL keystore path not defined, " + - "unable to start SSL protected JMX ConnectorServer"); - } - else - { - //ensure the system property is set (for use by SslRMIClientSocketFactory and SslRMIServerSocketFactory) - System.setProperty("javax.net.ssl.keyStore", keyStorePath); + validateKeyStoreProperties(keyStorePath, keyStorePassword); - //check the file is usable - File ksf = new File(keyStorePath); - - if (!ksf.exists()) - { - throw new FileNotFoundException("Cannot find JMX management SSL keystore file: " + ksf); - } - if (!ksf.canRead()) - { - throw new FileNotFoundException("Cannot read JMX management SSL keystore file: " - + ksf + ". Check permissions."); - } - - CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(ksf.getAbsolutePath())); - } - - if (_serverConfiguration.getManagementKeyStorePassword() == null) - { - throw new ConfigurationException("JMX management SSL keystore password not defined, " + - "unable to start requested SSL protected JMX server"); - } - else - { - System.setProperty("javax.net.ssl.keyStorePassword", - _serverConfiguration.getManagementKeyStorePassword()); - } + CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(keyStorePath)); //create the SSL RMI socket factories csf = new SslRMIClientSocketFactory(); @@ -182,29 +140,23 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry ssf = null; } + int jmxPortRegistryServer = _registryPort.getPort(); + int jmxPortConnectorServer = _connectorPort.getPort(); + //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server - RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator(ApplicationRegistry.getInstance(), new InetSocketAddress(_jmxPortRegistryServer)); - HashMap<String,Object> env = new HashMap<String,Object>(); - env.put(JMXConnectorServer.AUTHENTICATOR, rmipa); + RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator(_broker, new InetSocketAddress(jmxPortConnectorServer)); + HashMap<String,Object> connectorEnv = new HashMap<String,Object>(); + connectorEnv.put(JMXConnectorServer.AUTHENTICATOR, rmipa); + + System.setProperty("java.rmi.server.randomIDs", "true"); + boolean useCustomSocketFactory = Boolean.parseBoolean(System.getProperty(BrokerProperties.PROPERTY_USE_CUSTOM_RMI_SOCKET_FACTORY, Boolean.TRUE.toString())); /* * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub. * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI. * As a result, only binds made using the object reference will succeed, thus securing it from external change. */ - System.setProperty("java.rmi.server.randomIDs", "true"); - if(_useCustomSocketFactory) - { - _log.debug("Using custom RMIServerSocketFactory"); - _rmiRegistry = LocateRegistry.createRegistry(_jmxPortRegistryServer, null, new CustomRMIServerSocketFactory()); - } - else - { - _log.debug("Using default RMIServerSocketFactory"); - _rmiRegistry = LocateRegistry.createRegistry(_jmxPortRegistryServer, null, null); - } - - CurrentActor.get().message(ManagementConsoleMessages.LISTENING("RMI Registry", _jmxPortRegistryServer)); + _rmiRegistry = createRmiRegistry(jmxPortRegistryServer, useCustomSocketFactory); /* * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls @@ -212,57 +164,16 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry * locked it from any RMI based modifications, including our own. Instead, we will manually bind * the RMIConnectorServer stub to the registry using its object reference, which will still succeed. * - * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer - * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's. + * The registry is exported on the defined management port 'port'. */ - final Map<String, String> connectionIdUsernameMap = new ConcurrentHashMap<String, String>(); - final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(_jmxPortConnectorServer, csf, ssf, env) - { - - /** - * Override makeClient so we can cache the username of the client in a Map keyed by connectionId. - * ConnectionId is guaranteed to be unique per client connection, according to the JMX spec. - * An instance of NotificationListener (mapCleanupListener) will be responsible for removing these Map - * entries. - * - * @see javax.management.remote.rmi.RMIJRMPServerImpl#makeClient(String, javax.security.auth.Subject) - */ - @Override - protected RMIConnection makeClient(String connectionId, Subject subject) throws IOException - { - final RMIConnection makeClient = super.makeClient(connectionId, subject); - final AuthenticatedPrincipal authenticatedPrincipalFromSubject = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); - connectionIdUsernameMap.put(connectionId, authenticatedPrincipalFromSubject.getName()); - return makeClient; - } - }; - - // Create a Listener responsible for removing the map entries add by the #makeClient entry above. - final NotificationListener mapCleanupListener = new NotificationListener() - { + final UsernameCachingRMIJRMPServer usernameCachingRmiServer = new UsernameCachingRMIJRMPServer(jmxPortConnectorServer, csf, ssf, connectorEnv); - public void handleNotification(Notification notification, Object handback) - { - final String connectionId = ((JMXConnectionNotification) notification).getConnectionId(); - connectionIdUsernameMap.remove(connectionId); - } - }; - - String localHost; - try - { - localHost = InetAddress.getLocalHost().getHostName(); - } - catch(UnknownHostException ex) - { - localHost="127.0.0.1"; - } - final String hostname = localHost; + final String localHostName = getLocalhost(); final JMXServiceURL externalUrl = new JMXServiceURL( - "service:jmx:rmi://"+hostname+":"+(_jmxPortConnectorServer)+"/jndi/rmi://"+hostname+":"+_jmxPortRegistryServer+"/jmxrmi"); + "service:jmx:rmi://"+localHostName+":"+(jmxPortConnectorServer)+"/jndi/rmi://"+localHostName+":"+jmxPortRegistryServer+"/jmxrmi"); - final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, _jmxPortConnectorServer); - _cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) + final JMXServiceURL internalUrl = new JMXServiceURL("rmi", localHostName, jmxPortConnectorServer); + _cs = new RMIConnectorServer(internalUrl, connectorEnv, usernameCachingRmiServer, _mbeanServer) { @Override public synchronized void start() throws IOException @@ -270,7 +181,7 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry try { //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent - _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub); + _rmiRegistry.bind("jmxrmi", usernameCachingRmiServer); } catch (AlreadyBoundException abe) { @@ -298,7 +209,6 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry } catch (NotBoundException nbe) { - // TODO consider if we want to keep new logging _log.error("Failed to unbind jmxrmi", nbe); //ignore } @@ -313,97 +223,100 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry //must return our pre-crafted url that includes the full details, inc JNDI details return externalUrl; } - }; - //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer. - MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); + MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(_broker); _cs.setMBeanServerForwarder(mbsf); + // Install a ManagementLogonLogoffReporter so we can report as users logon/logoff + ManagementLogonLogoffReporter jmxManagementUserLogonLogoffReporter = new ManagementLogonLogoffReporter(_broker.getRootMessageLogger(), usernameCachingRmiServer); + _cs.addNotificationListener(jmxManagementUserLogonLogoffReporter, jmxManagementUserLogonLogoffReporter, null); - // Get the handler that is used by the above MBInvocationHandler Proxy. - // which is the MBeanInvocationHandlerImpl and so also a NotificationListener. - final NotificationListener invocationHandler = (NotificationListener) Proxy.getInvocationHandler(mbsf); - - // Install a notification listener on OPENED, CLOSED, and FAILED, - // passing the map of connection-ids to usernames as hand-back data. - final NotificationFilterSupport invocationHandlerFilter = new NotificationFilterSupport(); - invocationHandlerFilter.enableType(JMXConnectionNotification.OPENED); - invocationHandlerFilter.enableType(JMXConnectionNotification.CLOSED); - invocationHandlerFilter.enableType(JMXConnectionNotification.FAILED); - _cs.addNotificationListener(invocationHandler, invocationHandlerFilter, connectionIdUsernameMap); - - // Install a second notification listener on CLOSED AND FAILED only to remove the entry from the - // Map. Here we rely on the fact that JMX will call the listeners in the order in which they are - // installed. - final NotificationFilterSupport mapCleanupHandlerFilter = new NotificationFilterSupport(); - mapCleanupHandlerFilter.enableType(JMXConnectionNotification.CLOSED); - mapCleanupHandlerFilter.enableType(JMXConnectionNotification.FAILED); - _cs.addNotificationListener(mapCleanupListener, mapCleanupHandlerFilter, null); + // Install the usernameCachingRmiServer as a listener so it may cleanup as clients disconnect + _cs.addNotificationListener(usernameCachingRmiServer, usernameCachingRmiServer, null); _cs.start(); - String connectorServer = (sslEnabled ? "SSL " : "") + "JMX RMIConnectorServer"; - CurrentActor.get().message(ManagementConsoleMessages.LISTENING(connectorServer, _jmxPortConnectorServer)); - + String connectorServer = (connectorSslEnabled ? "SSL " : "") + "JMX RMIConnectorServer"; + CurrentActor.get().message(ManagementConsoleMessages.LISTENING(connectorServer, jmxPortConnectorServer)); CurrentActor.get().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); } - /* - * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry. - * Supplied to the registry at creation, this will prevent RMI-based operations on the - * registry such as attempting to bind a new object, thereby securing it from tampering. - * This is accomplished by always returning null when attempting to determine the address - * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc - * made using the object reference will not be affected and continue to operate normally. - */ - - private static class CustomRMIServerSocketFactory implements RMIServerSocketFactory + private Registry createRmiRegistry(int jmxPortRegistryServer, boolean useCustomRmiRegistry) + throws RemoteException { - - public ServerSocket createServerSocket(int port) throws IOException + Registry rmiRegistry; + if(useCustomRmiRegistry) { - return new NoLocalAddressServerSocket(port); + _log.debug("Using custom RMIServerSocketFactory"); + rmiRegistry = LocateRegistry.createRegistry(jmxPortRegistryServer, null, new CustomRMIServerSocketFactory()); } - - private static class NoLocalAddressServerSocket extends ServerSocket + else { - NoLocalAddressServerSocket(int port) throws IOException - { - super(port); - } + _log.debug("Using default RMIServerSocketFactory"); + rmiRegistry = LocateRegistry.createRegistry(jmxPortRegistryServer, null, null); + } - @Override - public Socket accept() throws IOException - { - Socket s = new NoLocalAddressSocket(); - super.implAccept(s); - return s; - } + CurrentActor.get().message(ManagementConsoleMessages.LISTENING("RMI Registry", jmxPortRegistryServer)); + return rmiRegistry; + } + + private void validateKeyStoreProperties(String keyStorePath, String keyStorePassword) throws FileNotFoundException + { + if (keyStorePath == null) + { + throw new IllegalConfigurationException("JVM system property 'javax.net.ssl.keyStore' is not set, " + + "unable to start requested SSL protected JMX connector"); + } + if (keyStorePassword == null) + { + throw new IllegalConfigurationException( "JVM system property 'javax.net.ssl.keyStorePassword' is not set, " + + "unable to start requested SSL protected JMX connector"); } - private static class NoLocalAddressSocket extends Socket + File ksf = new File(keyStorePath); + if (!ksf.exists()) { - @Override - public InetAddress getInetAddress() - { - return null; - } + throw new FileNotFoundException("Cannot find JMX management SSL keystore file: " + ksf); + } + if (!ksf.canRead()) + { + throw new FileNotFoundException("Cannot read JMX management SSL keystore file: " + + ksf + ". Check permissions."); } } - + @Override public void registerObject(ManagedObject managedObject) throws JMException { _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); } + @Override public void unregisterObject(ManagedObject managedObject) throws JMException { _mbeanServer.unregisterMBean(managedObject.getObjectName()); } + @Override + public void close() + { + _log.debug("close() called"); + + closeConnectorAndRegistryServers(); + + unregisterAllMbeans(); + + CurrentActor.get().message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); + } + + private void closeConnectorAndRegistryServers() + { + closeConnectorServer(); + closeRegistryServer(); + } + // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent. private boolean areOutOfTheBoxJMXOptionsSet() { @@ -420,29 +333,26 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry return false; } - //Stops the JMXConnectorServer and RMIRegistry, then unregisters any remaining MBeans from the MBeanServer - public void close() + private String getLocalhost() { - _log.debug("close() called"); - - if (_cs != null) + String localHost; + try { - // Stopping the JMX ConnectorServer - try - { - CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("JMX RMIConnectorServer", _cs.getAddress().getPort())); - _cs.stop(); - } - catch (IOException e) - { - _log.error("Exception while closing the JMX ConnectorServer: ", e); - } + localHost = InetAddress.getLocalHost().getHostName(); } + catch(UnknownHostException ex) + { + localHost="127.0.0.1"; + } + return localHost; + } + private void closeRegistryServer() + { if (_rmiRegistry != null) { // Stopping the RMI registry - CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("RMI Registry", _jmxPortRegistryServer)); + CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("RMI Registry", _registryPort.getPort())); try { boolean success = UnicastRemoteObject.unexportObject(_rmiRegistry, false); @@ -455,8 +365,36 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry { _log.error("Exception while closing the RMI Registry: ", e); } + finally + { + _rmiRegistry = null; + } } + } + private void closeConnectorServer() + { + if (_cs != null) + { + // Stopping the JMX ConnectorServer + try + { + CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("JMX RMIConnectorServer", _cs.getAddress().getPort())); + _cs.stop(); + } + catch (IOException e) + { + _log.error("Exception while closing the JMX ConnectorServer: ", e); + } + finally + { + _cs = null; + } + } + } + + private void unregisterAllMbeans() + { //ObjectName query to gather all Qpid related MBeans ObjectName mbeanNameQuery = null; try @@ -479,8 +417,6 @@ public class JMXManagedObjectRegistry implements ManagedObjectRegistry _log.error("Exception unregistering MBean '"+ name +"': " + e.getMessage()); } } - - CurrentActor.get().message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); } } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java index f529793118..f307f5118a 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java @@ -21,81 +21,214 @@ package org.apache.qpid.server.jmx; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.UUID; + import javax.management.JMException; -import javax.management.StandardMBean; import org.apache.log4j.Logger; -import org.apache.qpid.server.configuration.ServerConfiguration; import org.apache.qpid.server.jmx.mbeans.LoggingManagementMBean; import org.apache.qpid.server.jmx.mbeans.UserManagementMBean; import org.apache.qpid.server.jmx.mbeans.ServerInformationMBean; import org.apache.qpid.server.jmx.mbeans.Shutdown; import org.apache.qpid.server.jmx.mbeans.VirtualHostMBean; import org.apache.qpid.server.logging.log4j.LoggingManagementFacade; -import org.apache.qpid.server.management.plugin.ManagementPlugin; +import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfigurationChangeListener; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; +import org.apache.qpid.server.plugin.PluginFactory; import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.util.MapValueConverter; -public class JMXManagement implements ConfigurationChangeListener, ManagementPlugin +public class JMXManagement extends AbstractPluginAdapter implements ConfigurationChangeListener { private static final Logger LOGGER = Logger.getLogger(JMXManagement.class); + public static final String PLUGIN_TYPE = "MANAGEMENT-JMX"; + + // attributes + public static final String USE_PLATFORM_MBEAN_SERVER = "usePlatformMBeanServer"; + public static final String NAME = "name"; + + // default values + public static final String DEFAULT_NAME = "JMXManagement"; + public static final boolean DEFAULT_USE_PLATFORM_MBEAN_SERVER = true; + + @SuppressWarnings("serial") + private static final Collection<String> AVAILABLE_ATTRIBUTES = Collections.unmodifiableCollection(new HashSet<String>(Plugin.AVAILABLE_ATTRIBUTES){{ + add(NAME); + add(USE_PLATFORM_MBEAN_SERVER); + add(PluginFactory.PLUGIN_TYPE); + }}); + + @SuppressWarnings("serial") + private static final Map<String, Object> DEFAULTS = new HashMap<String, Object>(){{ + put(USE_PLATFORM_MBEAN_SERVER, DEFAULT_USE_PLATFORM_MBEAN_SERVER); + put(NAME, DEFAULT_NAME); + put(PluginFactory.PLUGIN_TYPE, PLUGIN_TYPE); + }}; + + @SuppressWarnings("serial") + private static final Map<String, Class<?>> ATTRIBUTE_TYPES = new HashMap<String, Class<?>>(){{ + put(USE_PLATFORM_MBEAN_SERVER, Boolean.class); + put(NAME, String.class); + put(PluginFactory.PLUGIN_TYPE, String.class); + }}; + private final Broker _broker; private JMXManagedObjectRegistry _objectRegistry; private final Map<ConfiguredObject, AMQManagedObject> _children = new HashMap<ConfiguredObject, AMQManagedObject>(); - private final ServerConfiguration _serverConfiguration; - - public JMXManagement(ServerConfiguration serverConfiguration, Broker broker) + public JMXManagement(UUID id, Broker broker, Map<String, Object> attributes) { + super(id, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), broker.getTaskExecutor()); _broker = broker; - _serverConfiguration = serverConfiguration; + addParent(Broker.class, broker); } @Override - public void start() throws Exception + protected boolean setState(State currentState, State desiredState) { - _objectRegistry = new JMXManagedObjectRegistry(_serverConfiguration); + if(desiredState == State.ACTIVE) + { + try + { + start(); + } + catch (JMException e) + { + throw new RuntimeException("Couldn't start JMX management", e); + } + catch (IOException e) + { + throw new RuntimeException("Couldn't start JMX management", e); + } + return true; + } + else if(desiredState == State.STOPPED) + { + stop(); + return true; + } + return false; + } + + private void start() throws JMException, IOException + { + Port connectorPort = null; + Port registryPort = null; + Collection<Port> ports = _broker.getPorts(); + for (Port port : ports) + { + if(isRegistryPort(port)) + { + registryPort = port; + } + else if(isConnectorPort(port)) + { + connectorPort = port; + } + } + if(connectorPort == null) + { + throw new IllegalStateException("No JMX connector port found supporting protocol " + Protocol.JMX_RMI); + } + if(registryPort == null) + { + throw new IllegalStateException("No JMX RMI port found supporting protocol " + Protocol.RMI); + } + + _objectRegistry = new JMXManagedObjectRegistry(_broker, connectorPort, registryPort, this); _broker.addChangeListener(this); + synchronized (_children) { for(VirtualHost virtualHost : _broker.getVirtualHosts()) { if(!_children.containsKey(virtualHost)) { - _children.put(virtualHost, new VirtualHostMBean(virtualHost, _objectRegistry)); + LOGGER.debug("Create MBean for virtual host:" + virtualHost.getName()); + VirtualHostMBean mbean = new VirtualHostMBean(virtualHost, _objectRegistry); + LOGGER.debug("Check for additional MBeans for virtual host:" + virtualHost.getName()); + createAdditionalMBeansFromProviders(virtualHost, mbean); + } + } + Collection<AuthenticationProvider> authenticationProviders = _broker.getAuthenticationProviders(); + for (AuthenticationProvider authenticationProvider : authenticationProviders) + { + if(authenticationProvider instanceof PasswordCredentialManagingAuthenticationProvider) + { + UserManagementMBean mbean = new UserManagementMBean( + (PasswordCredentialManagingAuthenticationProvider) authenticationProvider, + _objectRegistry); + _children.put(authenticationProvider, mbean); } } } new Shutdown(_objectRegistry); new ServerInformationMBean(_objectRegistry, _broker); new LoggingManagementMBean(LoggingManagementFacade.getCurrentInstance(), _objectRegistry); - _objectRegistry.start(); } - @Override - public void stop() + private boolean isConnectorPort(Port port) { - _broker.removeChangeListener(this); + return port.getProtocols().contains(Protocol.JMX_RMI); + } + private boolean isRegistryPort(Port port) + { + return port.getProtocols().contains(Protocol.RMI); + } + + private void stop() + { + synchronized (_children) + { + for(ConfiguredObject object : _children.keySet()) + { + AMQManagedObject mbean = _children.get(object); + if (mbean instanceof ConfigurationChangeListener) + { + object.removeChangeListener((ConfigurationChangeListener)mbean); + } + try + { + mbean.unregister(); + } + catch (JMException e) + { + LOGGER.error("Error unregistering mbean", e); + } + } + _children.clear(); + } + _broker.removeChangeListener(this); _objectRegistry.close(); } + @Override public void stateChanged(ConfiguredObject object, State oldState, State newState) { - + // no-op } + @Override public void childAdded(ConfiguredObject object, ConfiguredObject child) { synchronized (_children) @@ -130,7 +263,7 @@ public class JMXManagement implements ConfigurationChangeListener, ManagementPlu } } - + @Override public void childRemoved(ConfiguredObject object, ConfiguredObject child) { // TODO - implement vhost removal (possibly just removing the instanceof check below) @@ -157,6 +290,12 @@ public class JMXManagement implements ConfigurationChangeListener, ManagementPlu } } + @Override + public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + // no-op + } + private void createAdditionalMBeansFromProviders(ConfiguredObject child, AMQManagedObject mbean) throws JMException { _children.put(child, mbean); @@ -168,7 +307,7 @@ public class JMXManagement implements ConfigurationChangeListener, ManagementPlu if (provider.isChildManageableByMBean(child)) { LOGGER.debug("Provider will create mbean "); - StandardMBean bean = provider.createMBean(child, mbean); + provider.createMBean(child, mbean); // TODO track the mbeans that have been created on behalf of a child in a map, then // if the child is ever removed, destroy these beans too. } @@ -181,9 +320,16 @@ public class JMXManagement implements ConfigurationChangeListener, ManagementPlu return _broker; } - /** Added for testing purposes */ - ServerConfiguration getServerConfiguration() + @Override + public String getName() { - return _serverConfiguration; + return (String)getAttribute(NAME); } + + @Override + public Collection<String> getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagementFactory.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagementFactory.java index 12d4711595..c2186c372b 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagementFactory.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagementFactory.java @@ -18,27 +18,30 @@ */ package org.apache.qpid.server.jmx; +import java.util.Map; +import java.util.UUID; + import org.apache.log4j.Logger; -import org.apache.qpid.server.configuration.ServerConfiguration; import org.apache.qpid.server.model.Broker; -import org.apache.qpid.server.plugin.ManagementFactory; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.plugin.PluginFactory; -public class JMXManagementFactory implements ManagementFactory +public class JMXManagementFactory implements PluginFactory { - private static final Logger _logger = Logger.getLogger(JMXManagementFactory.class); + private static final Logger LOGGER = Logger.getLogger(JMXManagementFactory.class); @Override - public JMXManagement createInstance(ServerConfiguration serverConfiguration, Broker broker) + public Plugin createInstance(UUID id, Map<String, Object> attributes, Broker broker) { - if (serverConfiguration.getJMXManagementEnabled()) + if (JMXManagement.PLUGIN_TYPE.equals(attributes.get(PLUGIN_TYPE))) { - return new JMXManagement(serverConfiguration, broker); + return new JMXManagement(id, broker, attributes); } else { - if(_logger.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { - _logger.debug("Skipping registration of JMX plugin as JMX Management disabled in config."); + LOGGER.debug("Skipping registration of JMX plugin as JMX Management disabled in config."); } return null; } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java index 694b0b2913..8bc2afb176 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java @@ -22,12 +22,11 @@ package org.apache.qpid.server.jmx; import org.apache.log4j.Logger; -import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.configuration.BrokerProperties; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.ManagementActor; -import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; @@ -37,11 +36,8 @@ import javax.management.JMException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanServer; -import javax.management.Notification; -import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.RuntimeErrorException; -import javax.management.remote.JMXConnectionNotification; import javax.management.remote.MBeanServerForwarder; import javax.security.auth.Subject; import java.lang.reflect.InvocationHandler; @@ -51,26 +47,32 @@ import java.lang.reflect.Proxy; import java.security.AccessControlContext; import java.security.AccessController; import java.util.Arrays; -import java.util.Map; /** * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. It delegates * JMX access decisions to the SecurityPlugin. */ -public class MBeanInvocationHandlerImpl implements InvocationHandler, NotificationListener +public class MBeanInvocationHandlerImpl implements InvocationHandler { private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class); - private final IApplicationRegistry _appRegistry = ApplicationRegistry.getInstance(); private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate"; private MBeanServer _mbs; - private final ManagementActor _logActor = new ManagementActor(_appRegistry.getRootMessageLogger()); - private final boolean _managementRightsInferAllAccess = - _appRegistry.getConfiguration().getManagementRightsInferAllAccess(); + private final ManagementActor _logActor; - public static MBeanServerForwarder newProxyInstance() + private final boolean _managementRightsInferAllAccess; + private final Broker _broker; + + MBeanInvocationHandlerImpl(Broker broker) + { + _managementRightsInferAllAccess = Boolean.valueOf(System.getProperty(BrokerProperties.PROPERTY_MANAGEMENT_RIGHTS_INFER_ALL_ACCESS, "true")); + _broker = broker; + _logActor = new ManagementActor(broker.getRootMessageLogger()); + } + + public static MBeanServerForwarder newProxyInstance(Broker broker) { - final InvocationHandler handler = new MBeanInvocationHandlerImpl(); + final InvocationHandler handler = new MBeanInvocationHandlerImpl(broker); final Class<?>[] interfaces = new Class[] { MBeanServerForwarder.class }; Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler); @@ -212,11 +214,16 @@ public class MBeanInvocationHandlerImpl implements InvocationHandler, Notificati SecurityManager security; if (vhost == null) { - security = _appRegistry.getSecurityManager(); + security = _broker.getSecurityManager(); } else { - security = _appRegistry.getVirtualHostRegistry().getVirtualHost(vhost).getSecurityManager(); + VirtualHost virtualHost = _broker.findVirtualHostByName(vhost); + if (virtualHost == null) + { + throw new IllegalArgumentException("Virtual host with name '" + vhost + "' is not found."); + } + security = virtualHost.getSecurityManager(); } methodName = getMethodName(method, args); @@ -361,51 +368,5 @@ public class MBeanInvocationHandlerImpl implements InvocationHandler, Notificati return (methodName.startsWith("query") || methodName.startsWith("get") || methodName.startsWith("is")); } - /** - * Receives notifications from the MBeanServer. - */ - public void handleNotification(final Notification notification, final Object handback) - { - assert notification instanceof JMXConnectionNotification; - - final String connectionId = ((JMXConnectionNotification) notification).getConnectionId(); - final String type = notification.getType(); - - if (_logger.isDebugEnabled()) - { - _logger.debug("Notification connectionId : " + connectionId + " type : " + type - + " Notification handback : " + handback); - } - - // Normally JMXManagedObjectRegistry provides a Map as handback data containing a map - // between connection id and username. - String user = null; - if (handback instanceof Map) - { - @SuppressWarnings("unchecked") - final Map<String, String> connectionIdUsernameMap = (Map<String, String>) handback; - user = connectionIdUsernameMap.get(connectionId); - } - - // If user is still null, fallback to an unordered list of Principals from the connection id. - if (user == null) - { - final String[] splitConnectionId = connectionId.split(" "); - user = splitConnectionId[1]; - } - - // use a separate instance of actor as subject is not set on connect/disconnect - // we need to pass principal name explicitly into log actor - LogActor logActor = new ManagementActor(_appRegistry.getRootMessageLogger(), user); - if (JMXConnectionNotification.OPENED.equals(type)) - { - logActor.message(ManagementConsoleMessages.OPEN(user)); - } - else if (JMXConnectionNotification.CLOSED.equals(type) || - JMXConnectionNotification.FAILED.equals(type)) - { - logActor.message(ManagementConsoleMessages.CLOSE(user)); - } - } } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java new file mode 100644 index 0000000000..ae0574dc21 --- /dev/null +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jmx; + +import static javax.management.remote.JMXConnectionNotification.CLOSED; +import static javax.management.remote.JMXConnectionNotification.FAILED; +import static javax.management.remote.JMXConnectionNotification.OPENED; + +import javax.management.Notification; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.remote.JMXConnectionNotification; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; + +public class ManagementLogonLogoffReporter implements NotificationListener, NotificationFilter +{ + private static final Logger LOGGER = Logger.getLogger(ManagementLogonLogoffReporter.class); + private final RootMessageLogger _rootMessageLogger; + private final UsernameAccessor _usernameAccessor; + + public ManagementLogonLogoffReporter(RootMessageLogger rootMessageLogger, UsernameAccessor usernameAccessor) + { + _rootMessageLogger = rootMessageLogger; + _usernameAccessor = usernameAccessor; + } + + @Override + public void handleNotification(final Notification notification, final Object handback) + { + final String connectionId = ((JMXConnectionNotification) notification).getConnectionId(); + final String type = notification.getType(); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Notification connectionId : " + connectionId + " type : " + type); + } + + String user = _usernameAccessor.getUsernameForConnectionId(connectionId); + + // If user is still null, fallback to an unordered list of Principals from the connection id. + if (user == null) + { + final String[] splitConnectionId = connectionId.split(" "); + user = splitConnectionId[1]; + } + + // use a separate instance of actor as subject is not set on connect/disconnect + // we need to pass principal name explicitly into log actor + LogActor logActor = new ManagementActor(_rootMessageLogger, user); + if (JMXConnectionNotification.OPENED.equals(type)) + { + logActor.message(ManagementConsoleMessages.OPEN(user)); + } + else if (JMXConnectionNotification.CLOSED.equals(type) || + JMXConnectionNotification.FAILED.equals(type)) + { + logActor.message(ManagementConsoleMessages.CLOSE(user)); + } + } + + @Override + public boolean isNotificationEnabled(Notification notification) + { + return notification instanceof JMXConnectionNotification && isLogonTypeEvent(notification); + } + + private boolean isLogonTypeEvent(Notification notification) + { + final String type = notification.getType(); + return CLOSED.equals(type) || FAILED.equals(type) || OPENED.equals(type); + } + +} diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/UsernameAccessor.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/UsernameAccessor.java new file mode 100644 index 0000000000..0cbb0d2687 --- /dev/null +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/UsernameAccessor.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jmx; + +public interface UsernameAccessor +{ + public String getUsernameForConnectionId(String connectionId); + +} diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/UsernameCachingRMIJRMPServer.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/UsernameCachingRMIJRMPServer.java new file mode 100644 index 0000000000..838e9e5664 --- /dev/null +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/UsernameCachingRMIJRMPServer.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jmx; + +import static javax.management.remote.JMXConnectionNotification.CLOSED; +import static javax.management.remote.JMXConnectionNotification.FAILED; + +import java.io.IOException; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.management.Notification; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.rmi.RMIConnection; +import javax.management.remote.rmi.RMIJRMPServerImpl; +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; + +/** + * An implementation of RMIJRMPServerImpl that caches the usernames of users as they log-on + * and makes the same available via {@link UsernameAccessor#getUsernameForConnectionId(String)}. + * + * Caller is responsible for installing this object as a {@link NotificationListener} of the + * {@link JMXConnectorServer} so the cache entries are removed as the clients disconnect. + * + */ +public class UsernameCachingRMIJRMPServer extends RMIJRMPServerImpl implements NotificationListener, NotificationFilter, UsernameAccessor +{ + // ConnectionId is guaranteed to be unique per client connection, according to the JMX spec. + private final Map<String, String> _connectionIdUsernameMap = new ConcurrentHashMap<String, String>(); + + UsernameCachingRMIJRMPServer(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf, + Map<String, ?> env) throws IOException + { + super(port, csf, ssf, env); + } + + @Override + protected RMIConnection makeClient(String connectionId, Subject subject) throws IOException + { + final RMIConnection makeClient = super.makeClient(connectionId, subject); + final AuthenticatedPrincipal authenticatedPrincipalFromSubject = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); + _connectionIdUsernameMap.put(connectionId, authenticatedPrincipalFromSubject.getName()); + return makeClient; + } + + @Override + public String getUsernameForConnectionId(String connectionId) + { + return _connectionIdUsernameMap.get(connectionId); + } + + @Override + public void handleNotification(Notification notification, Object handback) + { + final String connectionId = ((JMXConnectionNotification) notification).getConnectionId(); + removeConnectionIdFromCache(connectionId); + } + + @Override + public boolean isNotificationEnabled(Notification notification) + { + return isClientDisconnectEvent(notification); + } + + private void removeConnectionIdFromCache(String connectionId) + { + _connectionIdUsernameMap.remove(connectionId); + } + + private boolean isClientDisconnectEvent(Notification notification) + { + final String type = notification.getType(); + return CLOSED.equals(type) || FAILED.equals(type); + } + +}
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java index 6990a40dee..51dea92775 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java @@ -65,7 +65,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual _managerMBean = new VirtualHostManagerMBean(this); } - private void initQueues() throws JMException + private void initQueues() { synchronized (_children) { @@ -73,13 +73,20 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual { if(!_children.containsKey(queue)) { - _children.put(queue, new QueueMBean(queue, this)); + try + { + _children.put(queue, new QueueMBean(queue, this)); + } + catch(Exception e) + { + LOGGER.error("Cannot create queue mbean for queue " + queue.getName(), e); + } } } } } - private void initExchanges() throws JMException + private void initExchanges() { synchronized (_children) { @@ -87,13 +94,20 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual { if(!_children.containsKey(exchange)) { - _children.put(exchange, new ExchangeMBean(exchange, this)); + try + { + _children.put(exchange, new ExchangeMBean(exchange, this)); + } + catch(Exception e) + { + LOGGER.error("Cannot create exchange mbean for exchange " + exchange.getName(), e); + } } } } } - private void initConnections() throws JMException + private void initConnections() { synchronized (_children) { @@ -101,7 +115,14 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual { if(!_children.containsKey(conn)) { - _children.put(conn, new ConnectionMBean(conn, this)); + try + { + _children.put(conn, new ConnectionMBean(conn, this)); + } + catch(Exception e) + { + LOGGER.error("Cannot create connection mbean for connection " + conn.getName(), e); + } } } } @@ -119,7 +140,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual public void stateChanged(ConfiguredObject object, State oldState, State newState) { - // ignore + // no-op } public void childAdded(ConfiguredObject object, ConfiguredObject child) @@ -208,4 +229,35 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual return queues; } + + @Override + public void unregister() throws JMException + { + synchronized (_children) + { + for (AMQManagedObject mbean : _children.values()) + { + if(mbean != null) + { + try + { + mbean.unregister(); + } + catch(JMException e) + { + LOGGER.error("Failed to remove mbean for child : " + mbean, e); + } + } + } + _children.clear(); + } + _managerMBean.unregister(); + } + + @Override + public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + // no-op + } + } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ManagementFactory b/qpid/java/broker-plugins/management-jmx/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PluginFactory index 8fa778269e..8fa778269e 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ManagementFactory +++ b/qpid/java/broker-plugins/management-jmx/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PluginFactory diff --git a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/JMXManagementFactoryTest.java b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/JMXManagementFactoryTest.java index 6b6d4018aa..5af1369239 100644 --- a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/JMXManagementFactoryTest.java +++ b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/JMXManagementFactoryTest.java @@ -19,33 +19,42 @@ package org.apache.qpid.server.jmx; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import org.apache.qpid.server.configuration.ServerConfiguration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.plugin.PluginFactory; import org.apache.qpid.test.utils.QpidTestCase; public class JMXManagementFactoryTest extends QpidTestCase { private final JMXManagementFactory _jmxManagementFactory = new JMXManagementFactory(); - private final ServerConfiguration _serverConfiguration = mock(ServerConfiguration.class); + private final Map<String, Object> _attributes = new HashMap<String, Object>(); private final Broker _broker = mock(Broker.class); + private UUID _id = UUID.randomUUID(); public void testJMXConfigured() throws Exception { - when(_serverConfiguration.getJMXManagementEnabled()).thenReturn(true); + _attributes.put(PluginFactory.PLUGIN_TYPE, JMXManagement.PLUGIN_TYPE); - JMXManagement jmxManagement = _jmxManagementFactory.createInstance(_serverConfiguration, _broker); + JMXManagement jmxManagement = (JMXManagement) _jmxManagementFactory.createInstance(_id, _attributes, _broker); assertNotNull(jmxManagement); + assertEquals("Unexpected plugin type", JMXManagement.PLUGIN_TYPE, jmxManagement.getAttribute(JMXManagementFactory.PLUGIN_TYPE)); + assertEquals("Unexpected default mbean platform", JMXManagement.DEFAULT_USE_PLATFORM_MBEAN_SERVER, jmxManagement.getAttribute(JMXManagement.USE_PLATFORM_MBEAN_SERVER)); + assertEquals("Unexpected default name", JMXManagement.DEFAULT_NAME, jmxManagement.getAttribute(JMXManagement.NAME)); } - public void testJMXNotConfigured() throws Exception + public void testCreateInstanceReturnsNullWhenPluginTypeMissing() { - when(_serverConfiguration.getJMXManagementEnabled()).thenReturn(false); - - JMXManagement jmxManagement = _jmxManagementFactory.createInstance(_serverConfiguration, _broker); + assertNull(_jmxManagementFactory.createInstance(_id, _attributes, _broker)); + } - assertNull(jmxManagement); + public void testCreateInstanceReturnsNullWhenPluginTypeNotJmx() + { + _attributes.put(PluginFactory.PLUGIN_TYPE, "notJmx"); + assertNull(_jmxManagementFactory.createInstance(_id, _attributes, _broker)); } } diff --git a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogActorTest.java b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogActorTest.java deleted file mode 100644 index b055cc476f..0000000000 --- a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogActorTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.jmx; - -import java.util.HashMap; -import java.util.Map; - -import javax.management.JMException; -import javax.management.MBeanServerConnection; -import javax.management.MBeanServerInvocationHandler; -import javax.management.ObjectName; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; - -import org.apache.commons.configuration.XMLConfiguration; -import org.apache.qpid.server.configuration.ServerConfiguration; -import org.apache.qpid.server.logging.actors.CurrentActor; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.security.Result; -import org.apache.qpid.server.security.AccessControl; -import org.apache.qpid.server.security.access.ObjectProperties; -import org.apache.qpid.server.security.access.ObjectType; -import org.apache.qpid.server.security.access.Operation; -import org.apache.qpid.server.util.TestApplicationRegistry; -import org.apache.qpid.test.utils.QpidTestCase; - -public class ManagementLogActorTest extends QpidTestCase -{ - private ApplicationRegistry _registry; - private JMXManagedObjectRegistry _objectRegistry; - private int _registryPort; - private int _connectorPort; - private TestPlugin _plugin; - - @Override - public void setUp() throws Exception - { - super.setUp(); - - _registryPort = findFreePort(); - _connectorPort = getNextAvailable(_registryPort + 1); - - //Start a TestApplicationRegistry with (JMX) management disabled, because we - //will instantiate our own directly in order to manipulate it for the test. - XMLConfiguration config = new XMLConfiguration(); - config.addProperty("management.enabled", "false"); - _registry = new TestApplicationRegistry(new ServerConfiguration(config)); - ApplicationRegistry.initialise(_registry); - - _plugin = new TestPlugin(); - _registry.getSecurityManager().addHostPlugin(_plugin); - - //Now start up a test JMXManagedObjectRegistry directly - XMLConfiguration jmxConfig = new XMLConfiguration(); - jmxConfig.addProperty(ServerConfiguration.MGMT_JMXPORT_REGISTRYSERVER, _registryPort + ""); - jmxConfig.addProperty(ServerConfiguration.MGMT_JMXPORT_CONNECTORSERVER, _connectorPort + ""); - - _objectRegistry = new JMXManagedObjectRegistry(new ServerConfiguration(jmxConfig)); - new TestMBean(_objectRegistry); - _objectRegistry.start(); - } - - public void tearDown() throws Exception - { - _objectRegistry.close(); - ApplicationRegistry.remove(); - super.tearDown(); - } - - public void testPrincipalInLogMessage() throws Throwable - { - Map<String, Object> environment = new HashMap<String, Object>(); - environment.put(JMXConnector.CREDENTIALS, new String[] { "admin", "admin" }); - String urlString = "service:jmx:rmi:///jndi/rmi://localhost:" + _registryPort + "/jmxrmi"; - JMXServiceURL url = new JMXServiceURL(urlString); - JMXConnector jmxConnector = JMXConnectorFactory.connect(url, environment); - MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection(); - ObjectName mbeanObject = new ObjectName("org.apache.qpid:type=TestMBean,name=test"); - - CurrentActorRetriever mbean = MBeanServerInvocationHandler.newProxyInstance(mbsc, - mbeanObject, CurrentActorRetriever.class, false); - String actorLogMessage = mbean.getActorLogMessage(); - - assertTrue("Unexpected log principal in security plugin", _plugin.getLogMessage().startsWith("[mng:admin")); - assertTrue("Unexpected log principal in MBean", actorLogMessage.startsWith("[mng:admin")); - } - - public static class TestMBean extends DefaultManagedObject implements CurrentActorRetriever - { - - public TestMBean(ManagedObjectRegistry registry) throws JMException - { - super(CurrentActorRetriever.class, "TestMBean", registry); - register(); - } - - @Override - public String getObjectInstanceName() - { - return "test"; - } - - @Override - public ManagedObject getParentObject() - { - return null; - } - - @Override - public String getActorLogMessage() - { - return CurrentActor.get().getLogMessage(); - } - - } - - public static interface CurrentActorRetriever - { - String getActorLogMessage(); - } - - public static class TestPlugin implements AccessControl - { - private String _logMessage; - - @Override - public Result getDefault() - { - return Result.ALLOWED; - } - - @Override - public Result access(ObjectType objectType, Object instance) - { - return Result.ALLOWED; - } - - @Override - public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties) - { - // set thread name to work around logic in MangementActor - Thread.currentThread().setName("RMI TCP Connection(1)-" + System.currentTimeMillis()); - _logMessage = CurrentActor.get().getLogMessage(); - return Result.ALLOWED; - } - - public String getLogMessage() - { - return _logMessage; - } - - } - -} diff --git a/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java new file mode 100644 index 0000000000..ba9c2cdaa5 --- /dev/null +++ b/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.jmx; + +import static javax.management.remote.JMXConnectionNotification.OPENED; +import static javax.management.remote.JMXConnectionNotification.CLOSED; +import static javax.management.remote.JMXConnectionNotification.FAILED; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; + +import javax.management.remote.JMXConnectionNotification; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.RootMessageLogger; + +import junit.framework.TestCase; + +public class ManagementLogonLogoffReporterTest extends TestCase +{ + private static final String TEST_JMX_UNIQUE_CONNECTION_ID = "jmxconnectionid1 jmxuser,group"; + private static final String TEST_USER = "jmxuser"; + + private ManagementLogonLogoffReporter _reporter; + private UsernameAccessor _usernameAccessor; + private RootMessageLogger _rootMessageLogger; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _usernameAccessor = mock(UsernameAccessor.class); + _rootMessageLogger = mock(RootMessageLogger.class); + // Enable messaging so we can valid the generated strings + when(_rootMessageLogger.isMessageEnabled(any(LogActor.class), anyString())).thenReturn(true); + + _reporter = new ManagementLogonLogoffReporter(_rootMessageLogger, _usernameAccessor); + } + + public void testOpenedNotification() + { + when(_usernameAccessor.getUsernameForConnectionId(TEST_JMX_UNIQUE_CONNECTION_ID)).thenReturn(TEST_USER); + JMXConnectionNotification openNotification = createMockNotification(TEST_JMX_UNIQUE_CONNECTION_ID, OPENED); + + _reporter.handleNotification(openNotification, null); + + verify(_rootMessageLogger).rawMessage("[main] MNG-1007 : Open : User jmxuser", "qpid.message.managementconsole.open"); + } + + public void testClosedNotification() + { + when(_usernameAccessor.getUsernameForConnectionId(TEST_JMX_UNIQUE_CONNECTION_ID)).thenReturn(TEST_USER); + JMXConnectionNotification closeNotification = createMockNotification(TEST_JMX_UNIQUE_CONNECTION_ID, CLOSED); + + _reporter.handleNotification(closeNotification, null); + + verify(_rootMessageLogger).rawMessage("[main] MNG-1008 : Close : User jmxuser", "qpid.message.managementconsole.close"); + } + + public void tesNotifiedForLogOnTypeEvents() + { + JMXConnectionNotification openNotification = createMockNotification(TEST_JMX_UNIQUE_CONNECTION_ID, OPENED); + JMXConnectionNotification closeNotification = createMockNotification(TEST_JMX_UNIQUE_CONNECTION_ID, CLOSED); + JMXConnectionNotification failedNotification = createMockNotification(TEST_JMX_UNIQUE_CONNECTION_ID, FAILED); + + assertTrue(_reporter.isNotificationEnabled(openNotification)); + assertTrue(_reporter.isNotificationEnabled(closeNotification)); + assertTrue(_reporter.isNotificationEnabled(failedNotification)); + + JMXConnectionNotification otherNotification = createMockNotification(TEST_JMX_UNIQUE_CONNECTION_ID, "other"); + assertFalse(_reporter.isNotificationEnabled(otherNotification)); + } + + private JMXConnectionNotification createMockNotification(String connectionId, String notificationType) + { + JMXConnectionNotification notification = mock(JMXConnectionNotification.class); + when(notification.getConnectionId()).thenReturn(connectionId); + when(notification.getType()).thenReturn(notificationType); + return notification; + } +} |
