Alarmsignal (Linux-Magazin, Mai 2017)

Dank einer Reihe von Sensoren und Kameras erhalte ich Kurznachrichten aufs Mobiltelefon, falls jemand an meiner Wohnungstür herumfummelt oder gar hindurchgeht. Da ist es nur konsequent, dass ebenfalls das Alarmhorn des Wachpersonals tutet, falls entweder ich oder ein Bösewicht durch die ssh-Tür meines Linuxrechners wollen.

Neben der schon einmal in [2] vorgestellten Lösung "Prowl" zum Verschicken von Textnachrichten findet sich in der kunterbunten Welt der Telefon-Apps ein weiterer Anbieter namens "Pushover", der gegen eine Einmalzahlung von $4.99 lebenslang über eine Web-API bis zu 7,500 Nachrichten im Monat an beliebig viele Smartphone- und sogar Desktop-Clients verteilt.

Hanebüchene Browserlösung

Auf iOS oder Android ist es die Pushover-App, in die sich der User einloggt und die dann eingehende Nachrichten als Push Notifications anzeigt, auch wenn das Telefon mit der Lockscreen abgesperrt ist. Als weiteres Schmankerl bietet Pushover native Desktop-Clients für den Mac und über eine etwas hanebüchene Browserlösung sogar für den Linux-Desktop. Dort ruft der User entweder in Chrome oder Firefox die Webseite client.pushover.net auf, loggt sich mit seinem Pushover-Account ein, erlaubt dem Browser anschließend Desktop-Notifications auf dem hauseigenen Desktop, und fertig ist der Desktop-Client. Dies funktioniert allerdings nur so lange wie der Browser offen ist und ein Tab auf die Webseite von Pushover zeigt, was aber bei mir daheim dank Pinned Tabs bereits für GMail und Evernote der Fall ist, also spielt ein Tab mehr auch keine Rolle mehr.

Fährte aufnehmen

Den neuesten Einträgen in einer Logdatei wie auth.log in /var/logs zu folgen ist gar keine so einfache Sache. Selbst eine Unix-Funktion wie tail -f zu implementieren, die jeder Admin wohl mehrmals täglich nutzt, erfordert Kenntnis der Systemfunktion seek(), mit der ein Filehandle bis ans Ende einer Datei fahren kann. Liefert dann ein read() keine mehr Daten zurück, ist dies wirklich das Ende der Datei, kommen aber zusätzliche Zeilen zum Vorschein, wurden diese offensichtlich zwischenzeitlich angehängt und tail -f gibt sie aus. Selbst falls der Admin die Datei umbenennt, bleibt mit dem Daten konsumierenden Prozess und seinem immer noch offenen Filehandle alles beim alten, das lesende Programm bekommt dergleichen gar nicht mit.

Abbildung 1: Ein tail -f auf die Datei /var/log/auth.log zeigt erfolgreiche und fehlgeschlagene Login-Versuche am ssh-Daemon an.

Aber selbst tail -f kommt aus dem Takt, falls der distributionseigene Logdateirotierer einsetzt, die alte Datei wegschiebt, komprimiert und eine frische leere an ihre Stelle setzt. In diesem Fall wäre es fatal, mit read() weiterhin auf dem offenen Filehandle herumzuorgeln, denn frische Daten kommen zweifellos nun in einer völlig anderen Datei an. Dies bekommt der Log-Jäger mit, in dem der in regelmäßigen Abständen prüft, ob die Datei unter dem angegebenen Namen noch immer auf die gleiche i-node im Dateisystem horcht. Fördert stat() zutage, dass letztere sich geändert hat, muss der Loganalysator das offene Filehandle schließen und eine neues auf die geänderte Datei (unter dem gleichen ursprünglichen Namen) öffnen.

Abbildung 2: Auf dem Linux-Desktop sorgt ein auf den Pushover-Service eingenordetes Browser-Tab für Popups.

Rad nicht neu erfinden

Zum Glück muss heutzutage niemand mehr diese Logik als Programm aufschreiben, denn mehrere Open-Source-Implementierungen erledigen den Job bereits perfekt. Für Python existiert zum Beispiel Pygtail ([3]), angeblich ein Python-Port der weit verbreiteten Utilty logcheck. Wer kein eigenes Python-Programm schreiben möchte und auf dynamische Wartbarkeit verzichten kann, darf für den ersten Teil der Alarm-Pipeline zum Parsen der System-Log-Datei natürlich auch logcheck direkt verwenden.

Abbildung 3: Das Mobiltelefon mit installierter Pushover-App zeigt einen Login-Versuch auf dem Ssh-Server der überwachten Linux-Rechners an.

Listing 1: authwatch

    01 #!/usr/bin/python3
    02 import sys
    03 import os
    04 import re
    05 from pygtail import Pygtail
    06 
    07 log_file    = '/var/log/auth.log'
    08 
    09 offset_file = os.path.join(os.getenv("HOME"),"data",
    10   os.path.basename(sys.argv[0]) + "." +
    11   os.path.basename(log_file) +
    12   ".offset" )
    13 
    14 for line in Pygtail(log_file, offset_file=offset_file):
    15     if not re.search('CRON',line) and \
    16       not re.search('Connection closed',line):
    17         sys.stdout.write(line)

Listing 1 importiert das mittels pip3 install pygtail installierte Modul für Python 3.x und schustert in Zeile 9 einen Pfad für die von Pygtail benötigte Merkerdatei im Verzeichnis data im Home-Directory des Users zusammen, im vorliegenden Fall data/authwatch.auth.log.offset. Hier legt Pygtail den Offset in die Datei ab, bis zu dem es beim letzten Aufruf gelesen hat, und erst wenn hinter dieser Stelle neue Daten auftauchen, wird es sie auch ausgeben und ansonsten still schweigen. Da der Cron das Skript später im 5-Minuten-Takt aufgerufen wird, und es sich nach getaner Arbeit sofort verabschiedet, braucht es diesen persistenten Merker. Das Verzeichnis ~/data muss der Admin vor Benutzung des Skripts einmal manuell anlegen, falls es noch nicht existiert.

Weiter blendet Listing 1 regulär auftretende Events auf, wie Einträge in denen Schlüsselworte "CRON" oder "Connection closed" auftauchen. Die Zeilen 15-16 suchen danach mittels regulärer Ausdrücke und dem dafür importierten Standardmodul re.

Wer ein neumodisches Linux mit dem vielgescholtenen systemd fährt, findet dort keine Logdatei auth.log, sondern darf mittels journalctl auf der Kommandozeile oder den Python-Bindings von systemd und deren Methode journal die letzten Einträge des System-Logs herauswringen. Statt eines Offsets in einer Datei merkt sich das Skript dann den Zeitstempel der letzten Abfrage in einer extra Datei und springt bei der nächsten mit seek_realtime() knapp darüber hinaus, damit keine Duplikate erfasst werden. Um rotierte Logdateien braucht sich das Skript in diesem Fall keine Gedanken zu machen, da systemd solche Implementierungsniederungen abstrahiert.

Bequeme Web-Requests mit requests

Der zweite Teil der Alarm-Pipeline steht in Listing 2, und der dort abgesetzte REST-Request auf den Pushover-API-Server verlangt nach zwei Tokens, die der User mit seiner Registrierung auf dem Pushover-Service erhält. Die ersten vier Wochen sind kostenlos, wer Gefallen daran findet, kann danach für $4.99 eine Lizenz erwerben.

Den ersten Token unter dem Schlüssel user sehen registrierte User auf der Dashboard-Übersicht in Abbildung 4. Der zweite Token unter dem Schlüssel token identifiziert die App gegenüber Pushover, im vorliegenden Fall das Python-Skript in Listing 2, das ich bei Pushover unter dem Namen Snapshot registriert habe. Für einen erfolgreichen REST-Request mit der Python-Library requests fehlt nur noch der Parameter message mit dem Nachrichtentext und schon setzt sich die Methode post() in Zeile 9 mit Pushover in Verbindung. Nach Python-Manier wirft die Bibliothek bei auftretenden Fehlern Exceptions, die ohne Bearbeitung das Programm abbrechen und einen Stacktrace ausgeben, der hoffentlich bei der Behebung hilft.

Die Python-Library requests verspricht mit ihrem Werbeslogan "HTTP for Humans" nicht zuviel. Sie kommt durchdachter daher wie die von mir letztes Mal gescholtenen <urllib> und <urllib2>.

Abbildung 4: Registrierte User sehen im Dashboard ihren User-Token.

Listing 2: pushover

    01 #!/usr/bin/python3
    02 import requests
    03 import sys
    04 import re
    05 
    06 string = sys.stdin.read()[:1024]
    07 
    08 if re.search('\S', string):
    09     r = requests.post(
    10       'https://api.pushover.net/1/messages.json',
    11       data = { 
    12           'token':'XXXXXXXXXXXXXXX',
    13           'user':'YYYYYYYYYYYYYYY',
    14           'message':string
    15       })

Zeile 6 in Listing 2 holt den vom ersten Teil der Pipeline gesendeten Text aus der Standardeingabe herein und stutzt ihn mit der in Python üblichen Syntax für Array-Slices [:1024] auf die bei Pushover maximal zulässige Nachrichtenlänge von 1024 Zeichen zurecht.

Abbildung 5: Neu registrierte Apps des Users bekommen einen Token.

Das if-Konstrukt ab Zeile 8 prüft dann mit dem regulären Ausdruck \S, ob die Nachricht überhaupt druckbare Zeichen enthält und beendet Leerfahrten ohne viel Federlesens.

Freund Cron

Bleibt nur noch, einen Cronjob aufzusetzen, der die Pipeline etwa alle 5 Minuten aufruft und der in der Environment-Variablen $HOME das Home-Verzeichnis des Admins findet damit das beschreibbare Verzeichnis data zur Ablage der Merkerdatei:

    */5 * * * * /path/authwatch | /path/pushover

Abgesetzte Nachrichten verbreitet Pushover auf alle vom User registrierten Devices, und so kann es sein, dass ein fehlgeschlagenes Login ein wahres Feuerwerk an Notifications auslöst, wenn in einem Raum mehrere Mobilgeräte aktiv angeschlossen sind. Aber zumindest zählt dies in den Nutzungsbeschränkungen als nur eine Nachricht, von denen pro Monat insgesamt 7.500 erlaubt sind bevor der Pushover-Server den Zähler zurücksetzt.

Infos

[1]

Listings zu diesem Artikel: http://www.linux-magazin.de/static/listings/magazin/2017/05/snapshot/

[2]

Michael Schilli, "Private Rezeption": Linux-Magazin 04/16, S.94, <U>http://www.linux-magazin.de/Ausgaben/2016/04/Perl-Snapshot

[3]

"pygtail", Logcheck's logtail2 for Python, https://github.com/bgreenlee/pygtail

Michael Schilli

arbeitet als Software-Engineer in der San Francisco Bay Area in Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen verschiedener Programmiersprachen. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.