ARTEMIS-2574 allow security manager config via XML

The test-suite has long used the broker's ability to configure the
security manager. This commit implements this functionality via XML
configuration.
This commit is contained in:
Justin Bertram 2019-12-10 15:15:21 -06:00 committed by Clebert Suconic
parent 49d3a88703
commit c06404406c
22 changed files with 988 additions and 5 deletions

View File

@ -0,0 +1,42 @@
/*
* 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.activemq.artemis.cli.factory.security;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import org.apache.activemq.artemis.dto.SecurityManagerDTO;
import org.apache.activemq.artemis.dto.PropertyDTO;
import org.apache.activemq.artemis.dto.SecurityDTO;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.utils.ClassloadingUtil;
public class SecurityManagerHandler implements SecurityHandler {
@Override
public ActiveMQSecurityManager createSecurityManager(SecurityDTO security) {
SecurityManagerDTO customSecurity = (SecurityManagerDTO) security;
String clazz = customSecurity.className;
Map<String, String> properties = new HashMap<>();
for (PropertyDTO property : customSecurity.properties) {
properties.put(property.key, property.value);
}
return AccessController.doPrivileged((PrivilegedAction<ActiveMQSecurityManager>) () -> (ActiveMQSecurityManager) ClassloadingUtil.newInstanceFromClassLoader(SecurityManagerHandler.class, clazz)).init(properties);
}
}

View File

@ -0,0 +1,17 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
class=org.apache.activemq.artemis.cli.factory.security.SecurityManagerHandler

View File

@ -28,6 +28,10 @@ import javax.json.JsonObject;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
@ -43,6 +47,8 @@ import org.apache.activemq.artemis.api.core.JsonUtil;
import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.Pair;
import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ClientProducer;
import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.api.core.client.ServerLocator;
@ -62,12 +68,13 @@ import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl; import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
import org.apache.activemq.artemis.core.config.FileDeploymentManager; import org.apache.activemq.artemis.core.config.FileDeploymentManager;
import org.apache.activemq.artemis.core.config.impl.FileConfiguration; import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.core.server.JournalType;
import org.apache.activemq.artemis.core.server.management.ManagementContext; import org.apache.activemq.artemis.core.server.management.ManagementContext;
import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.apache.activemq.artemis.jms.client.ActiveMQDestination; import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec; import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
import org.apache.activemq.artemis.utils.HashProcessor; import org.apache.activemq.artemis.utils.HashProcessor;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil; import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
@ -81,6 +88,8 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -235,6 +244,84 @@ public class ArtemisTest extends CliTestBase {
assertEquals("password2", trustPass); assertEquals("password2", trustPass);
} }
@Test
public void testSecurityManagerConfiguration() throws Exception {
setupAuth();
Run.setEmbedded(true);
File instance1 = new File(temporaryFolder.getRoot(), "instance1");
Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-fsync", "--no-autotune", "--no-web", "--no-stomp-acceptor", "--no-amqp-acceptor", "--no-mqtt-acceptor", "--no-hornetq-acceptor");
File originalBootstrapFile = new File(new File(instance1, "etc"), "bootstrap.xml");
assertTrue(originalBootstrapFile.exists());
// modify the XML programmatically since we don't support setting this stuff via the CLI
Document config = parseXml(originalBootstrapFile);
Node broker = config.getChildNodes().item(1);
NodeList list = broker.getChildNodes();
Node server = null;
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if ("jaas-security".equals(node.getNodeName())) {
// get rid of the default jaas-security config
broker.removeChild(node);
}
if ("server".equals(node.getNodeName())) {
server = node;
}
}
// add the new security-plugin config
Element securityPluginElement = config.createElement("security-manager");
securityPluginElement.setAttribute("class-name", TestSecurityManager.class.getName());
Element property1 = config.createElement("property");
property1.setAttribute("key", "myKey1");
property1.setAttribute("value", "myValue1");
securityPluginElement.appendChild(property1);
Element property2 = config.createElement("property");
property2.setAttribute("key", "myKey2");
property2.setAttribute("value", "myValue2");
securityPluginElement.appendChild(property2);
broker.insertBefore(securityPluginElement, server);
originalBootstrapFile.delete();
// write the modified config into the bootstrap.xml file
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(config);
StreamResult streamResult = new StreamResult(originalBootstrapFile);
transformer.transform(source, streamResult);
System.setProperty("artemis.instance", instance1.getAbsolutePath());
Object result = Artemis.internalExecute("run");
ActiveMQServer activeMQServer = ((Pair<ManagementContext, ActiveMQServer>)result).getB();
// trigger security
ServerLocator locator = ActiveMQClient.createServerLocator("tcp://127.0.0.1:61616");
ClientSessionFactory sessionFactory = locator.createSessionFactory();
ClientSession session = sessionFactory.createSession("myUser", "myPass", false, true, true, false, 0);
ClientProducer producer = session.createProducer("foo");
producer.send(session.createMessage(true));
// verify results
assertTrue(activeMQServer.getSecurityManager() instanceof TestSecurityManager);
TestSecurityManager securityPlugin = (TestSecurityManager) activeMQServer.getSecurityManager();
assertTrue(securityPlugin.properties.containsKey("myKey1"));
assertEquals("myValue1", securityPlugin.properties.get("myKey1"));
assertTrue(securityPlugin.properties.containsKey("myKey2"));
assertEquals("myValue2", securityPlugin.properties.get("myKey2"));
assertTrue(securityPlugin.validateUser);
assertEquals("myUser", securityPlugin.validateUserName);
assertEquals("myPass", securityPlugin.validateUserPass);
assertTrue(securityPlugin.validateUserAndRole);
assertEquals("myUser", securityPlugin.validateUserAndRoleName);
assertEquals("myPass", securityPlugin.validateUserAndRolePass);
assertEquals(CheckType.SEND, securityPlugin.checkType);
activeMQServer.stop();
stopServer();
}
@Test @Test
public void testStopManagementContext() throws Exception { public void testStopManagementContext() throws Exception {
Run.setEmbedded(true); Run.setEmbedded(true);

View File

@ -0,0 +1,62 @@
/**
* 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.activemq.cli.test;
import java.util.Map;
import java.util.Set;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
/**
* This is a simple *test* security manager used to verify configuration via bootstrap.xml.
*/
public class TestSecurityManager implements ActiveMQSecurityManager {
public Map<String, String> properties;
public boolean validateUser = false;
public String validateUserName;
public String validateUserPass;
public boolean validateUserAndRole = false;
public String validateUserAndRoleName;
public String validateUserAndRolePass;
public CheckType checkType;
@Override
public boolean validateUser(String user, String password) {
this.validateUser = true;
this.validateUserName = user;
this.validateUserPass = password;
return true;
}
@Override
public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) {
this.validateUserAndRole = true;
this.validateUserAndRoleName = user;
this.validateUserAndRolePass = password;
this.checkType = checkType;
return true;
}
@Override
public ActiveMQSecurityManager init(Map<String, String> properties) {
this.properties = properties;
return this;
}
}

View File

@ -81,6 +81,7 @@ cd rest; mvn verify; cd ..
cd scheduled-message; mvn verify; cd .. cd scheduled-message; mvn verify; cd ..
cd security; mvn verify; cd .. cd security; mvn verify; cd ..
cd security-ldap; mvn verify; cd .. cd security-ldap; mvn verify; cd ..
cd security-manager; mvn verify; cd ..
cd send-acknowledgements; mvn verify; cd .. cd send-acknowledgements; mvn verify; cd ..
cd shared-consumer; mvn verify; cd .. cd shared-consumer; mvn verify; cd ..
cd slow-consumer; mvn verify; cd .. cd slow-consumer; mvn verify; cd ..

View File

@ -80,6 +80,7 @@ cd rest; mvn verify; cd ..
cd scheduled-message; mvn verify; cd .. cd scheduled-message; mvn verify; cd ..
cd security; mvn verify; cd .. cd security; mvn verify; cd ..
cd security-ldap; mvn verify; cd .. cd security-ldap; mvn verify; cd ..
cd security-manager; mvn verify; cd ..
cd send-acknowledgements; mvn verify; cd .. cd send-acknowledgements; mvn verify; cd ..
cd shared-consumer; mvn verify; cd .. cd shared-consumer; mvn verify; cd ..
cd slow-consumer; mvn verify; cd .. cd slow-consumer; mvn verify; cd ..

View File

@ -0,0 +1,33 @@
/*
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "property")
@XmlAccessorType(XmlAccessType.FIELD)
public class PropertyDTO {
@XmlAttribute
public String key;
@XmlAttribute
public String value;
}

View File

@ -0,0 +1,35 @@
/*
* 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.activemq.artemis.dto;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.List;
@XmlRootElement(name = "security-manager")
@XmlAccessorType(XmlAccessType.FIELD)
public class SecurityManagerDTO extends SecurityDTO {
@XmlAttribute(name = "class-name", required = true)
public String className;
@XmlElementRef
public List<PropertyDTO> properties;
}

View File

@ -17,5 +17,6 @@
BrokerDTO BrokerDTO
SecurityDTO SecurityDTO
JaasSecurityDTO JaasSecurityDTO
SecurityManagerDTO
ManagementContextDTO ManagementContextDTO

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.activemq.artemis.spi.core.security; package org.apache.activemq.artemis.spi.core.security;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.CheckType;
@ -50,4 +51,16 @@ public interface ActiveMQSecurityManager {
* @return true if the user is valid and they have the correct roles * @return true if the user is valid and they have the correct roles
*/ */
boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType); boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType);
/**
* Initialize the manager with the given configuration properties. This method is called by the broker when the
* file-based configuration is read. If you're creating/configuring the plugin programmatically then the
* recommended approach is to simply use the manager's getters/setters rather than this method.
*
* @param properties name/value pairs used to configure the ActiveMQSecurityManager instance
* @return {@code this} instance
*/
default ActiveMQSecurityManager init(Map<String, String> properties) {
return this;
}
} }

View File

@ -1,8 +1,10 @@
# Security # Security
This chapter describes how security works with Apache ActiveMQ Artemis and how This chapter describes how security works with Apache ActiveMQ Artemis and how
you can configure it. To disable security completely simply set the you can configure it.
`security-enabled` property to false in the `broker.xml` file.
To disable security completely simply set the `security-enabled` property to
`false` in the `broker.xml` file.
For performance reasons security is cached and invalidated every so long. To For performance reasons security is cached and invalidated every so long. To
change this period set the property `security-invalidation-interval`, which is change this period set the property `security-invalidation-interval`, which is
@ -1202,3 +1204,27 @@ The param-value for each list is a comma separated string value representing the
For details about masking passwords in broker.xml please see the [Masking For details about masking passwords in broker.xml please see the [Masking
Passwords](masking-passwords.md) chapter. Passwords](masking-passwords.md) chapter.
## Custom Security Manager
The underpinnings of the broker's security implementation can be changed if so
desired. The broker uses a component called a "security manager" to implement
the actual authentication and authorization checks. By default, the broker uses
`org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager` to
provide JAAS integration, but users can provide their own implementation of
`org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3` and
configure it in `bootstrap.xml` using the `security-manager` element, e.g.:
```xml
<broker xmlns="http://activemq.org/schema">
<security-manager class-name="com.foo.MySecurityManager">
<property key="myKey1" value="myValue1"/>
<property key="myKey2" value="myValue2"/>
</security-manager>
...
</broker>
```
The `security-manager` example demonstrates how to do this is more detail.

View File

@ -438,8 +438,12 @@ The bootstrap file is very simple. Let's take a look at an example:
`configuration` attribute. This is the main broker POJO necessary to do all `configuration` attribute. This is the main broker POJO necessary to do all
the real messaging work. the real messaging work.
- `jaas-security` - Configures security for the server. The `domain` attribute - `jaas-security` - Configures JAAS-based security for the server. The
refers to the relevant login module entry in `login.config`. `domain` attribute refers to the relevant login module entry in
`login.config`. If different behavior is needed then a custom security
manager can be configured by replacing `jaas-security` with
`security-manager`. See the "Custom Security Manager" section in the
[security chapter](security.md) for more details.
- `web` - Configures an embedded Jetty instance to serve web applications like - `web` - Configures an embedded Jetty instance to serve web applications like
the admin console. the admin console.

View File

@ -91,6 +91,7 @@ under the License.
<module>scheduled-message</module> <module>scheduled-message</module>
<module>security</module> <module>security</module>
<module>security-ldap</module> <module>security-ldap</module>
<module>security-manager</module>
<module>send-acknowledgements</module> <module>send-acknowledgements</module>
<module>shared-consumer</module> <module>shared-consumer</module>
<module>slow-consumer</module> <module>slow-consumer</module>
@ -164,6 +165,7 @@ under the License.
<module>scheduled-message</module> <module>scheduled-message</module>
<module>security</module> <module>security</module>
<module>security-ldap</module> <module>security-ldap</module>
<module>security-manager</module>
<module>send-acknowledgements</module> <module>send-acknowledgements</module>
<module>shared-consumer</module> <module>shared-consumer</module>
<module>slow-consumer</module> <module>slow-consumer</module>

View File

@ -0,0 +1,137 @@
<?xml version='1.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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.activemq.examples.broker</groupId>
<artifactId>jms-examples</artifactId>
<version>2.11.0-SNAPSHOT</version>
</parent>
<artifactId>security-manager</artifactId>
<packaging>jar</packaging>
<name>ActiveMQ Artemis JMS Security Manager Example</name>
<properties>
<activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client-all</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-server</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-maven-plugin</artifactId>
<executions>
<execution>
<id>create</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<libList>
<!-- For the security manager -->
<arg>org.apache.activemq.examples.broker:security-manager:${project.version}</arg>
</libList>
<ignore>${noServer}</ignore>
<allowAnonymous>false</allowAnonymous>
</configuration>
</execution>
<execution>
<id>start</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<testURI>tcp://localhost:61616</testURI>
<testUser>bill</testUser>
<testPassword>activemq</testPassword>
<args>
<param>run</param>
</args>
</configuration>
</execution>
<execution>
<id>runClient</id>
<goals>
<goal>runClient</goal>
</goals>
<configuration>
<clientClass>org.apache.activemq.artemis.jms.example.SecurityManagerExample</clientClass>
</configuration>
</execution>
<execution>
<id>stop</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.activemq.examples.broker</groupId>
<artifactId>security-manager</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>markdown-page-generator-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,5 @@
# JMS Security Manager Example
To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start and create the broker manually.
This example is based on the "security" example and demonstrates how to implement a custom security manager. The custom security manager in this example simply logs details for authentication and authorization and then passes everything through to an instance of `org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager` (i.e. the default security manager).

View File

@ -0,0 +1,72 @@
/**
* 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.activemq.artemis.jms.example;
import java.util.Map;
import java.util.Set;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager3 {
ActiveMQJAASSecurityManager activeMQJAASSecurityManager;
@Override
public String validateUser(String user, String password, RemotingConnection remotingConnection) {
System.out.println("validateUser(" + user + ", " + password + ", " + remotingConnection.getRemoteAddress() + ")");
return activeMQJAASSecurityManager.validateUser(user, password, remotingConnection);
}
@Override
public String validateUserAndRole(String user,
String password,
Set<Role> roles,
CheckType checkType,
String address,
RemotingConnection remotingConnection) {
System.out.println("validateUserAndRole(" + user + ", " + password + ", " + roles + ", " + checkType + ", " + address + ", " + remotingConnection.getRemoteAddress() + ")");
return activeMQJAASSecurityManager.validateUserAndRole(user, password, roles, checkType, address, remotingConnection);
}
@Override
public String getDomain() {
return activeMQJAASSecurityManager.getDomain();
}
@Override
public boolean validateUser(String user, String password) {
return activeMQJAASSecurityManager.validateUser(user, password);
}
@Override
public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) {
return activeMQJAASSecurityManager.validateUserAndRole(user, password, roles, checkType);
}
@Override
public ActiveMQSecurityManager init(Map<String, String> properties) {
activeMQJAASSecurityManager = new ActiveMQJAASSecurityManager(properties.get("domain"));
return this;
}
}

View File

@ -0,0 +1,271 @@
/*
* 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.activemq.artemis.jms.example;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;
public class SecurityManagerExample {
public static void main(final String[] args) throws Exception {
boolean result = true;
Connection failConnection = null;
Connection billConnection = null;
Connection andrewConnection = null;
Connection frankConnection = null;
Connection samConnection = null;
InitialContext initialContext = null;
try {
// /Step 1. Create an initial context to perform the JNDI lookup.
initialContext = new InitialContext();
// Step 2. perform lookup on the topics
Topic genericTopic = (Topic) initialContext.lookup("topic/genericTopic");
Topic europeTopic = (Topic) initialContext.lookup("topic/europeTopic");
Topic usTopic = (Topic) initialContext.lookup("topic/usTopic");
// Step 3. perform a lookup on the Connection Factory
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
// Step 4. Try to create a JMS Connection without user/password. It will fail.
try {
failConnection = cf.createConnection();
result = false;
} catch (JMSSecurityException e) {
System.out.println("Default user cannot get a connection. Details: " + e.getMessage());
}
// Step 5. bill tries to make a connection using wrong password
try {
billConnection = createConnection("bill", "activemq1", cf);
result = false;
} catch (JMSException e) {
System.out.println("User bill failed to connect. Details: " + e.getMessage());
}
// Step 6. bill makes a good connection.
billConnection = createConnection("bill", "activemq", cf);
billConnection.start();
// Step 7. andrew makes a good connection.
andrewConnection = createConnection("andrew", "activemq1", cf);
andrewConnection.start();
// Step 8. frank makes a good connection.
frankConnection = createConnection("frank", "activemq2", cf);
frankConnection.start();
// Step 9. sam makes a good connection.
samConnection = createConnection("sam", "activemq3", cf);
samConnection.start();
// Step 10. Check every user can publish/subscribe genericTopics.
System.out.println("------------------------Checking permissions on " + genericTopic + "----------------");
checkUserSendAndReceive(genericTopic, billConnection, "bill");
checkUserSendAndReceive(genericTopic, andrewConnection, "andrew");
checkUserSendAndReceive(genericTopic, frankConnection, "frank");
checkUserSendAndReceive(genericTopic, samConnection, "sam");
System.out.println("-------------------------------------------------------------------------------------");
System.out.println("------------------------Checking permissions on " + europeTopic + "----------------");
// Step 11. Check permissions on news.europe.europeTopic for bill: can't send and can't receive
checkUserNoSendNoReceive(europeTopic, billConnection, "bill");
// Step 12. Check permissions on news.europe.europeTopic for andrew: can send but can't receive
checkUserSendNoReceive(europeTopic, andrewConnection, "andrew", frankConnection);
// Step 13. Check permissions on news.europe.europeTopic for frank: can't send but can receive
checkUserReceiveNoSend(europeTopic, frankConnection, "frank", andrewConnection);
// Step 14. Check permissions on news.europe.europeTopic for sam: can't send but can receive
checkUserReceiveNoSend(europeTopic, samConnection, "sam", andrewConnection);
System.out.println("-------------------------------------------------------------------------------------");
System.out.println("------------------------Checking permissions on " + usTopic + "----------------");
// Step 15. Check permissions on news.us.usTopic for bill: can't send and can't receive
checkUserNoSendNoReceive(usTopic, billConnection, "bill");
// Step 16. Check permissions on news.us.usTopic for andrew: can't send and can't receive
checkUserNoSendNoReceive(usTopic, andrewConnection, "andrew");
// Step 17. Check permissions on news.us.usTopic for frank: can both send and receive
checkUserSendAndReceive(usTopic, frankConnection, "frank");
// Step 18. Check permissions on news.us.usTopic for sam: can't send but can receive
checkUserReceiveNoSend(usTopic, samConnection, "sam", frankConnection);
System.out.println("-------------------------------------------------------------------------------------");
} finally {
// Step 19. Be sure to close our JMS resources!
if (failConnection != null) {
failConnection.close();
}
if (billConnection != null) {
billConnection.close();
}
if (andrewConnection != null) {
andrewConnection.close();
}
if (frankConnection != null) {
frankConnection.close();
}
if (samConnection != null) {
samConnection.close();
}
// Also the initialContext
if (initialContext != null) {
initialContext.close();
}
}
}
// Check the user can receive message but cannot send message.
private static void checkUserReceiveNoSend(final Topic topic,
final Connection connection,
final String user,
final Connection sendingConn) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
MessageConsumer consumer = session.createConsumer(topic);
TextMessage msg = session.createTextMessage("hello-world-1");
try {
producer.send(msg);
throw new IllegalStateException("Security setting is broken! User " + user +
" can send message [" +
msg.getText() +
"] to topic " +
topic);
} catch (JMSException e) {
System.out.println("User " + user + " cannot send message [" + msg.getText() + "] to topic: " + topic);
}
// Now send a good message
Session session1 = sendingConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session1.createProducer(topic);
producer.send(msg);
TextMessage receivedMsg = (TextMessage) consumer.receive(2000);
if (receivedMsg != null) {
System.out.println("User " + user + " can receive message [" + receivedMsg.getText() + "] from topic " + topic);
} else {
throw new IllegalStateException("Security setting is broken! User " + user + " cannot receive message from topic " + topic);
}
session1.close();
session.close();
}
// Check the user can send message but cannot receive message
private static void checkUserSendNoReceive(final Topic topic,
final Connection connection,
final String user,
final Connection receivingConn) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
try {
session.createConsumer(topic);
} catch (JMSException e) {
System.out.println("User " + user + " cannot receive any message from topic " + topic);
}
Session session1 = receivingConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer goodConsumer = session1.createConsumer(topic);
TextMessage msg = session.createTextMessage("hello-world-2");
producer.send(msg);
TextMessage receivedMsg = (TextMessage) goodConsumer.receive(2000);
if (receivedMsg != null) {
System.out.println("User " + user + " can send message [" + receivedMsg.getText() + "] to topic " + topic);
} else {
throw new IllegalStateException("Security setting is broken! User " + user +
" cannot send message [" +
msg.getText() +
"] to topic " +
topic);
}
session.close();
session1.close();
}
// Check the user has neither send nor receive permission on topic
private static void checkUserNoSendNoReceive(final Topic topic,
final Connection connection,
final String user) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
try {
session.createConsumer(topic);
} catch (JMSException e) {
System.out.println("User " + user + " cannot create consumer on topic " + topic);
}
TextMessage msg = session.createTextMessage("hello-world-3");
try {
producer.send(msg);
throw new IllegalStateException("Security setting is broken! User " + user +
" can send message [" +
msg.getText() +
"] to topic " +
topic);
} catch (JMSException e) {
System.out.println("User " + user + " cannot send message [" + msg.getText() + "] to topic: " + topic);
}
session.close();
}
// Check the user connection has both send and receive permissions on the topic
private static void checkUserSendAndReceive(final Topic topic,
final Connection connection,
final String user) throws JMSException {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
TextMessage msg = session.createTextMessage("hello-world-4");
MessageProducer producer = session.createProducer(topic);
MessageConsumer consumer = session.createConsumer(topic);
producer.send(msg);
TextMessage receivedMsg = (TextMessage) consumer.receive(5000);
if (receivedMsg != null) {
System.out.println("User " + user + " can send message: [" + msg.getText() + "] to topic: " + topic);
System.out.println("User " + user + " can receive message: [" + msg.getText() + "] from topic: " + topic);
} else {
throw new IllegalStateException("Error! User " + user + " cannot receive the message! ");
}
session.close();
}
private static Connection createConnection(final String username,
final String password,
final ConnectionFactory cf) throws JMSException {
return cf.createConnection(username, password);
}
}

View File

@ -0,0 +1,20 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
user=bill,andrew,frank,sam
europe-user=andrew
news-user=frank,sam
us-user=frank

View File

@ -0,0 +1,20 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
bill = ENC(1024:020FEC8DB7EBBCB987FD25F1188EA71FA13FD4E0BF504963891EDC97E1ED1285:3E53D34A96F9995612C7C585CA04BA63CF5F531C92510E882960F848BFC3982AF47FCD40AB888F9AC10648CCEBA1DD52C0F0A312B2C90225D9A46DDC50198B3C)
andrew = ENC(1024:3E09F4D16A6970F3C40E24784AFE64AFD66349174AB20B2609109646A8F0561F:F22063143058EBCF47A0ACA1C29DBCB82C4AF15E510F5C801B47928AEA1836D1480BFD0DFD0320BA567D1A32C98859C02350AE271DC530F29D7E16E910E251AD)
frank = ENC(1024:49292EEC8AA19AB5390A0F0D67AA5A3978DE1AF0F561B641A1CE90B3C9637AAD:22A8F9A4B144B9CC173F3B1D5A2B09FE57642234534C2EB3A805DB7D5F7FEA398B58EB9380B8EA69B916B5CFA23BC7573E09A87A20C0DF1A35A1134270260BE4)
sam = ENC(1024:39832F10D9734D7E6EECE16BCEAA5E2917D384B4CE482A2A4B3D3E7A550B0A5C:CCA47914C6DD64AE6B69FE977BB445CBCDEA50D458E7F42AA341FA84A11C302E2EAB072E57B41A636589C89246911A6A49424CBA4B629F4846826183E9AD9DA1)

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ 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.
-->
<broker xmlns="http://activemq.org/schema">
<security-manager class-name="org.apache.activemq.artemis.jms.example.JAASSecurityManagerWrapper">
<property key="domain" value="activemq"/>
</security-manager>
<!-- artemis.URI.instance is parsed from artemis.instance by the CLI startup.
This is to avoid situations where you could have spaces or special characters on this URI -->
<server configuration="file:/home/jbertram/jboss/src/activemq-artemis/examples/features/standard/security-manager/target/server0/etc//broker.xml"/>
</broker>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">
<core xmlns="urn:activemq:core">
<bindings-directory>./data/messaging/bindings</bindings-directory>
<journal-directory>./data/messaging/journal</journal-directory>
<large-messages-directory>./data/messaging/largemessages</large-messages-directory>
<paging-directory>./data/messaging/paging</paging-directory>
<!-- Acceptors -->
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
</acceptors>
<!-- Other config -->
<security-settings>
<!-- any user can have full control of generic topics -->
<security-setting match="#">
<permission roles="user" type="createDurableQueue"/>
<permission roles="user" type="deleteDurableQueue"/>
<permission roles="user" type="createNonDurableQueue"/>
<permission roles="user" type="deleteNonDurableQueue"/>
<permission roles="user" type="send"/>
<permission roles="user" type="consume"/>
</security-setting>
<security-setting match="news.europe.#">
<permission roles="user" type="createDurableQueue"/>
<permission roles="user" type="deleteDurableQueue"/>
<permission roles="user" type="createNonDurableQueue"/>
<permission roles="user" type="deleteNonDurableQueue"/>
<permission roles="europe-user" type="send"/>
<permission roles="news-user" type="consume"/>
</security-setting>
<security-setting match="news.us.#">
<permission roles="user" type="createDurableQueue"/>
<permission roles="user" type="deleteDurableQueue"/>
<permission roles="user" type="createNonDurableQueue"/>
<permission roles="user" type="deleteNonDurableQueue"/>
<permission roles="us-user" type="send"/>
<permission roles="news-user" type="consume"/>
</security-setting>
</security-settings>
<addresses>
<address name="genericTopic">
<multicast/>
</address>
<address name="news.europe.europeTopic">
<multicast/>
</address>
<address name="news.us.usTopic">
<multicast/>
</address>
</addresses>
</core>
</configuration>

View File

@ -0,0 +1,22 @@
# 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.
java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
connectionFactory.ConnectionFactory=tcp://localhost:61616
topic.topic/genericTopic=genericTopic
topic.topic/europeTopic=news.europe.europeTopic
topic.topic/usTopic=news.us.usTopic