Code Refresh beim Zugangskontrollsystem und Containerisierung

Was in letzter Zeit passiert ist

Die letzten Wochen habe ich nun dazu genutzt das Zugangskontrollsystem etwas zu überarbeiten. Nicht die interne Codebasis, aber das drum herum.
Zum einen werden die Einzelteile immer mehr in Container Images (Docker) gepackt. Dies wird zukünftig dabei helfen, die Skalierbarkeit zu verbessern und auch die Ausfallsicherheit weiter zu erhöhen. Der Schritt ist noch nicht entgültig abgeschlossen aber ich bin auf einem guten Weg.

Dieser Schritt hat nun vorausgesetzt, die aktuelle FEIG SDK (Generation 2) (OBIDISC4J v5.6.3) zu verwenden. Leider haben wir ja gelernt, dass die aktuellste Generation 3 der SDK nicht mehr (oder auch noch nicht) verschlüsselt mit dem RFID Leser kommuniziert. Das folglich ein Ausschlußkriterium darstellt für den Betrieb als Zugangskontrollsystem.
Also bleiben wir vorerst auf der Generation 2 des SDKs.

Weiterhin setze ich nun Java 17 ein, da es im Docker Container doch deutlich besser performt und weniger Speicherprobleme hat als die älteren Java Versionen. Das hatte aber zur Folge, alle bislang vewendeten Java Extensions (javax Klassen) zu den Neuen (jakarta Klassen) zu migrieren (Danke Oracle).
Das war zum Glück nicht so aufwändig und konnte ohne größere Schwierigkeiten erledigt werden. Hier hing dann aber auch der gesamte Build-Prozess dran und da musste ich auch noch einiges gerade rücken. So ist das eben mit dem „Rattenschwanz“.
Vorteil ist, ich bin wieder up-to-date mit den Maven Repos und der Build-Prozess ist nun ausgelagert in eine Azure DevOps Pipeline.

Docker

Allgemein

Nicht alle Probleme kann man mit Docker lösen, aber es gibt uns durchaus Möglichkeiten einige der Probleme in den Griff zu bekommen.
Zum einen wird das „Shipping“ einfacher, da alles im besagen Container liegt was für die Laufzeit benötigt wird. Die FEIG Bibliotheken sind ja „closed source“ und die Java Libraries sind nur Wrapper rund um diese Linux Bibliotheken die im System zu installieren sind. Das macht es schwer die Software „mal eben schnell“ wo anders einzusetzen.
Zum anderen, wenn nun aber alles in handliche Päckchen (Container Images) gepackt wurde, kann man eben mal die Version tauschen und ratzfatz zurückrollen. Was Upgrades deutlich einfacher macht und auch wenn man mal die Hardware darunter tauschen möchte.

ACS Tag-Manager

Hier plane ich auch bereits seit längerem den ACS TAG Manager umzubauen. Diese Anwendung ist dafür verantwortlich, die RFID Tags zu beschreiben. Die Kernfunktionalität soll hier zukünftig als REST API in einem Docker Container laufen. Das Ganze mit API first Ansatz mt der aktuellen OpenAPI Spezifikation.
Das wird es deutlich einfacher machen, die Kommunikation zwischen Anwendung und RFID TAG zu abstrahieren.
Am Ende steht dann die Integration in den ACS-Manager. Diese Anwendung ist in PHP geschrieben und würde die REST API verwenden, um die Tags direkt aus der Management Anwendung heraus zu beschreiben.

Aber bis dahin ist es noch ein langer Weg. Kurzfristig muss nun die zentrale ACS-Server Komponente erfolgreich in den Docker Container gepackt werden, damit ist dann schon viel gewonnen.

Docker Basiscontainer

2021 hatte ich versucht mit Alpine Linux als Basiscontainer zu arbeiten. Damals noch mit OpenJRE 11. Dort scheiterte es dann erst an der FEIG Bibliothek v4 (Gen 2) und danach an einer nicht kompatiblen libc Bibliothek. Daher musste ich auf Debian als Basis ausweichen mit dem Upgrade auf die FEIG Bibliothek v6 (Gen 3).
Dann kam allerdings die Gen 3/Gen 2 Thematik und das Fehlen der Verschlüsselung was mich so frustrierte, dass ich dann zunächst nicht weiter daran gearbeitet habe.

Nun, 2 Jahre später, versuche ich mal einen Neuanfang. Zunächst auf Basis Debian. Problem ist hier aber, dass der Debian Container schon so viele ungepatchte Sicherheitslücken enthält, dass ich da ein ungutes Gefühl habe, dass so zu betreiben.
Allerdings darf man sich da nichts vor machen, denn wenn man Debian auch so als OS laufen hat, hat man ja auch die selben Lücken. Aber beim Containerbau hat man ja so schicke Tools, die einem das gleich zeigen. Bei einer Bare-Metal Installation fällt das ja erst mal nicht auf.

Wichtig ist aber erstmal, dass es überhaupt funktioniert, dann kann man sich um die Optimierung kümmern. Da fallen mir schon noch Dinge dazu ein. Man könnte z.B. die Teile löschen die eine Sicherheitslücke haben (soweit diese nicht benötigt werden) oder rman nähert sich doch von Alpine Linux und versucht die fehlenden Dinge zu integrieren.

Stand der Containerisierung

Die einzelnen Bestandteile des Zugangskontrollsystems haben folgenden Stand was die Containerisierung angeht:

KomponenteStand
ACS ServerContainerisiert (aber ungetestet)
ACS ManagerContainerisiert
ACS Tag Manageroffen
ACS Pushover ServiceContainerisiert
REDISContainerisiert
LDAPContainerisiert
MQTTContainerisiert
ACS, Stand der Containerisierung

Coderefresh

Seit dem letzten Firmwareupdate hat sich nicht viel getan. Seit dem sind 2,5 Jahre vergangen und das System leistet anstandslos seinen Dienst ohne jegliche Ausfälle.

Vor 3 Wochen habe ich begonnen den Quellcode nach Azure DevOps umzuziehen. Via Azure Pipelines werden nun die einzelenen Java Bibliotheken voll automatisiert gebaut.

Vor 2 Wochen habe ich angefangen mich nun endlich um das Management System zu kümmern. Dieses wird als PHP WebAnwendung umgesetzt und als Docker Container bereitgestellt. Mit start dieses Projekts kam nun auch die Idee, die Java Anwendungen als Docker Container zur Verfügung zu stellen. Die ersten Tests mit der FEIG Bibliothek OBIDISC4J 4.8.0 waren allerdings nicht erfolgreich. Bei start des Tagmanagers (zur Initialisierung der RFID Medien) beendet dieses sich nach dem Start direkt mit einem Coredump. Ein Ad-Hoc upgrade auf die Version 5.5.2 (die aktuelle Version) endet mit einer Fehlermeldung, dass die Feig Crypto Componente sich nicht initialisieren lässt. Daraufhin habe ich mich mal an den Support der Firma FEIG ELECTRONIC GmbH gewendet.

Die Firma arbeitet allerdings aktuell an einer neuen Generation der Java Bibliotheken und mir wurde empfohlen gleich auf die dritte Generation upzugraten, allerdings wird hier einiges an Codingaufwand auf mich zukommen. Der 18. Januar 2021 ist offizielles Releasedatum des ersten Release Candidate der neuen Java Bibliothek. Allerdings noch ohne Crypto Componente.

Ich habe mich dazu entschlossen das Angebot anzunehmen und die Anwendung(en) entsprechend umzuschreiben. Ich werde diese Gelegenheit nutzen um folgende Optimierungen durchzuführen:

  • Überführung der Konfiguration aus der Konfigurationsdatei in den LDAP
  • Dynamische Ermittlung der Zugangspunkte (aus dem LDAP) und erstellen der Listener, damit soll es möglich sein mehrere Leser mit einer Serveranwendung zu versorgen.

Vorbereitungen für den Coderefresh, wie ein Upgrade auf Java 11, das aktualisieren der anderen eingebundenen Bibliotheken und die eEinbindung der neuesten Maven Plugins, habe ich heute abgeschlossen.

Da ich jetzt wieder Betatester spiele, wird es wohl auch wieder mehr Updates hier im Blog geben.

MQTT mit Eclipse Mosquitto

Zwischenzeitlich läuft das Zugangssystem stabil. In der Zwischenzeit sind einige Umstellungen erfolgt:

  • Umstellung auf Maven für die Java Builds
  • Veröffentlichung einiger OpenSource Artefakte auf Maven Central
  • Installation eines neuen Docker Hosts
  • Installation eines Eclipse Mosquitto MQTT Servers (als Docker Container)

Den MQTT Server hatte ich ursprünglich für die geplante IoT Infrastruktur (mit ESP8266 und ESP32 basierten Sensoren – hier werde ich auch berichten sobald die ersten Sensoren in Betrieb gehen) installiert. Ich habe diesen auf einem alten Raspberry Pi 1 Model A aufgesetzt als Docker Container.

Für das User Management System hatte ich bereits länger geplant einen Access Monitor geplant. Dieser sollte die letzten Zutritte visuell darstellen. Also wer hat wann zuletzt einen Zugang angefragt und wie war der Status dazu. Hier soll unter anderem auch ein Bild des Benutzers kommen angezeigt werden das aus dem LDAP Server geladen wird.

Ich hatte lange überlegt wie man einen solchen Monitor implementieren könnte. Vorallem wie dieser die Daten übermittelt bekommt.

Mit MQTT ist die Sache nun relativ einfach. Das Zugangskontrollsystem muss nur den Status eines Zutritts in einen Topic des MQTT Servers Publishen. Der Zutrittsmonitor subscribed auf diesen Topic und erhält so alle notwendigen Informationen zur Visualisierung.

Für den Publish der Nachricht aus dem Zugangskontrollsystem verwende ich nun den Eclipse Paho Java Client. Die Bibliothek war unkompliziert zu integrieren.
Der Zutritts Monitor ist eine Webanwendung. Für den subscripe verwende ich aktuell Eclipse Paho JavaScript Client. Dieser war etwas Tricky, denn das Beispiel auf der Seite hat bei mir nicht funktioniert.

Folgender JavaScript Sourcecode führte dann zum Erfolg:

<head>
<script> // configuration var mqtt; var reconnectTimeout=2000; var host="myMQTTServer"; var port=9001; var clientId="oitc-acs-monitor"; var topic="/oitc-acs" // called when the client connects function onConnect() { // Once a connection has been made, make a subscription and send a message. console.log("Successfully connected to " + host + ":" + port); console.log("Subscribing to topic " + topic); mqtt.subscribe(topic); } // called when a message arrives function onMessageArrived(message) { // console.log(message.payloadString); obj = JSON.parse(message.payloadString); console.log(obj); } function MQTTconnect() { console.log("Try to open connection to " + host + ":" + port); mqtt = new Paho.MQTT.Client(host,port,clientId); mqtt.onMessageArrived = onMessageArrived; // Valid properties are: timeout userName password willMessage // keepAliveInterval cleanSession useSSL // invocationContext onSuccess onFailure // hosts ports mqttVersion mqttVersionExplicit // uris var options = { timeout: 3, userName: "acsMonitorUser", password: "myUsersPass", keepAliveInterval: 60, useSSL: true, onSuccess: onConnect, mqttVersion: 3, }; mqtt.connect(options); } </script> </head> <body>
<script>
MQTTconnect(); </script>
</body>

Wenn die Seite lädt, verbindet sich die Webanwendung mit dem MQTT websocket und subscribed den topic. Da die Nachrichten „retained“ vom Zugangskontrollsystem gesendet werden, wird auf jeden Fall der letzte Zugriff im JavaScript consolen Log angezeigt. Bei weiteren Zugängen erscheinen diese ebenfalls im Log.

Die nächsten Schritte sind nun die Anzeige auf der HTML Seite. Ich werde hier mit jQuery arbeiten um mir das Leben etwas einfacher zu machen. Einen ersten Prototypen habe ich bereits am Laufen. Die nächste Herausforderung ist nun, das Bild der Person noch aus dem LDAP Server zu laden. Ich werde berichten, sobald ich hier wieder ein Stück weiter gekommen bin.

Abschließend noch das aktuelle Architektur Schaubild:

Neues Feig Java SDK 4.8.0

Kuze Info am Rande.

Es gibt ein neues Java SDK von der Firma FEIG Electronic GmbH. Heruntergeladen ist es. Leider ist das SDK nicht kompatibel mit v4.7. Daher musste ich ein paar Anpassungen am Listener vornehmen. Ob das SDK tut wie es soll und ob der SSL Bug damit behoben wurde werde ich die kommenden Tage mal testen.

Ich werde berichten 🙂

Aktuell sieht die Architektur so aus. Mal sehen ob die 2 gelben Verbindungen bald grün werden.

Server läuft weiterhin aber FEIG SSL Bug weiterhin offen

Am 8. August 2016 habe ich die Version 2 des Zugangskontrollsystems live genommen. Bisher ist nicht viel passiert. Das System läuft ohne murren vor sich hin und verrichtet seinen Dienst ohne Störungen.

Gelegentlich hört der Leser auf KeepAlive Requests zu senden, aber das war schon von anfang an so. Der Workaround mit dem CPU reset funktioniert prima.

Leider ist noch immer die Sache mit der verschlüsselten Kommunikation offen.
Nachdem einige Zeit ins Land gegangen war hatte ich hierzu Anfang der Woche nochmals bei der Firma FEIG nachgefragt wie denn der Stand der Fehlerbehebung sei. Hierauf kam erfreulicherweise die Meldung dass es ein neues JavaSDK (v4.7.0) gibt, bei dem der Fehler auf Linux wohl behoben wurde.
Heute Abend habe ich dann die neue Version installiert und getestet. Aber leider habe ich noch immer das selbe Problem. Nach aktivierung des Crypto Mode kann ich zwar dem Leser Befehle senden aber sobald ich den Listener initialisiere erhalte ich eine Java Exception:

de.feig.FedmException: StartAsyncTask

Dies habe ich eben gemeldet. Mal sehen was passiert.

Der erste Monat Testzeitraum

Vor ca. 1 Monat habe ich das Zugangskontrollsystem in Betrieb genommen. Seit dem gab es die ein oder anderen Problemchen.

  1. Der Server schmiert mit einem OutOfMemory ab
  2. Bei Notifikation per Mail (kann man bei Rollen hinterlegen) kann es zu Verzögerungen von bis zu 30 Sekunden kommen.
  3. Der Leser hört auf, KeepAlive requests zu senden

Das erste Problem war relativ schnell gelöst. Schuld war hier die Temparaturmessung des Raspberry. Bei, Auslesen von /proc gibt es hier manchmal einen deadlock. Ich hoffe dass dies bei dem ein oder anderen OS Update behoben wird. Vorläufig habe ich die Temparaturmessung aber deaktiviert.

Das zweite Problem hoffe ich nun so zu lösen, indem ich meine Klasse hierzu mit „Runnable“ ausstatte und die Klasse zur Laufzeit initialisiere und dann im Hintergrund die Mail versenden lasse. Warum nicht multi threading nutzen wenn es die Plattform hergibt.

Das dritte Problem bin ich nur am Rande angegangen. Hier muss ich erst noch ein paar Informationen sammeln. Hierzu habe ich meinen Monitor Thread umgebaut und berechne nun die Zeit, die zum letzten KeepAlive vergangen ist. Wenn hier ein Schwellwert überschritten wird, sende ich einen Befehl an den Leser. Ich möchte versuchen Ihn darüer zu bewegen wieder KeepAlive Requests zu senden.

In dem Zuge habe ich nun auch die LDAP Anbindung eingebaut und die Authentication Klasse ebenfalls als Hintergrundthread implementiert. Dadurch kommt der Notify Thread schneller zurück und kann dadurch schneller neu getriggert werden.

Die Optimierungen und Bugfixes habe ich zwischenzeitlich alle im Programm, jedoch steht der Test noch aus. Ich hoffe dies morgen durchführen zu können. Aktuell sitz ich nämlich in Katzenelbogen in einem Café fest und warte auf meine Frau. 🙂

Konfigurationsdateien

„Hardcoded configuration“ ist schlechter Stil, daher habe ich mir vorgenommen meine Parameter extern in einer Konfigurationsdatei zu halten und eine kleine Helferklasse im Java zu schreiben die mir die Daten einliest.

die nutzung ist denkbar einfach, nach dem Import der Klasse kann man mit 2 einfachen Kommandos die Konfig Datei laden und einen Parameter einlesen.

Beispiel:

import de.oberdorf_itc.textproc.ConfigFile;
[..]
ConfigFile prop = new ConfigFile();
[..]
// read in the configuration file
prop.getProperties("/home/cybcon/etc/my_config.properties");
// get the parameter
String value = prop.getValue("myAttribute");
[..]

die Konfigurationsdatei könnte wie folgt aussehen:

myAttribute=This is the attributes value

Hier noch der Quellcode der Klasse „ConfigFile“:

package de.oberdorf_itc.textproc;

/**
 * Import Java libraries
 */
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.File;
import java.util.Properties;
import java.io.FileInputStream;

/**
 *
 * This java class can be used to load configuration files and read
 * the values.
 *
 * @author Michael Oberdorf IT-Consulting
 * @version 0.100
 *
 */
public class ConfigFile {
    static Properties prop = new Properties();

    /**
     * Method to load a configuration file
     *
     * @param configFile (String)
     * @return Properties
     * @throws IOException
     *
     */
     public Properties getProperties(String configFile) throws IOException {
         // Do some error handling
         if (configFile == null) { throw new IOException("File not given."); }
         File FH = new File(configFile);
         if(!FH.exists() || !FH.isFile()) { throw new FileNotFoundException("File not found exception for: " + configFile); }
         else { if (!FH.canRead()) { throw new IOException("No Permission to read file: " + configFile); } }
         // Cleanup FileHandle
         FH = null;

         // get the input stream from file contents
         FileInputStream inputStream = new FileInputStream(configFile);
         prop.load(inputStream);
         inputStream.close();

         // return properties
         return prop;
     }

     /**
      * Method to read an attributes value from the configuration file
      *
      * @param attribute (String)
      * @return String
      * @throws IOException
      *
      */
     public String getValue(String attribute) throws IOException {
         if (attribute == null) { throw new IOException("No attribute given"); }
         return prop.getProperty(attribute);
         }
}

 

 

Die Türe mit dem PiFace Digital 2 öffnen

Meine Recherche war erfolgreich und ich konnte eine kleine Java Klasse erstellen mit der ich den OpenCollector Ausgang (OUT PIN 0) für 500 Millisekunden öffnen (also auf GND ziehen) kann.

Ursprünglich wollte ich mit der Klasse com.pi4j.device.piface.PiFace arbeiten, leider kommt aber Java nach Ausführung nicht wieder zurück.

Ein strace hat ergeben, dass Java auf die Beendigung eines Prozesses wartet. Die angegebene PID im strace war aber nicht mehr im system vorhanden, damit würde die Classe sich wohl nicht wieder beenden. Daher bin ich umgestiegen auf die etwas weiter unten liegende com.pi4j.io.gpio Klassen.

Und hier das Ergebnis:

// Importing Libraries
import com.pi4j.gpio.extension.piface.PiFaceGpioProvider;
import com.pi4j.gpio.extension.piface.PiFacePin;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.spi.SpiChannel;
import java.io.IOException;

/**
 * 
 * @author Michael Oberdorf
 * @version 0.100
 * Description:
 *   PiFaceOut is the class to control the PiFace Digital 2 Output Ports
 *   The Class uses the Pi4J GPIO interface instead of the device.piface class because
 *   the finalizing is defect there.
 *   The GpioController can be shutdown 
 *
 */
public class PiFaceOut {
    public static void main(String args[]) throws InterruptedException, IOException {
        // create gpio controller
        final GpioController gpio = GpioFactory.getInstance();

        // Trigger the Out PIN 00 for 500 milliseconds to open the entrance door
        openDoor(gpio, 500);

        // shut down the interface to clean up native heap from WiringPi
        gpio.shutdown();
    }
    
    /**
     * 
     * private method to trigger the digital output pin 0 to open the entrance door
     * @param gpio (GpioController)
     * @param time (int)
     * @throws IOException
     * @throws InterruptedException
     *
     */
    private static void openDoor(GpioController gpio, int time) throws IOException, InterruptedException {
        // create custom PiFace GPIO provider
        final PiFaceGpioProvider gpioProvider = new PiFaceGpioProvider(PiFaceGpioProvider.DEFAULT_ADDRESS, SpiChannel.CS0);

        // provision gpio output pins and make sure they are all LOW at startup
        GpioPinDigitalOutput myOutputs[] = {
                gpio.provisionDigitalOutputPin(gpioProvider, PiFacePin.OUTPUT_00)
        };

        // pull digital out pin to GND (OpenCollector)
        gpio.setState(true, myOutputs);
        // sleep for a while
        Thread.sleep(time);
        // close the digital out pin
        gpio.setState(false, myOutputs); 
    }
}