ActiveMQ Security (mit Authentifizierung und Autorisierung)

Telefunken-Tower Berlin, by Christoph Burmeister (own photo)

Telefunken-Tower Berlin, by Christoph Burmeister (own photo)


Sicherheit geht vor… zumindest, wenn die Anwendung die Entwicklungs-Phase verlĂ€sst 😉

ActiveMQ hat da ein paar nette Möglichkeiten eingebaut. Im Folgenden werden zwei Sachen dargestellt: 1) Absicherung der Webconsole und 2) Absicherung des Queue-Zugriffs.

1) Die ActiveMQ-Webconsole ist ganz praktisch, will man einfach mal einen Blick auf die Queues und Topics werfen. DefaultmĂ€ĂŸig lĂ€uft die unter http://localhost:8161/admin und wird ĂŒber einen integrierten Jetty zur VerfĂŒgung gestellt. Die conf/jetty.xml muss angepasst werden, damit die conf/jetty-realm.properties zur Authentifizierung angezogen werden. Folgendes Beispiel einfach mal mit der BASIC-Auth:

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- location of Access-Credential-File for the webconsole --> 
    <bean id="securityLoginService" class="org.eclipse.jetty.security.HashLoginService">
        <property name="name" value="ActiveMQRealm" />
        <property name="config" value="${activemq.base}/conf/jetty-realm.properties" />
    </bean>

	<!-- Access-Process for authentication -->
    <bean id="securityConstraint" class="org.eclipse.jetty.http.security.Constraint">
        <property name="name" value="BASIC" />
        <property name="roles" value="admin" />
        <property name="authenticate" value="true" />
    </bean>
	
    <bean id="securityConstraintMapping" class="org.eclipse.jetty.security.ConstraintMapping">
        <property name="constraint" ref="securityConstraint" />
        <property name="pathSpec" value="/*" />
    </bean>
	
    <bean id="securityHandler" class="org.eclipse.jetty.security.ConstraintSecurityHandler">
        <property name="loginService" ref="securityLoginService" />
        <property name="authenticator">
            <bean class="org.eclipse.jetty.security.authentication.BasicAuthenticator" />
        </property>
        <property name="constraintMappings">
            <list>
                <ref bean="securityConstraintMapping" />
            </list>
        </property>
        <property name="handler">
            <bean id="sec" class="org.eclipse.jetty.server.handler.HandlerCollection">
                <property name="handlers">
                    <list>
						<!-- the admin-web -->
                        <bean class="org.eclipse.jetty.webapp.WebAppContext">
                            <property name="contextPath" value="/admin" />
                            <property name="resourceBase" value="${activemq.home}/webapps/admin" />
                            <property name="logUrlOnStart" value="true" />
                        </bean>
						<!-- the RESTfull-Web-Access 
                        <bean class="org.eclipse.jetty.webapp.WebAppContext">
                            <property name="contextPath" value="/fileserver" />
                            <property name="resourceBase" value="${activemq.home}/webapps/fileserver" />
                            <property name="logUrlOnStart" value="true" />
                            <property name="parentLoaderPriority" value="true" />
                        </bean>-->
						
                        <bean class="org.eclipse.jetty.server.handler.ResourceHandler">
                            <property name="directoriesListed" value="false" />
                            <property name="welcomeFiles">
                                <list>
                                    <value>index.html</value>
                                </list>
                            </property>
                            <property name="resourceBase" value="${activemq.home}/webapps/" />
                        </bean>
                        <bean id="defaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler">
                            <property name="serveIcon" value="false" />
                        </bean>
                    </list>
                </property>
            </bean>
        </property>
    </bean>

    <bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection" />

    <bean id="Server" class="org.eclipse.jetty.server.Server" init-method="start"  destroy-method="stop">

        <property name="connectors">
            <list>
                <bean id="Connector" class="org.eclipse.jetty.server.nio.SelectChannelConnector">
                    <property name="port" value="8161" />
                </bean>
            </list>
        </property>

        <property name="handler">
            <bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
                <property name="handlers">
                    <list>
                        <ref bean="contexts" />
                        <ref bean="securityHandler" />
                    </list>
                </property>
            </bean>
        </property>

    </bean>

</beans>

dazu dann ein Beispiel fĂŒr die jetty-real.properties:

# Defines users that can access the web (console, demo, etc.)
# username: password [,rolename ...]
admin: topsecret, admin

Soweit so gut. Jetzt kann zumindest keiner mehr ohne in der Realm-Datei zu stehen, auf das Web raufgucken. Trotdem kann man ja mit ein bisschen Wissen ĂŒber Standort und Benamung der Queues einfache einen JMS-Client so konfigurieren, dass er auf die existierenden Queues lauscht. Wollen wir auch unterbinden 🙂

DafĂŒr kann man das Broker-Element in der activemq.xml um ein paar selbstsprechende Plugins erweitern:
Authentifizierung:
Das ist der erste Schritt. Hierbei wird erstmal sichergestellt, dass der zugreifende Consumer ĂŒberhaupt der ist, der er zu sein behauptet. ActiveMQ bietet hierfĂŒr das SimpleAuthentication-Plugin:

<simpleAuthenticationPlugin anonymousAccessAllowed="false">
   <users>
      <authenticationUser username="writer1" password="topsecret" groups="queue_writers"/>
      <authenticationUser username="reader1" password="topsecret" groups="queue_readers"/>
      <authenticationUser username="admin"   password="topsecret" groups="admins"/>
   </users>
</simpleAuthenticationPlugin>

Autorisation:
Nachdem der Consumer bewiesen hat, der zu sein, als der er sich ausgibt, muss noch entschieden werden, dass er auch das machen darf, was er vorhat. Mittels des Authorization-Plugins ermöglicht eine Destination-basierte Autorisierung:

<authorizationPlugin>
   <map>
      <authorizationMap>
         <authorizationEntries>
            <authorizationEntry queue="outbox" read="queue_readers" write="queue_writers" admin="admins" />   
         </authorizationEntries>
      </authorizationMap>
   </map>
</authorizationPlugin>

Beide Plugins muss man noch in ein


-Tag packen und diese (Achtung alfabetisch !!!) in das Broker-Tag packen… also folgendermaßen:

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.base}/data" destroyApplicationContextOnStop="true">
		<destinationPolicy>
            <policyMap>
              <policyEntries>
                <policyEntry topic=">" producerFlowControl="true" memoryLimit="1mb">
                  <pendingSubscriberPolicy>
                    <vmCursor />
                  </pendingSubscriberPolicy>
                </policyEntry>
                <policyEntry queue=">" producerFlowControl="true" memoryLimit="1mb">
                  <!-- Use VM cursor for better latency
                       For more information, see:
                       
                       http://activemq.apache.org/message-cursors.html
                       
                  <pendingQueuePolicy>
                    <vmQueueCursor/>
                  </pendingQueuePolicy>
                  -->
                </policyEntry>
              </policyEntries>
            </policyMap>
        </destinationPolicy> 
      
		<managementContext>
            <managementContext createConnector="false"/>
        </managementContext>
       
        <persistenceAdapter>
            <kahaDB directory="${activemq.base}/data/kahadb"/>
        </persistenceAdapter>
		
		<plugins>		  
			<!-- the authorization -->
			<authorizationPlugin>
				<map>
					<authorizationMap>
						<authorizationEntries>
							<authorizationEntry queue="outbox" read="queue_readers" write="queue_writers" admin="admins" />   
						</authorizationEntries>
					</authorizationMap>
				</map>
			</authorizationPlugin>
		
			<!-- some authentication -->
			<simpleAuthenticationPlugin anonymousAccessAllowed="false">
				<users>
					<authenticationUser username="writer1" 	password="topsecret"	groups="queue_writers"/>
					<authenticationUser username="reader1" 	password="topsecret"	groups="queue_readers"/>
					<authenticationUser username="admin" 	password="topsecret"	groups="admins"/>
				</users>
			</simpleAuthenticationPlugin>
		</plugins>
      
        <transportConnectors>
            <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/>
        </transportConnectors>
		
    </broker>

Auf Client-Seite sieht man dann bei Verwendung des JMS-Consumers aus dem letzten Beitrag, dass folgende Fehlermeldung geworfen wird:

Could not refresh JMS Connection for destination 'queue://outbox' - retrying in 10000 ms. Cause: User name or password is invalid.

Klar, da ist ja auch noch nix fĂŒr konfiguriert 🙂 Machen wir aber jetzt, indem wir einfach die ConnectionFactory des Consumers anpassen:

   <bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
      <property name="brokerURL" value="tcp://127.0.0.1:61616" />
      <property name="userName" value="reader1" />
      <property name="password" value="topsecret" />
   </bean>

So, sollte jetzt eigentlich gehen, aber leider wird jetzt folgende Fehlermeldung geworfen:

Could not refresh JMS Connection for destination 'queue://outbox' - retrying in 10000 ms. Cause: User reader1 is not authorized to create: topic://ActiveMQ.Advisory.Connection

Hmm, hÀtte man die Doku weiterlesen sollen:
„Note that full access rights should always be given to the ActiveMQ.Advisory destinations, else your client will receive an exception stating it does not have access rights to these series of destinations.“

Also muss man das Authorization-Plugin folgendermaßen noch anpassen:

<!-- the authorization -->
<authorizationPlugin>
   <map>
      <authorizationMap>
         <authorizationEntries>
            <authorizationEntry queue="outbox" read="queue_readers" write="queue_writers" admin="admins" />   
            <authorizationEntry topic="ActiveMQ.Advisory.>" read="queue_readers, queue_writers, admins" write="queue_readers, queue_writers, admins" admin="queue_readers, queue_writers, admins" />
         </authorizationEntries>
      </authorizationMap>
   </map>
</authorizationPlugin>
				

Und dann klappt’s auch mit der JMS-Queue 🙂

Die rudimentĂ€re Absicherung wĂ€re damit durchgefĂŒhrt. Jetzt sollte man nur noch von TCP auf SSL umsteigen und dann wĂ€re es noch sicherer… nĂ€chstes mal…