diff options
Diffstat (limited to 'qpid/java/systests/src/main/java/org/apache/qpid/server/security')
8 files changed, 2073 insertions, 0 deletions
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java new file mode 100644 index 0000000000..f845ff1214 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java @@ -0,0 +1,285 @@ +/* + * 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.security.acl; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.jms.Connection; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.naming.NamingException; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.lang.StringUtils; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQConnectionURL; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.apache.qpid.url.URLSyntaxException; + +/** + * Abstract test case for ACLs. + * + * This base class contains convenience methods to mange ACL files and implements a mechanism that allows each + * test method to run its own setup code before the broker starts. + * + * TODO move the pre broker-startup setup method invocation code to {@link QpidBrokerTestCase} + * + * @see SimpleACLTest + * @see ExternalACLTest + * @see ExternalACLFileTest + * @see ExternalACLJMXTest + * @see ExternalAdminACLTest + * @see ExhaustiveACLTest + */ +public abstract class AbstractACLTestCase extends QpidBrokerTestCase implements ConnectionListener +{ + /** Used to synchronise {@link #tearDown()} when exceptions are thrown */ + protected CountDownLatch _exceptionReceived; + + /** Override this to return the name of the configuration XML file. */ + public String getConfig() + { + return "config-systests-acl.xml"; + } + + /** Override this to setup external ACL files for virtual hosts. */ + public List<String> getHostList() + { + return Collections.emptyList(); + } + + /** + * This setup method checks {@link #getConfig()} and {@link #getHostList()} to initialise the broker with specific + * ACL configurations and then runs an optional per-test setup method, which is simply a method with the same name + * as the test, but starting with {@code setUp} rather than {@code test}. + * + * @see #setUpACLFile(String) + * @see org.apache.qpid.test.utils.QpidBrokerTestCase#setUp() + */ + @Override + public void setUp() throws Exception + { + if (QpidHome == null) + { + fail("QPID_HOME not set"); + } + + // Initialise ACLs. + _configFile = new File(QpidHome, "etc" + File.separator + getConfig()); + + // Initialise ACL files + for (String virtualHost : getHostList()) + { + setUpACLFile(virtualHost); + } + + // run test specific setup + String testSetup = StringUtils.replace(getName(), "test", "setUp"); + try + { + Method setup = getClass().getDeclaredMethod(testSetup); + setup.invoke(this); + } + catch (NoSuchMethodException e) + { + // Ignore + } + catch (InvocationTargetException e) + { + throw (Exception) e.getTargetException(); + } + + super.setUp(); + } + + @Override + public void tearDown() throws Exception + { + try + { + super.tearDown(); + } + catch (JMSException e) + { + //we're throwing this away as it can happen in this test as the state manager remembers exceptions + //that we provoked with authentication failures, where the test passes - we can ignore on con close + } + } + + /** + * Configures specific ACL files for a virtual host. + * + * This method checks for ACL files that exist on the filesystem. If dynamically generatyed ACL files are required in a test, + * then it is easier to use the {@code setUp} prefix on a method to generate the ACL file. In order, this method looks + * for three files: + * <ol> + * <li><em>virtualhost</em>-<em>class</em>-<em>test</em>.txt + * <li><em>virtualhost</em>-<em>class</em>.txt + * <li><em>virtualhost</em>-default.txt + * </ol> + * The <em>class</em> and <em>test</em> parts are the test class and method names respectively, with the word {@code test} + * removed and the rest of the text converted to lowercase. For example, the test class and method named + * {@code org.apache.qpid.test.AccessExampleTest#testExampleMethod} on the {@code testhost} virtualhost would use + * one of the following files: + * <ol> + * <li>testhost-accessexample-examplemethod.txt + * <li>testhost-accessexample.txt + * <li>testhost-default.txt + * </ol> + * These files should be copied to the <em>${QPID_HOME}/etc</em> directory when the test is run. + * + * @see #writeACLFile(String, String...) + */ + public void setUpACLFile(String virtualHost) throws IOException, ConfigurationException + { + String path = QpidHome + File.separator + "etc"; + String className = StringUtils.substringBeforeLast(getClass().getSimpleName().toLowerCase(), "test"); + String testName = StringUtils.substringAfter(getName(), "test").toLowerCase(); + + File aclFile = new File(path, virtualHost + "-" + className + "-" + testName + ".txt"); + if (!aclFile.exists()) + { + aclFile = new File(path, virtualHost + "-" + className + ".txt"); + if (!aclFile.exists()) + { + aclFile = new File(path, virtualHost + "-" + "default.txt"); + } + } + + // Set the ACL file configuration property + if (virtualHost.equals("global")) + { + setConfigurationProperty("security.aclv2", aclFile.getAbsolutePath()); + } + else + { + setConfigurationProperty("virtualhosts.virtualhost." + virtualHost + ".security.aclv2", aclFile.getAbsolutePath()); + } + } + + public void writeACLFile(String vhost, String...rules) throws ConfigurationException, IOException + { + File aclFile = File.createTempFile(getClass().getSimpleName(), getName()); + aclFile.deleteOnExit(); + + if ("global".equals(vhost)) + { + setConfigurationProperty("security.aclv2", aclFile.getAbsolutePath()); + } + else + { + setConfigurationProperty("virtualhosts.virtualhost." + vhost + ".security.aclv2", aclFile.getAbsolutePath()); + } + + PrintWriter out = new PrintWriter(new FileWriter(aclFile)); + out.println(String.format("# %s", _testName)); + for (String line : rules) + { + out.println(line); + } + out.close(); + } + + /** + * Creates a connection to the broker, and sets a connection listener to prevent failover and an exception listener + * with a {@link CountDownLatch} to synchronise in the {@link #check403Exception(Throwable)} method and allow the + * {@link #tearDown()} method to complete properly. + */ + public Connection getConnection(String vhost, String username, String password) throws NamingException, JMSException, URLSyntaxException + { + AMQConnection connection = (AMQConnection) getConnection(createConnectionURL(vhost, username, password)); + + //Prevent Failover + connection.setConnectionListener(this); + + //QPID-2081: use a latch to sync on exception causing connection close, to work + //around the connection close race during tearDown() causing sporadic failures + _exceptionReceived = new CountDownLatch(1); + + connection.setExceptionListener(new ExceptionListener() + { + public void onException(JMSException e) + { + _exceptionReceived.countDown(); + } + }); + + return (Connection) connection; + } + + // Connection Listener Interface - Used here to block failover + + public void bytesSent(long count) + { + } + + public void bytesReceived(long count) + { + } + + public boolean preFailover(boolean redirect) + { + //Prevent failover. + return false; + } + + public boolean preResubscribe() + { + return false; + } + + public void failoverComplete() + { + } + + /** + * Convenience method to build an {@link AMQConnectionURL} with the right parameters. + */ + public AMQConnectionURL createConnectionURL(String vhost, String username, String password) throws URLSyntaxException + { + String url = "amqp://" + username + ":" + password + "@clientid/" + vhost + "?brokerlist='" + getBroker() + "?retries='0''"; + return new AMQConnectionURL(url); + } + + /** + * Convenience method to validate a JMS exception with a linked {@link AMQConstant#ACCESS_REFUSED} 403 error code exception. + */ + public void check403Exception(Throwable t) throws Exception + { + assertNotNull("There was no linked exception", t); + assertTrue("Wrong linked exception type", t instanceof AMQException); + assertEquals("Incorrect error code received", 403, ((AMQException) t).getErrorCode().getCode()); + + //use the latch to ensure the control thread waits long enough for the exception thread + //to have done enough to mark the connection closed before teardown commences + assertTrue("Timed out waiting for conneciton to report close", _exceptionReceived.await(2, TimeUnit.SECONDS)); + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java new file mode 100644 index 0000000000..1b2c98d30a --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java @@ -0,0 +1,195 @@ +/* + * 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.security.acl; + +import java.util.Arrays; +import java.util.List; + +import javax.jms.Connection; +import javax.jms.Session; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.protocol.AMQConstant; + +/** + * ACL version 2/3 file testing to verify that ACL entries control queue creation with specific properties. + * + * Tests have their own ACL files that setup specific permissions, and then try to create queues with every possible combination + * of properties to show that rule matching works correctly. For example, a rule that specified {@code autodelete="true"} for + * queues with {@link name="temp.true.*"} as well should not affect queues that have names that do not match, or queues that + * are not autodelete, or both. Also checks that ACL entries only affect the specified users and virtual hosts. + */ +public class ExhaustiveACLTest extends AbstractACLTestCase +{ + @Override + public String getConfig() + { + return "config-systests-aclv2.xml"; + } + + @Override + public List<String> getHostList() + { + return Arrays.asList("test", "test2"); + } + + /** + * Creates a queue. + * + * Connects to the broker as a particular user and create the named queue on a virtual host, with the provided + * parameters. Uses a new {@link Connection} and {@link Session} and closes them afterwards. + */ + private void createQueue(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception + { + Connection conn = getConnection(vhost, user, "guest"); + Session sess = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + ((AMQSession<?, ?>) sess).createQueue(new AMQShortString(name), autoDelete, durable, false); + sess.commit(); + conn.close(); + } + + /** + * Calls {@link #createQueue(String, String, String, boolean, boolean)} with the provided parameters and checks that + * no exceptions were thrown. + */ + private void createQueueSuccess(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception + { + try + { + createQueue(vhost, user, name, autoDelete, durable); + } + catch (AMQException e) + { + fail(String.format("Create queue should have worked for \"%s\" for user %s@%s, autoDelete=%s, durable=%s", + name, user, vhost, Boolean.toString(autoDelete), Boolean.toString(durable))); + } + } + + /** + * Calls {@link #createQueue(String, String, String, boolean, boolean)} with the provided parameters and checks that + * the exception thrown was an {@link AMQConstant#ACCESS_REFUSED} or 403 error code. + */ + private void createQueueFailure(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception + { + try + { + createQueue(vhost, user, name, autoDelete, durable); + fail(String.format("Create queue should have failed for \"%s\" for user %s@%s, autoDelete=%s, durable=%s", + name, user, vhost, Boolean.toString(autoDelete), Boolean.toString(durable))); + } + catch (AMQException e) + { + assertEquals("Should be an ACCESS_REFUSED error", 403, e.getErrorCode().getCode()); + } + } + + public void setUpAuthoriseCreateQueueAutodelete() throws Exception + { + writeACLFile("test", + "acl allow client access virtualhost", + "acl allow server access virtualhost", + "acl allow client create queue name=\"temp.true.*\" autodelete=true", + "acl allow client create queue name=\"temp.false.*\" autodelete=false", + "acl deny client create queue", + "acl allow client delete queue", + "acl deny all create queue" + ); + } + + /** + * Test creation of temporary queues, with the autodelete property set to true. + */ + public void testAuthoriseCreateQueueAutodelete() throws Exception + { + createQueueSuccess("test", "client", "temp.true.00", true, false); + createQueueSuccess("test", "client", "temp.true.01", true, false); + createQueueSuccess("test", "client", "temp.true.02", true, true); + createQueueSuccess("test", "client", "temp.false.03", false, false); + createQueueSuccess("test", "client", "temp.false.04", false, false); + createQueueSuccess("test", "client", "temp.false.05", false, true); + createQueueFailure("test", "client", "temp.true.06", false, false); + createQueueFailure("test", "client", "temp.false.07", true, false); + createQueueFailure("test", "server", "temp.true.08", true, false); + createQueueFailure("test", "client", "temp.other.09", false, false); + createQueueSuccess("test2", "guest", "temp.true.01", false, false); + createQueueSuccess("test2", "guest", "temp.false.02", true, false); + createQueueSuccess("test2", "guest", "temp.true.03", true, false); + createQueueSuccess("test2", "guest", "temp.false.04", false, false); + createQueueSuccess("test2", "guest", "temp.other.05", false, false); + } + + public void setUpAuthoriseCreateQueue() throws Exception + { + writeACLFile("test", + "acl allow client access virtualhost", + "acl allow server access virtualhost", + "acl allow client create queue name=\"create.*\"" + ); + } + + /** + * Tests creation of named queues. + * + * If a named queue is specified + */ + public void testAuthoriseCreateQueue() throws Exception + { + createQueueSuccess("test", "client", "create.00", true, true); + createQueueSuccess("test", "client", "create.01", true, false); + createQueueSuccess("test", "client", "create.02", false, true); + createQueueSuccess("test", "client", "create.03", true, false); + createQueueFailure("test", "server", "create.04", true, true); + createQueueFailure("test", "server", "create.05", true, false); + createQueueFailure("test", "server", "create.06", false, true); + createQueueFailure("test", "server", "create.07", true, false); + createQueueSuccess("test2", "guest", "create.00", true, true); + createQueueSuccess("test2", "guest", "create.01", true, false); + createQueueSuccess("test2", "guest", "create.02", false, true); + createQueueSuccess("test2", "guest", "create.03", true, false); + } + + public void setUpAuthoriseCreateQueueBoth() throws Exception + { + writeACLFile("test", + "acl allow all access virtualhost", + "acl allow client create queue name=\"create.*\"", + "acl allow all create queue temporary=true" + ); + } + + /** + * Tests creation of named queues. + * + * If a named queue is specified + */ + public void testAuthoriseCreateQueueBoth() throws Exception + { + createQueueSuccess("test", "client", "create.00", true, false); + createQueueSuccess("test", "client", "create.01", false, false); + createQueueFailure("test", "server", "create.02", false, false); + createQueueFailure("test", "guest", "create.03", false, false); + createQueueSuccess("test", "client", "tmp.00", true, false); + createQueueSuccess("test", "server", "tmp.01", true, false); + createQueueSuccess("test", "guest", "tmp.02", true, false); + createQueueSuccess("test2", "guest", "create.02", false, false); + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java new file mode 100644 index 0000000000..1d08015669 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java @@ -0,0 +1,184 @@ +/* + * 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.security.acl; + +import java.util.Arrays; +import java.util.List; + +import javax.jms.Connection; +import javax.jms.Session; + +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.framing.AMQShortString; + +/** + * Tests that ACL version 2/3 files following the specification work correctly. + * + * ACL lines that are identical in meaning apart from differences allowed by the specification, such as whitespace or case + * of tokens are set up for numbered queues and the queues are then created to show that the meaning is correctly parsed by + * the plugin. + * + * TODO move this to the access-control plugin unit tests instead + */ +public class ExternalACLFileTest extends AbstractACLTestCase +{ + @Override + public String getConfig() + { + return "config-systests-aclv2.xml"; + } + + @Override + public List<String> getHostList() + { + return Arrays.asList("test"); + } + + private void createQueuePrefixList(String prefix, int count) + { + try + { + Connection conn = getConnection("test", "client", "guest"); + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + conn.start(); + + //Create n queues + for (int n = 0; n < count; n++) + { + AMQShortString queueName = new AMQShortString(String.format("%s.%03d", prefix, n)); + ((AMQSession<?, ?>) sess).createQueue(queueName, false, false, false); + } + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + private void createQueueNameList(String...queueNames) + { + try + { + Connection conn = getConnection("test", "client", "guest"); + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + conn.start(); + + //Create all queues + for (String queueName : queueNames) + { + ((AMQSession<?, ?>) sess).createQueue(new AMQShortString(queueName), false, false, false); + } + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void setUpCreateQueueMixedCase() throws Exception + { + writeACLFile( + "test", + "acl allow client create queue name=mixed.000", + "ACL ALLOW client CREATE QUEUE NAME=mixed.001", + "Acl Allow client Create Queue Name=mixed.002", + "aCL aLLOW client cREATE qUEUE nAME=mixed.003", + "aCl AlLoW client cReAtE qUeUe NaMe=mixed.004" + ); + } + + public void testCreateQueueMixedCase() + { + createQueuePrefixList("mixed", 5); + } + + public void setUpCreateQueueContinuation() throws Exception + { + writeACLFile( + "test", + "acl allow client create queue name=continuation.000", + "acl allow client create queue \\", + " name=continuation.001", + "acl allow client \\", + " create queue \\", + " name=continuation.002", + "acl allow \\", + " client \\", + " create queue \\", + " name=continuation.003", + "acl \\", + " allow \\", + " client \\", + " create queue \\", + " name=continuation.004" + ); + } + + public void testCreateQueueContinuation() + { + createQueuePrefixList("continuation", 5); + } + + public void setUpCreateQueueWhitespace() throws Exception + { + writeACLFile( + "test", + "acl allow client create queue name=whitespace.000", + "acl\tallow\tclient\tcreate\tqueue\tname=whitespace.001", + "acl allow client create queue name = whitespace.002", + "acl\tallow\tclient\tcreate\tqueue\tname\t=\twhitespace.003", + "acl allow\t\tclient\t \tcreate\t\t queue\t \t name \t =\t \twhitespace.004" + ); + } + + public void testCreateQueueWhitespace() + { + createQueuePrefixList("whitespace", 5); + } + + public void setUpCreateQueueQuoting() throws Exception + { + writeACLFile( + "test", + "acl allow client create queue name='quoting.ABC.000'", + "acl allow client create queue name='quoting.*.000'", + "acl allow client create queue name='quoting.#.000'", + "acl allow client create queue name='quoting. .000'", + "acl allow client create queue name='quoting.!@$%.000'" + ); + } + + public void testCreateQueueQuoting() + { + createQueueNameList( + "quoting.ABC.000", + "quoting.*.000", + "quoting.#.000", + "quoting. .000", + "quoting.!@$%.000" + ); + } +} + + + diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java new file mode 100644 index 0000000000..b823690002 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java @@ -0,0 +1,244 @@ +/* + * 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.security.acl; + +import java.util.Arrays; +import java.util.List; + +import org.apache.qpid.AMQConnectionClosedException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.test.utils.JMXTestUtils; + +/** + * Tests that ACL entries that apply to AMQP objects also apply when those objects are accessed via JMX. + */ +public class ExternalACLJMXTest extends AbstractACLTestCase +{ + private JMXTestUtils _jmx; + + private static final String QUEUE_NAME = "kipper"; + private static final String EXCHANGE_NAME = "amq.kipper"; + + @Override + public String getConfig() + { + return "config-systests-aclv2.xml"; + } + + @Override + public List<String> getHostList() + { + return Arrays.asList("test"); + } + + @Override + public void setUp() throws Exception + { + _jmx = new JMXTestUtils(this, "admin", "admin"); + _jmx.setUp(); + super.setUp(); + _jmx.open(); + } + + @Override + public void tearDown() throws Exception + { + _jmx.close(); + super.tearDown(); + } + + // test-externalacljmx.txt + // create queue owner=client # success + public void testCreateClientQueueSuccess() throws Exception + { + //Queue Parameters + String queueOwner = "client"; + + _jmx.createQueue("test", QUEUE_NAME, queueOwner, true); + } + + // test-externalacljmx.txt + // create queue owner=client # failure + public void testCreateServerQueueFailure() throws Exception + { + //Queue Parameters + String queueOwner = "server"; + + try + { + _jmx.createQueue("test", QUEUE_NAME, queueOwner, true); + + fail("Queue create should fail"); + } + catch (Exception e) + { + assertNotNull("Cause is not set", e.getCause()); + assertEquals("Cause message incorrect", + "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage()); + } + } + + // no create queue acl in file # failure + public void testCreateQueueFailure() throws Exception + { + //Queue Parameters + String queueOwner = "guest"; + + try + { + _jmx.createQueue("test", QUEUE_NAME, queueOwner, true); + + fail("Queue create should fail"); + } + catch (Exception e) + { + assertNotNull("Cause is not set", e.getCause()); + assertEquals("Cause message incorrect", + "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage()); + } + } + + // test-externalacljmx.txt + // allow create exchange name=amq.kipper.success + public void testCreateExchangeSuccess() throws Exception + { + _jmx.createExchange("test", EXCHANGE_NAME + ".success", "direct", true); + } + + // test-externalacljmx.txt + // deny create exchange name=amq.kipper.failure + public void testCreateExchangeFailure() throws Exception + { + try + { + _jmx.createExchange("test", EXCHANGE_NAME + ".failure", "direct", true); + + fail("Exchange create should fail"); + } + catch (Exception e) + { + assertNotNull("Cause is not set", e.getCause()); + assertEquals("Cause message incorrect", + "org.apache.qpid.AMQSecurityException: Permission denied: exchange-name 'amq.kipper.failure' [error code 403: access refused]", e.getCause().getMessage()); + } + } + + // test-externalacljmx.txt + // allow create exchange name=amq.kipper.success + // allow delete exchange name=amq.kipper.success + public void testDeleteExchangeSuccess() throws Exception + { + _jmx.createExchange("test", EXCHANGE_NAME + ".success", "direct", true); + _jmx.unregisterExchange("test", EXCHANGE_NAME + ".success"); + } + + // test-externalacljmx-deleteexchangefailure.txt + // allow create exchange name=amq.kipper.delete + // deny delete exchange name=amq.kipper.delete + public void testDeleteExchangeFailure() throws Exception + { + _jmx.createExchange("test", EXCHANGE_NAME + ".delete", "direct", true); + try + { + _jmx.unregisterExchange("test", EXCHANGE_NAME + ".delete"); + + fail("Exchange delete should fail"); + } + catch (Exception e) + { + assertNotNull("Cause is not set", e.getCause()); + assertEquals("Cause message incorrect", + "org.apache.qpid.AMQSecurityException: Permission denied [error code 403: access refused]", e.getCause().getMessage()); + } + } + + /** + * admin user has JMX right but not AMQP + */ + public void setUpCreateQueueJMXRights() throws Exception + { + writeACLFile("test", + "ACL ALLOW admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"", + "ACL DENY admin CREATE QUEUE"); + } + + public void testCreateQueueJMXRights() throws Exception + { + try + { + _jmx.createQueue("test", QUEUE_NAME, "admin", true); + + fail("Queue create should fail"); + } + catch (Exception e) + { + assertNotNull("Cause is not set", e.getCause()); + assertEquals("Cause message incorrect", + "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage()); + } + } + + /** + * admin user has AMQP right but not JMX + */ + public void setUpCreateQueueAMQPRights() throws Exception + { + writeACLFile("test", + "ACL DENY admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"", + "ACL ALLOW admin CREATE QUEUE"); + } + + public void testCreateQueueAMQPRights() throws Exception + { + try + { + _jmx.createQueue("test", QUEUE_NAME, "admin", true); + + fail("Queue create should fail"); + } + catch (Exception e) + { + assertEquals("Cause message incorrect", "Permission denied: Execute createNewQueue", e.getMessage()); + } + } + + /** + * admin has both JMX and AMQP rights + */ + public void setUpCreateQueueJMXAMQPRights() throws Exception + { + writeACLFile("test", + "ACL ALLOW admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"", + "ACL ALLOW admin CREATE QUEUE"); + } + + public void testCreateQueueJMXAMQPRights() throws Exception + { + try + { + _jmx.createQueue("test", QUEUE_NAME, "admin", true); + } + catch (Exception e) + { + fail("Queue create should succeed: " + e.getCause().getMessage()); + } + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java new file mode 100644 index 0000000000..4603cc1862 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java @@ -0,0 +1,37 @@ +/* + * 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.security.acl; + +import java.util.Arrays; +import java.util.List; + +public class ExternalACLTest extends SimpleACLTest +{ + @Override + public String getConfig() + { + return "config-systests-aclv2.xml"; + } + + @Override + public List<String> getHostList() + { + return Arrays.asList("test", "test2"); + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java new file mode 100644 index 0000000000..290cbfdc14 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java @@ -0,0 +1,186 @@ +/* + * 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.security.acl; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.apache.qpid.server.logging.management.LoggingManagementMBean; +import org.apache.qpid.test.utils.JMXTestUtils; + +/** + * Tests that ACLs can be applied to mangement operations that do not correspond to a specific AMQP object. + * + * Theses tests use the logging component, exposed as the {@link LoggingManagementMBean}, to get and set properties. + */ +public class ExternalAdminACLTest extends AbstractACLTestCase +{ + private static final String CATEGORY_PRIORITY = "LogManMBeanTest.category.priority"; + private static final String CATEGORY_LEVEL = "LogManMBeanTest.category.level"; + private static final String LOGGER_LEVEL = "LogManMBeanTest.logger.level"; + + private static final String NEWLINE = System.getProperty("line.separator"); + + private JMXTestUtils _jmx; + private File _testConfigFile; + + @Override + public String getConfig() + { + return "config-systests-aclv2.xml"; + } + + @Override + public List<String> getHostList() + { + return Arrays.asList("global"); + } + + @Override + public void setUp() throws Exception + { + _testConfigFile = createTempTestLog4JConfig(); + + _jmx = new JMXTestUtils(this, "admin", "admin"); + _jmx.setUp(); + super.setUp(); + _jmx.open(); + } + + @Override + public void tearDown() throws Exception + { + _jmx.close(); + super.tearDown(); + } + + private File createTempTestLog4JConfig() + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE); + writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE); + + writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " + + "threshold=\"null\">"+NEWLINE); + + writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE); + writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE); + writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE); + writer.write(" </layout>"+NEWLINE); + writer.write(" </appender>"+NEWLINE); + + //Example of a 'category' with a 'priority' + writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_PRIORITY +"\">"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'category' with a 'level' + writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_LEVEL +"\">"+NEWLINE); + writer.write(" <level value=\"warn\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'logger' with a 'level' + writer.write(" <logger additivity=\"true\" name=\"" + LOGGER_LEVEL + "\">"+NEWLINE); + writer.write(" <level value=\"error\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </logger>"+NEWLINE); + + //'root' logger + writer.write(" <root>"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </root>"+NEWLINE); + + writer.write("</log4j:configuration>"+NEWLINE); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create temporary test log4j configuration"); + } + + return tmpFile; + } + + public void testGetAllLoggerLevels() throws Exception + { + String[] levels = _jmx.getAvailableLoggerLevels(); + for (int i = 0; i < levels.length; i++) + { + System.out.println(levels[i]); + } + assertEquals("Got incorrect number of log levels", 9, levels.length); + } + + public void testGetAllLoggerLevelsDenied() throws Exception + { + try + { + _jmx.getAvailableLoggerLevels(); + fail("Got list of log levels"); + } + catch (Exception e) + { + // Exception throws + e.printStackTrace(); + assertEquals("Permission denied: Access getAvailableLoggerLevels", e.getMessage()); + } + } + + public void testChangeLoggerLevel() throws Exception + { + String oldLevel = _jmx.getRuntimeRootLoggerLevel(); + System.out.println("old level = " + oldLevel); + _jmx.setRuntimeRootLoggerLevel("DEBUG"); + String newLevel = _jmx.getRuntimeRootLoggerLevel(); + System.out.println("new level = " + newLevel); + assertEquals("Logging level was not changed", "DEBUG", newLevel); + } + + public void testChangeLoggerLevelDenied() throws Exception + { + try + { + _jmx.setRuntimeRootLoggerLevel("DEBUG"); + fail("Logging level was changed"); + } + catch (Exception e) + { + assertEquals("Permission denied: Update setRuntimeRootLoggerLevel", e.getMessage()); + } + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java new file mode 100644 index 0000000000..a50817e659 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java @@ -0,0 +1,644 @@ +/* + * 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.security.acl; + +import java.io.IOException; + +import javax.jms.Connection; +import javax.jms.DeliveryMode; +import javax.jms.IllegalStateException; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.jms.Topic; +import javax.jms.TopicSubscriber; +import javax.naming.NamingException; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.url.URLSyntaxException; + +/** + * Basic access control list tests. + * + * These tests require an access control security plugin to be configured in the broker, and carry out various broker + * operations that will succeed or fail depending on the user and virtual host. See the {@code config-systests-acl-setup.xml} + * configuration file for the SimpleXML version of the ACLs used by the Java broker only, or the various {@code .txt} + * files in the system tests directory for the external version 3 ACL files used by both the Java and C++ brokers. + * <p> + * This class can be extended and the {@link #getConfig()} method overridden to run the same tests with a different type + * of access control mechanism. Extension classes should differ only in the configuration file used, but extra tests can be + * added that are specific to a particular configuration. + * <p> + * The tests perform basic AMQP operations like creating queues or excahnges and publishing and consuming messages, using + * JMS to contact the broker. + * + * @see ExternalACLTest + */ +public class SimpleACLTest extends AbstractACLTestCase +{ + public void testAccessAuthorizedSuccess() throws AMQException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + Session sess = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + + //Do something to show connection is active. + sess.rollback(); + + conn.close(); + } + catch (Exception e) + { + fail("Connection was not created due to:" + e); + } + } + + public void testAccessVhostAuthorisedGuestSuccess() throws IOException, Exception + { + //The 'guest' user has no access to the 'test' vhost, as tested below in testAccessNoRights(), and so + //is unable to perform actions such as connecting (and by extension, creating a queue, and consuming + //from a queue etc). In order to test the vhost-wide 'access' ACL right, the 'guest' user has been given + //this right in the 'test2' vhost. + + try + { + //get a connection to the 'test2' vhost using the guest user and perform various actions. + Connection conn = getConnection("test2", "guest", "guest"); + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + conn.start(); + + //create Queues and consumers for each + Queue namedQueue = sess.createQueue("vhostAccessCreatedQueue" + getTestQueueName()); + Queue tempQueue = sess.createTemporaryQueue(); + MessageConsumer consumer = sess.createConsumer(namedQueue); + MessageConsumer tempConsumer = sess.createConsumer(tempQueue); + + //send a message to each queue (also causing an exchange declare) + MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null); + ((org.apache.qpid.jms.MessageProducer) sender).send(namedQueue, sess.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + ((org.apache.qpid.jms.MessageProducer) sender).send(tempQueue, sess.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + //consume the messages from the queues + consumer.receive(2000); + tempConsumer.receive(2000); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testAccessNoRightsFailure() throws Exception + { + try + { + Connection conn = getConnection("test", "guest", "guest"); + Session sess = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + sess.rollback(); + + fail("Connection was created."); + } + catch (JMSException e) + { + // JMSException -> linkedException -> cause = AMQException (403 or 320) + Exception linkedException = e.getLinkedException(); + assertNotNull("There was no linked exception", linkedException); + Throwable cause = linkedException.getCause(); + assertNotNull("Cause was null", cause); + assertTrue("Wrong linked exception type", cause instanceof AMQException); + AMQConstant errorCode = isBroker010() ? AMQConstant.CONTEXT_IN_USE : AMQConstant.ACCESS_REFUSED; + assertEquals("Incorrect error code received", errorCode, ((AMQException) cause).getErrorCode()); + } + } + + public void testClientDeleteQueueSuccess() throws Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + Session sess = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + + // create kipper + Topic kipper = sess.createTopic("kipper"); + TopicSubscriber subscriber = sess.createDurableSubscriber(kipper, "kipper"); + + subscriber.close(); + sess.unsubscribe("kipper"); + + //Do something to show connection is active. + sess.rollback(); + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerDeleteQueueFailure() throws Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + Session sess = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + + // create kipper + Topic kipper = sess.createTopic("kipper"); + TopicSubscriber subscriber = sess.createDurableSubscriber(kipper, "kipper"); + + subscriber.close(); + sess.unsubscribe("kipper"); + + //Do something to show connection is active. + sess.rollback(); + conn.close(); + } + catch (JMSException e) + { + // JMSException -> linedException = AMQException.403 + check403Exception(e.getLinkedException()); + } + } + + public void testClientConsumeFromTempQueueSuccess() throws AMQException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sess.createConsumer(sess.createTemporaryQueue()); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testClientConsumeFromNamedQueueFailure() throws NamingException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sess.createConsumer(sess.createQueue("IllegalQueue")); + + fail("Test failed as consumer was created."); + } + catch (JMSException e) + { + check403Exception(e.getLinkedException()); + } + } + + public void testClientCreateTemporaryQueueSuccess() throws JMSException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create Temporary Queue - can't use the createTempQueue as QueueName is null. + ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("doesnt_matter_as_autodelete_means_tmp"), + true, false, false); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testClientCreateNamedQueueFailure() throws NamingException, JMSException, AMQException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create a Named Queue + ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("IllegalQueue"), false, false, false); + + fail("Test failed as Queue creation succeded."); + //conn will be automatically closed + } + catch (AMQException e) + { + check403Exception(e); + } + } + + public void testClientPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(true, Session.SESSION_TRANSACTED); + + conn.start(); + + MessageProducer sender = sess.createProducer(sess.createQueue("example.RequestQueue")); + + sender.send(sess.createTextMessage("test")); + + //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker. + sess.commit(); + + conn.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testClientPublishValidQueueSuccess() throws AMQException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null); + + Queue queue = sess.createQueue("example.RequestQueue"); + + // Send a message that we will wait to be sent, this should give the broker time to process the msg + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, sess.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + conn.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testClientPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession<?, ?>) session).createProducer(null); + + Queue queue = session.createQueue("Invalid"); + + // Send a message that we will wait to be sent, this should give the broker time to close the connection + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + // Test the connection with a valid consumer + // This may fail as the session may be closed before the queue or the consumer created. + Queue temp = session.createTemporaryQueue(); + + session.createConsumer(temp).close(); + + //Connection should now be closed and will throw the exception caused by the above send + conn.close(); + + fail("Close is not expected to succeed."); + } + catch (IllegalStateException e) + { + _logger.info("QPID-2345: Session became closed and we got that error rather than the authentication error."); + } + catch (JMSException e) + { + check403Exception(e.getLinkedException()); + } + } + + public void testServerConsumeFromNamedQueueValid() throws AMQException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sess.createConsumer(sess.createQueue("example.RequestQueue")); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException, NamingException, Exception + { + try + { + Connection conn = getConnection("test", "client", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sess.createConsumer(sess.createQueue("Invalid")); + + fail("Test failed as consumer was created."); + } + catch (JMSException e) + { + check403Exception(e.getLinkedException()); + } + } + + public void testServerConsumeFromTemporaryQueue() throws AMQException, URLSyntaxException, NamingException, Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sess.createConsumer(sess.createTemporaryQueue()); + + fail("Test failed as consumer was created."); + } + catch (JMSException e) + { + check403Exception(e.getLinkedException()); + } + } + + public void testServerCreateNamedQueueValid() throws JMSException, URLSyntaxException, Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create Temporary Queue + ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("example.RequestQueue"), false, false, false); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerCreateNamedQueueInvalid() throws JMSException, URLSyntaxException, AMQException, NamingException, Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + + Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create a Named Queue + ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("IllegalQueue"), false, false, false); + + fail("Test failed as creation succeded."); + } + catch (Exception e) + { + check403Exception(e); + } + } + + public void testServerCreateTemporaryQueueInvalid() throws NamingException, Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + session.createTemporaryQueue(); + + fail("Test failed as creation succeded."); + } + catch (JMSException e) + { + check403Exception(e.getLinkedException()); + } + } + + public void testServerCreateAutoDeleteQueueInvalid() throws NamingException, JMSException, AMQException, Exception + { + try + { + Connection connection = getConnection("test", "server", "guest"); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + connection.start(); + + ((AMQSession<?, ?>) session).createQueue(new AMQShortString("again_ensure_auto_delete_queue_for_temporary"), + true, false, false); + + fail("Test failed as creation succeded."); + } + catch (Exception e) + { + check403Exception(e); + } + } + + /** + * This test uses both the cilent and sender to validate that the Server is able to publish to a temporary queue. + * The reason the client must be involved is that the Server is unable to create its own Temporary Queues. + * + * @throws AMQException + * @throws URLSyntaxException + * @throws JMSException + */ + public void testServerPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception + { + //Set up the Server + Connection serverConnection = getConnection("test", "server", "guest"); + + Session serverSession = serverConnection.createSession(true, Session.SESSION_TRANSACTED); + + Queue requestQueue = serverSession.createQueue("example.RequestQueue"); + + MessageConsumer server = serverSession.createConsumer(requestQueue); + + serverConnection.start(); + + //Set up the consumer + Connection clientConnection = getConnection("test", "client", "guest"); + + //Send a test mesage + Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + Queue responseQueue = clientSession.createTemporaryQueue(); + + MessageConsumer clientResponse = clientSession.createConsumer(responseQueue); + + clientConnection.start(); + + Message request = clientSession.createTextMessage("Request"); + + assertNotNull("Response Queue is null", responseQueue); + + request.setJMSReplyTo(responseQueue); + + clientSession.createProducer(requestQueue).send(request); + + try + { + Message msg = null; + + msg = server.receive(2000); + + while (msg != null && !((TextMessage) msg).getText().equals("Request")) + { + msg = server.receive(2000); + } + + assertNotNull("Message not received", msg); + + assertNotNull("Reply-To is Null", msg.getJMSReplyTo()); + + MessageProducer sender = serverSession.createProducer(msg.getJMSReplyTo()); + + sender.send(serverSession.createTextMessage("Response")); + + //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker. + serverSession.commit(); + + //Ensure Response is received. + Message clientResponseMsg = clientResponse.receive(2000); + assertNotNull("Client did not receive response message,", clientResponseMsg); + assertEquals("Incorrect message received", "Response", ((TextMessage) clientResponseMsg).getText()); + + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + finally + { + try + { + serverConnection.close(); + } + finally + { + clientConnection.close(); + } + } + } + + public void testServerPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception + { + try + { + Connection conn = getConnection("test", "server", "guest"); + + ((AMQConnection) conn).setConnectionListener(this); + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession<?, ?>) session).createProducer(null); + + Queue queue = session.createQueue("Invalid"); + + // Send a message that we will wait to be sent, this should give the broker time to close the connection + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + // Test the connection with a valid consumer + // This may not work as the session may be closed before the queue or consumer creation can occur. + // The correct JMSexception with linked error will only occur when the close method is recevied whilst in + // the failover safe block + session.createConsumer(session.createQueue("example.RequestQueue")).close(); + + //Connection should now be closed and will throw the exception caused by the above send + conn.close(); + + fail("Close is not expected to succeed."); + } + catch (IllegalStateException e) + { + _logger.info("QPID-2345: Session became closed and we got that error rather than the authentication error."); + } + catch (JMSException e) + { + check403Exception(e.getLinkedException()); + } + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java new file mode 100644 index 0000000000..f40e95885d --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java @@ -0,0 +1,298 @@ +/* + * 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.security.firewall; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import javax.jms.Connection; +import javax.jms.JMSException; + +import org.apache.qpid.client.AMQConnectionURL; +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +public class FirewallConfigTest extends QpidBrokerTestCase +{ + private File _tmpConfig, _tmpVirtualhosts; + + @Override + protected void setUp() throws Exception + { + // do setup + final String QPID_HOME = System.getProperty("QPID_HOME"); + + if (QPID_HOME == null) + { + fail("QPID_HOME not set"); + } + + // Setup initial config file. + _configFile = new File(QPID_HOME, "etc/config-systests-firewall.xml"); + + // Setup temporary config file + _tmpConfig = File.createTempFile("config-systests-firewall", ".xml"); + setSystemProperty("QPID_FIREWALL_CONFIG_SETTINGS", _tmpConfig.getAbsolutePath()); + _tmpConfig.deleteOnExit(); + + // Setup temporary virtualhosts file + _tmpVirtualhosts = File.createTempFile("virtualhosts-systests-firewall", ".xml"); + setSystemProperty("QPID_FIREWALL_VIRTUALHOSTS_SETTINGS", _tmpVirtualhosts.getAbsolutePath()); + _tmpVirtualhosts.deleteOnExit(); + } + + private void writeFirewallFile(boolean allow, boolean inVhost) throws IOException + { + FileWriter out = new FileWriter(inVhost ? _tmpVirtualhosts : _tmpConfig); + String ipAddr = "127.0.0.1"; // FIXME: get this from InetAddress.getLocalHost().getAddress() ? + if (inVhost) + { + out.write("<virtualhosts><virtualhost><test>"); + } + else + { + out.write("<broker>"); + } + out.write("<security><firewall>"); + out.write("<rule access=\""+((allow) ? "allow" : "deny")+"\" network=\""+ipAddr +"\"/>"); + out.write("</firewall></security>"); + if (inVhost) + { + out.write("</test></virtualhost></virtualhosts>"); + } + else + { + out.write("</broker>"); + } + out.close(); + } + + public void testVhostAllowBrokerDeny() throws Exception + { + if (_broker.equals(VM)) + { + //No point running this test with an InVM broker as the + //firewall plugin only functions for TCP connections. + return; + } + + _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-2.xml"); + + super.setUp(); + + Connection conn = null; + try + { + //Try to get a connection to the 'test2' vhost + //This is expected to succeed as it is allowed at the vhost level + conn = getConnection(new AMQConnectionURL("amqp://guest:guest@clientid/test2?brokerlist='" + getBroker() + "'")); + } + catch (JMSException e) + { + e.getLinkedException().printStackTrace(); + fail("The connection was expected to succeed: " + e.getMessage()); + } + + conn = null; + try + { + //Try to get a connection to the 'test' vhost + //This is expected to fail as it is denied at the broker level + conn = getConnection(); + fail("We expected the connection to fail"); + } + catch (JMSException e) + { + //ignore + } + } + + public void testVhostDenyBrokerAllow() throws Exception + { + if (_broker.equals(VM)) + { + //No point running this test with an InVM broker as the + //firewall plugin only functions for TCP connections. + return; + } + + _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-3.xml"); + + super.setUp(); + + Connection conn = null; + try + { + //Try to get a connection to the 'test2' vhost + //This is expected to fail as it is denied at the vhost level + conn = getConnection(new AMQConnectionURL("amqp://guest:guest@clientid/test2?brokerlist='" + getBroker() + "'")); + fail("The connection was expected to fail"); + } + catch (JMSException e) + { + //ignore + } + + conn = null; + try + { + //Try to get a connection to the 'test' vhost + //This is expected to succeed as it is allowed at the broker level + conn = getConnection(); + } + catch (JMSException e) + { + e.getLinkedException().printStackTrace(); + fail("The connection was expected to succeed: " + e.getMessage()); + } + } + + public void testDenyOnRestart() throws Exception + { + testDeny(false, new Runnable() { + + public void run() + { + try + { + restartBroker(); + } catch (Exception e) + { + fail(e.getMessage()); + } + } + }); + } + + public void testDenyOnRestartInVhost() throws Exception + { + testDeny(true, new Runnable() { + + public void run() + { + try + { + restartBroker(); + } catch (Exception e) + { + fail(e.getMessage()); + } + } + }); + } + + public void testAllowOnReloadInVhost() throws Exception + { + testFirewall(false, true, new Runnable() { + + public void run() + { + try + { + reloadBrokerSecurityConfig(); + } catch (Exception e) + { + fail(e.getMessage()); + } + } + }); + } + + public void testDenyOnReload() throws Exception + { + testDeny(false, new Runnable() { + + public void run() + { + try + { + reloadBrokerSecurityConfig(); + } catch (Exception e) + { + fail(e.getMessage()); + } + } + } + ); + } + + public void testDenyOnReloadInVhost() throws Exception + { + testDeny(true, new Runnable() { + + public void run() + { + try + { + reloadBrokerSecurityConfig(); + } catch (Exception e) + { + fail(e.getMessage()); + } + } + } + ); + + } + + private void testDeny(boolean inVhost, Runnable restartOrReload) throws Exception + { + testFirewall(true, inVhost, restartOrReload); + } + + /* + * Check we can get a connection + */ + private boolean checkConnection() throws Exception + { + Exception exception = null; + Connection conn = null; + try + { + conn = getConnection(); + } + catch (JMSException e) + { + exception = e; + } + + return conn != null; + } + + private void testFirewall(boolean initial, boolean inVhost, Runnable restartOrReload) throws Exception + { + if (_broker.equals(VM)) + { + // No point running this test in a vm broker + return; + } + + writeFirewallFile(initial, inVhost); + setConfigurationProperty("management.enabled", String.valueOf(true)); + super.setUp(); + + assertEquals("Initial connection check failed", initial, checkConnection()); + + // Reload changed firewall file after restart or reload + writeFirewallFile(!initial, inVhost); + restartOrReload.run(); + + assertEquals("Second connection check failed", !initial, checkConnection()); + } +} |