I'm using JMS on a Spring boot client to connect to an ActiveMQ Artemis broker over SSL. The client is able to connect regardless of the validity of the certificates in the truststore and even if invalid credentials are used. How do I ensure that the broker is filtering clients out based on the configured parameters?
The acceptors
in the broker.xml
are defined as show below. The SSL acceptor uses port 61617
.
<acceptors>
<!-- Acceptor for every supported protocol -->
<acceptor name="artemis">tcp://0.0.0.0:61616?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;amqpMinLargeMessageSize=102400;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpDuplicateDetection=true;supportAdvisory=false;suppressInternalManagementObjects=false</acceptor>
<!-- AMQP Acceptor. Listens on default AMQP port for AMQP traffic.-->
<acceptor name="amqp">tcp://0.0.0.0:5672?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=AMQP;useEpoll=true;amqpCredits=1000;amqpLowCredits=300;amqpMinLargeMessageSize=102400;amqpDuplicateDetection=true</acceptor>
<!-- STOMP Acceptor. -->
<acceptor name="stomp">tcp://0.0.0.0:61613?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=STOMP;useEpoll=true</acceptor>
<!-- HornetQ Compatibility Acceptor. Enables HornetQ Core and STOMP for legacy HornetQ clients. -->
<acceptor name="hornetq">tcp://0.0.0.0:5445?anycastPrefix=jms.queue.;multicastPrefix=jms.topic.;protocols=HORNETQ,STOMP;useEpoll=true</acceptor>
<!-- MQTT Acceptor -->
<acceptor name="mqtt">tcp://0.0.0.0:1883?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=MQTT;useEpoll=false</acceptor>
<!-- SSL Acceptor -->
<acceptor name="netty-ssl-acceptor">tcp://0.0.0.0:61617?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;sslEnabled=true;keyStorePath=E:/apache-artemis-2.18.0/bin/localBroker/etc/sprink.jks;keyStorePassword=changeit;trustStorePath=E:/apache-artemis-2.18.0/bin/localBroker/etc/sprinktrust.ts;trustStorePassword=changeit;needClientAuth=true;protocols=CORE,AMQP,STOMP,HORNETQ,MQTT,OPENWIRE</acceptor>
</acceptors>
The connection factory, listener, and JmsTemplate are configured on the spring boot client as shown below
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.JMSException;
@Configuration
@EnableJms
public class MQTTConfig {
@Value("${activemq.broker-url}")
private String brokerUrl;
@Value("${activemq.ssl-url}")
private String sslUrl;
@Value("${JMS_BROKER_TRUSTSTORE}")
private String pathToTrustStore;
@Value("${JMS_BROKER_KEYSTORE}")
private String pathToKeystore;
@Value("${JMS_BROKER_TRUSTSTORE_PASSWORD}")
private String truststorePassword;
@Value("${JMS_BROKER_KEYSTORE_PASSWORD}")
private String keystorePassword;
/**
* Initialise the connection factory that will be used
*/
@Bean
public ActiveMQConnectionFactory artemisSSLConnectionFactory() {
ActiveMQConnectionFactory artemisConnectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61617?&" "sslEnabled=true&"
"trustStorePath=" pathToTrustStore "&trustStorePassword=changeit&needClientAuth=true");
artemisConnectionFactory.setUser("user");
artemisConnectionFactory.setPassword("password");
return artemisConnectionFactory;
}
/**
* Initialise {@link JmsTemplate} as required
*/
@Bean
public JmsTemplate jmsTemplate() throws JMSException {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(artemisSSLConnectionFactory());
jmsTemplate.setExplicitQosEnabled(true);
//setting PuSubDomain to true configures JmsTemplate to work with topics instead of queues
jmsTemplate.setPubSubDomain(true);
jmsTemplate.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
jmsTemplate.setDeliveryPersistent(true);
return jmsTemplate;
}
/**
* Initialise {@link DefaultJmsListenerContainerFactory} as required
*/
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() throws JMSException {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(artemisSSLConnectionFactory());
//setting PuSubDomain to true configures the DefaultJmsListenerContainerFactory to work with topics instead of queues
factory.setPubSubDomain(true);
return factory;
}
}
The artemis-users.properties
file is as shown below
admin = ENC(1024...)
system=manager
user=password
guest=password
The artemis-roles.properties
has the below roles defined
admins = admin
users=user
Admins and users have been given privileges in the broker.xml
as shown below
<security-settings>
<security-setting match="#">
<permission type="createNonDurableQueue" roles="admins, users"/>
<permission type="deleteNonDurableQueue" roles="admins, users"/>
<permission type="createDurableQueue" roles="admins, users"/>
<permission type="deleteDurableQueue" roles="admins, users"/>
<permission type="createAddress" roles="admins, users"/>
<permission type="deleteAddress" roles="admins, users"/>
<permission type="consume" roles="admins, users"/>
<permission type="browse" roles="admins, users"/>
<permission type="send" roles="admins, users"/>
<!-- we need this otherwise ./artemis data imp wouldn't work -->
<permission type="manage" roles="admins"/>
</security-setting>
</security-settings>
With the above setup any client with any truststore or username and password is able to connect to the broker on port 61617
and produce and consume messages. What am I missing that is allowing this to happen?
CodePudding user response:
Here's what got things working.
Firstly, the login.config file on Artemis has a GuestLoginModule that this link says is chained to the PropertiesLoginModule and the guest module allows clients without credentials, or even invalid credentials to connect to the broker. Now, by default, the GuestLoginModule looks like this
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule sufficient
debug=false
org.apache.activemq.jaas.guest.user="admin"
org.apache.activemq.jaas.guest.role="admins";
Notice that the guests are treated as admins. My artemis-users.properties file already has a guest user defined (as seen in my original post above), so I created a "guests" group in the artemis-roles.properties file and assigned this guest user to this group. I then changed the admin mapping in the GuestLoginModule so that the module would reference guests. The GuestLoginModule now looks like this
org.apache.activemq.artemis.spi.core.security.jaas.GuestLoginModule sufficient
debug=false
org.apache.activemq.jaas.guest.user="guest"
org.apache.activemq.jaas.guest.role="guests";
The security settings in the broker.xml file can be modified to accommodate guest functions as per one's relevant use case, mine doesn't have any use case for a guest user, so guests have no permissions.
Once this was done, I tried connecting to the broker and got the null cert chain exception. Fixed this by including the keystore in the connection factory config. Because needClientAuth is set to true(dual auth is enabled), Artemis needs the clients to have the relevant keystore built by bundling the key pair derived from the root CA, so my connection factory configuration changed from
@Bean
public ActiveMQConnectionFactory artemisSSLConnectionFactory() {
ActiveMQConnectionFactory artemisConnectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61617?&" "sslEnabled=true&"
"trustStorePath=" pathToTrustStore "&trustStorePassword=changeit&needClientAuth=true");
artemisConnectionFactory.setUser("user");
artemisConnectionFactory.setPassword("password");
return artemisConnectionFactory;
}
to
@Bean
public ActiveMQConnectionFactory artemisSSLConnectionFactory() {
ActiveMQConnectionFactory artemisConnectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61617?&" "sslEnabled=true&"
"trustStorePath=" pathToTrustStore "&trustStorePassword=changeit&keyStorePath=" pathToKeystore "&keyStorePassword=changeit&needClientAuth=true");
artemisConnectionFactory.setUser("user");
artemisConnectionFactory.setPassword("password");
return artemisConnectionFactory;
}
The only difference here is the addition of the keystore path and password. The broker did not connect when it was just the truststore in the config. If one way auth is needed, just omit the needClientAuth field in the acceptor as it is set to false by default.
This is what finally worked.