Intrexx Industrial - Konfiguration eines MQTT-Message-Brokers

1. Allgemeines

Für die Anbindung an Intrexx wird der von Apache entwickelte ActiveMQ-Server verwendet. Dieser Server ist frei unter der Apache 2.0 Lizenz verfügbar. Das im Folgenden verwendete Kürzel "<ACTIVE_MQ_INST>" bezieht sich auf den Installationspfad des ActiveMQ-Servers. In der von United Planet bereitgestellten Test-VM ist der Installationspfad /opt/activemq.

2. SSL-Verschlüsselung

Im Folgenden wird eine einfache Möglichkeit beschrieben, wie mit dem Java-Keytool ein selbstsigniertes Zertifikat erstellt, und dieses dann in ActiveMQ eingebunden werden kann. Hierzu muss auf dem System Java installiert und konfiguriert sein.

2.1. Keystore mit selbstsigniertem Private-Key erstellen

Zuerst wird ein Schlüsselbund (Keystore) erstellt, der den privaten Schlüssel enthält:
keytool -genkey -alias amq_server -keyalg RSA -keystore amq_server.ks
Als CN (im Dialog: "Vor- und Nachname" bzw. "First and last name") muss der FQHN der ActiveMQ-Servers übergeben werden. Es muss dann später sichergestellt werden, dass der ActiveMQ über diesen Namen erreichbar ist, damit das SSL-Handshake funktioniert. Anfangs muss ausserdem ein Passwort für den Zugriff auf den Keystore eingegeben werden. Das später anzugebende Passwort für den Alias muss einfach mit ENTER quittiert werden, und ist somit identisch mit dem Keystore-Passwort. Außerdem sollte die Datei, da sie den privaten Schlüssel des ActiveMQ enthält, entsprechend vor Zugriff geschützt werden.

2.2 Zertifikat für Clients exportieren

Aus der zuvor erstellten Keystore-Datei wird nun der öffentliche Schlüssel sowie das Zertifikat generiert:
keytool -exportcert -alias amq_server -keystore amq_server.ks -file amq_server.der
Das Zertifikat wird hier im DER-Format (also binär) exportiert. Über den weiteren Parameter "-rfc" kann das Zertifikat auch als PEM (als Klartext bzw. BASE64-kodiert) gespeichert werden. Der Import des Zertifikates in einen Truststore, der in Java-Applikationen häufig verwendet wird, wird mit folgendem Befehl ausgeführt:
keytool -importcert -alias amq_client -file amq_client.der -keystore amq_client.ts

2.3. Zertifikat in Intrexx importieren

Damit Intrexx später eine verschlüsselte TLS-Verbindung zum ActiveMQ-Server herstellen kann, muss die gerade erstellte Datei "amq_server.der" nun in Intrexx in den Portaleigenschaften eingebunden werden. Wählen Sie im Dialog Zertifikatsquelle die Option "Import der Datei" und wählen Sie dann die Datei "amq_server.der" aus. Damit die Änderung greift, muss der Portal-Dienst neu gestartet werden.

2.4 SSL in ActiveMQ aktivieren und Keystore mit dem privaten Schlüssel einbinden

Hierzu muss in der <ACTIVEMQ_INST>/conf/activemq.xml im "core"-Namespace der Pfad zum erstellten Keystore, sowie dessen Passwort übergeben werden. Da das Passwort im Klartext vorliegt, sollte die activemq.xml entsprechend vor unberechtigtem Zugriff geschützt sein.

Für die Pfadangaben sollten laut ActiveMQ-Dokumentation relative Pfade verwendet werden. Das Arbeitsverzeichnis ist per Default <ACTIVEMQ_INST>/conf/, d.h. liegt die "amq_server.ks" direkt in diesem Verzeichnis, genügt die Angabe keyStore="amq_server.ks".

Auszug aus der activemq.xml:
	<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">
...
..
.
<!-- Im sslContext muss der Pfad zur Keystore-Datei sowie das Passwort des Keystore übergeben werden -->
    <sslContext>
        <sslContext
            keyStore="/PATH/TO/amq_server.ks" keyStorePassword="GEHEIM" />
    </sslContext>

<!-- in den entsprechenden Konnektoren muss dann SSL aktiviert werden, die unbenötigten Konnektoren sollten am besten deaktiviert werden,
Hinweis: openwire wird für die Verbindung zu Intrexx via JMS benötigt-->

    <transportConnectors>
        <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
        <!-- <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> -->
        <transportConnector name="openwire" uri="ssl://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
        <transportConnector name="mqtt+ssl" uri="mqtt+ssl://0.0.0.0:8883?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
        <!-- transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
        <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
        <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
        <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/ -->
    </transportConnectors>
Bitte beachten Sie die Groß-/Kleinschreibung bei den Parametern. Weitere Informationen finden Sie hier.

2.5. Keystore in Jetty einbinden

ActiveMQ stellt eine Weboberfläche bereit, die einen Überblick über bestimmte Statusinformationen, wie z.B. die gerade aktiven Topics, oder auch die aktuell angemeldeten Publisher/Subscriber bietet. ActiveMQ liefert hierfür den Webserver Jetty von Apache mit aus. Damit dieser über eine verschlüsselte HTTPS-Verbindung erreichbar ist, muss der zuvor erstellte Keystore dort ebenfalls eingebunden werden. Hierzu gibt es in der <ACTIVEMQ_INST>/conf/jetty.xml bereits entsprechende, aber noch auskommentierte Einträge, die, wie zuvor bereits im ActiveMQ entsprechend, um Pfad und Passwort des Keystores erweitert werden müssen:
    <!--
        Enable this connector if you wish to use https with web console
    -->
    <bean id="SecureConnector" class="org.eclipse.jetty.server.ServerConnector">
        <constructor-arg ref="Server" />
        <constructor-arg>
            <bean id="handlers" class="org.eclipse.jetty.util.ssl.SslContextFactory">
                <property name="keyStorePath" value="/PATH/TO/amq_server.ks" />
                <property name="keyStorePassword" value="GEHEIM" />
            </bean>
        </constructor-arg>
        <property name="port" value="8162" />
    </bean>
Damit die Umleitung von HTTP auf HTTPS funktioniert, muss dann noch ein entsprechender Eintrag in der web.xml vorgenommen werden. Dies wird im Abschnitt Redirecting http requests to https in der Jetty-Dokumentation beschrieben.

3. SSL - Client-seitige Authentifizierung

Mit der Vorgehensweise in Kapitel 1 ist nun eine Server-seitige Authentizizierung sichergestellt. Außerdem wurde die Verschlüsselung für die Verbindung aktiviert. Ein zusätzlicher Sicherheitsgewinn kann dadurch erreicht werden, dass sich der Client authentifizieren muss. Die Einrichtung ist identisch zu der Vorgehensweise in Kapitel 1, nur dass in diesem Fall ein Privater Schlüssel für den Client erzeugt wird, das entsprechende Zertifikat mit enthaltenem öffentlichen Schlüssel nun aber auf Server-Seite eingebunden wird.

3.1. Keystore mit einem selbstsignierten privaten Schlüssel für Client erstellen

Zuerst erstellen wir einen Schlüsselbund (Keystore), der den privaten Schlüssel enthält:
keytool -genkey -alias amq_client -keyalg RSA -keystore amq_client.ks

3.2. Zertifikat mit enthaltenem öffentlichen Schlüssel für ActiveMQ-Server exportieren

Aus der zuvor erstellten Keystore-Datei wird nun der öffentliche Schlüssel sowie das Zertifikat generiert:
keytool -exportcert -alias amq_client -keystore amq_client.ks -file amq_client.der
Für die spätere Einbindung in ActiveMQ muss das nun exportierte Zertifikat, das momentan im DER(binär)-Format vorliegt, in einen Keystore bzw. Truststore importiert werden:
keytool -importcert -alias amq_client -file amq_client.der -keystore amq_client.ts

3.3 Client-Authentifizierung in ActiveMQ aktivieren, Truststore mit Zertifikat und öffentlichem Schlüssel einbinden

Im entsprechenden sslContext-Abschnitt der <ACTIVEMQ_INST>/conf/activemq.xml, den wir in Kapitel 2.4. bereits erzeugt hatten, wird nun noch der Truststore, der die entsprechenden Client-Zertifikate beinhaltet, als weiterer Parameter angegeben:
    <sslContext>
        <sslContext
            keyStore="/PATH/TO/amq_server.ks" keyStorePassword="GEHEIM"
            trustStore="/PATH/TO/amq_client.ts" trustStorePassword="GEHEIM" />
    </sslContext>
Die Client-Authentifizierung wird nun im entsprechenden Connector über den zusätzlichen URI-Parameter "needClientAuth=false" aktiviert, also für den Mqtt-Connector z.B. wie folgt:
    <transportConnector name="ssl" uri="mqtt+ssl://0.0.0.0:8883?needClientAuth=false&maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>

3.4. Hinweis zur Verwendung eines offiziell signierten Zertifikates

Die oben genannten Punkte beschreiben die einfachste Vorgehensweise über ein selbsigniertes Zertifikat. Der Vorteil dabei ist, dass man die Verbindung auf einfache Weise verschlüsseln kann. Da das Zertifikat aber selbstsigniert ist, und eben nicht von einer offiziellen Zertifikatsstelle (CA Authority) signiert wurde ist die Vertrauenskette (Chain of Trust) nicht gewährleistet. Das führt beispielsweise dazu, dass im Webbrower beim Zugriff auf die ActiveMQ Webmin-Oberfläche ein Warnhinweis erscheint, da der Ersteller nicht bekannt ist. Wenn offiziell signierte Zertifikate erstellt werden sollen, wäre die Vorgehensweise wie folgt: Die dann von der Zertifizierungsstelle zurückerhaltenen, signierten Zertifikate liegen meist in unterschiedlichen Formaten vor. Am Einfachsten lassen sich die Zertifikate im PEM-Format (im Klartext) einbinden, indem man die entsprechenden Dateien mit einem Texteditor öffnet, den privaten Schlüssel, Zertifikate sowie Zwischenzertifikate kopiert, und daraus eine neue Datei, die die komplette Kette enthält, generiert. Gehen Sie dazu wie folgt vor: Erstellen Sie eine neue Textdatei mit dem Namen "zertifikatskette.pem", in die per Copy & Paste der private Schlüssel, sowie sämtliche Zertifikate inklusive BEGIN/END-Prolog eingetragen werden. Der private Schlüssel (bspw. *.key):
-----BEGIN RSA PRIVATE KEY-----
...
Das Zertifikat (bspw. *.crt):
-----BEGIN CERTIFICATE-----
...
Eventuelle CA-Zwischenzertifikate (bspw. *.ca):
-----BEGIN CERTIFICATE-----
...
Anschließend wird diese pem-Datei über den folgenden Befehl nach pkcs12 konvertiert (OpenSSL muss installiert sein):
openssl pkcs12 -export -name MY_ALIAS -in zertifikatskette.pem -out zertifikats_keystore.p12
Die Einbindung in ActiveMQ bzw. Jetty funktioniert wie in Kapitel 2.4. bzw. 2.5. beschrieben. Da der gerade erzeugte Keystore vom Typ PKCS12 ist, muss lediglich noch ein weiterer Parameter, der den Typ des Keystores beschreibt, mitangegeben werden.

In der jetty.xml:
        <property name="keyStorePath" value="/PATH/TO/zertifikats_keystore.p12" />
        <property name="keyStorePassword" value="GEHEIM" />
        <property name="keyStoreType" value="pkcs12" />
In der activemq.xml im sslContext:
        <sslContext>
            <sslContext
                keyStore="/PATH/TO/zertifikats_keystore.p12" keyStorePassword="GEHEIM" keyStoreType="pkcs12" />
        </sslContext>

4. Einrichtung der Benutzer

4.1. Benutzer der Jetty-Webmin

Die Benutzer, die sich an der Webmin-Oberfläche anmelden dürfen, werden in der Datei <ACTIVEMQ_INST>/conf/jetty-realm.properties definiert.

4.2. Benutzerberechtigungen der Connectoren

Die einfachste Möglichkeit ist das direkte Setzen der Berechtigungen in der activemq.xml mit dem "SimpleAuthenticationPlugin". Über einen entsprechenden authentication-Eintrag fügt man einen neuen Benutzer, Passwort und Gruppenzugehörigkeit hinzu. Über das authorizationPlugin können dann den Benutzern die Berechtigungen an den Topics/Queues zugewiesen werden. Read- bzw. Write-Permissions dürften selbsterklärend sein. Die "admin"-Rolle beschreibt in dem Zusammenhang die Rechte ein Topic zu erstellen.
    <plugins>
        <simpleAuthenticationPlugin anonymousAccessAllowed="false">
            <users>
                <authenticationUser username="admin" password="admin"
                groups="admins,publishers,consumers"/>
                <authenticationUser username="user_publ" password="admin"
                groups="publishers"/>
                <authenticationUser username="user_cons" password="admin"
                groups="consumers"/>
            </users>
        </simpleAuthenticationPlugin>

        <authorizationPlugin>
            <map>
                <authorizationMap>
                    <authorizationEntries>
                        <authorizationEntry topic=">"
                            read="admins" write="admins" admin="admins" />
                        <authorizationEntry topic="ActiveMQ.Advisory.>"
                            read="publishers,consumers" write="publishers,consumers" admin="admins,consumers,publishers" />
                        <authorizationEntry topic="test-mqtt.>"
                            read="consumers" write="publishers"
                            admin="admins" />
                    </authorizationEntries>
                </authorizationMap>
            </map>
        </authorizationPlugin>
    </plugins>
Weitere Informationen finden Sie unter
http://activemq.apache.org/security.html
http://activemq.apache.org/wildcards.html

ActiveMQ bietet die Möglichkeit, Informationen über die Topics über den ActiveMQ.Advisory-Zweig zur Verfügung zu stellen, z.B. für das Generieren einer Message beim Verbindungsaufbau eines Clients im ActiveMQ.Advisory.Connection-Zweig. Falls man die dort bereitsgestellten Informationen benötigt, benötigt der Client auch dort die notwendigen Lese-/Schreibberechtigungen. Falls die entsprechenden Zweige nicht vorhanden sind, benötigt der Client dann auch Admin-Berechtigungen um die Topics generieren zu können bzw. muss zuvor sichergestellt werden, dass die benötigen Advisory topics vorhanden sind. Über den folgenden Eintrag im Broker-Namespace der activemq.xml lassen sich die Advisory Messages aber auch deaktivieren:
<broker advisorySupport="false">
Weitere Informationen finden Sie hier.

4.3. Berechtigungen sicherstellen und Dienst neu starten

Der ActiveMQ-Server läuft unter dem restriktiven Benutzer-Account "activemq". Deshalb muss nach den in Kapitel 2 und 3 vorgenommenen Änderungen sichergestellt werden, dass Besitzer und Gruppenzugehörigkeit der geänderten Dateien noch stimmen. Besitzer und Gruppenzugehörigkeit der Datei müssen beide "activemq" lauten. Über folgenden Befehl lässt sich das kontrollieren:
ls -la /opt/activemq/conf/activemq.xml /opt/activemq/conf/jetty.xml

-rw-r--r-- 1 activemq activemq 7785 2017-03-20 08:14 /opt/activemq/conf/activemq.xml
-rw-r--r-- 1 activemq activemq 7841 2017-03-15 16:04 /opt/activemq/conf/jetty.xml
Dasselbe gilt für die in Kapitel 2 erstellte Keystore-Datei. Der Speicherpfad der Datei und Benutzer/Gruppe müssen entsprechend vom activemq-Benutzer gelesen werden können. Korrigieren ließe sich das über den folgenden Befehl:
chown activemq.activemq <DATEINAME>
Danach muss der ActiveMQ-Dienst neu gestartet werden:
service activemq restart

5. Weitere Informationen

  1. Intrexx Industrial
  2. Test-MQTT-Broker
  3. MQTT in Intrexx
  4. Polling
  5. Seiteninhalte mit JavaScript aktualisieren
  6. Kompletter Java Sample Code für eine nahtlose Integration im Modul Prozesse (Ereignisquelle, Ereignisbehandler und Aktion)
  7. http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html