Serviler Wächter (Linux-Magazin, Dezember 2017)

Was flackern da die Lichter am Router so aufgeregt? Um diese Uhrzeit sollte doch keiner mehr im Internet brausen, was ist da los, ein Eindringling etwa? Schade, dass es noch keine Router gibt, die einfach auf einer LED-Anzeige die Adressen durchrauschender Netzwerkpakete anzeigen. Weil ich Bescheid wissen will, was im Hausnetz so abgeht, habe ich mir auf Ratschlag eines Arbeitskollegen eine Micro-Appliance der chinesischen Firma "Protectli" (Abbildung 1 und [4]) gekauft, auf der die FreeBSD-basierte Open-Source-Firewall pfSense läuft. Das Gehäuse ist etwa 10cm mal 10cm groß und rein passiv gekühlt, verursacht also keinerlei Lüfterlärm. Die Installation war ein Klacks, einfach die Distribution von der Webseite der pfSense Community Edition auf einen bootbaren USB-Stick geladen, eine Msata-Platte und RAM in das kleine Gehäuse des Protectli eingebaut und nach dem Booten die Installationsfragen abgenickt, schon kam die Web-GUI von pfSense hoch (Abbildung 2).

Abbildung 1: Der Micro-Appliance Protectli als Router

Wächter am Portal

Die Appliance hängt direkt an der Schnittstelle zum Internet (in meinem Fall ein DSL-Modem zum ISP) und bietet auf der LAN-Seite allen im Netzwerk der Wohnung betriebenen Geräte (in meinem Fall eine Reihe von gerouteten Subnetzen) eine Verbindung zum Internet. Mit einem 4-kernigen Celeron ausgerüstet ist sie bullig genug, um jedes einzelne durchsausende Paket anzuschauen, Statistiken zu erstellen, oder sogar bei Bedarf einzuschreiten und entsprechend eingestellter Firewall-Regeln bestimmte Kommunikationsversuche abzublocken. Will ich wissen, warum die Router-Lichter flackern, brauche ich nur die pfSense-GUI aufzurufen, um zu sehen, wer gerade Spotify streamt, Netflix-Filme schaut, oder Bestellungen auf Amazon aufgibt (Abbildung 2).

Abbildung 2: Endlich ein Gerät, um den gesamten Netzwerkverkehr des Hauses zu überwachen.

Die pfSense-GUI bietet neben traditionell terminal-basierten Tools wie pfTop auch sehr elegante Zusatzpakete wie ntopNG, damit der Admin nach Herzenslust in Tortengrafiken und HTML-Tabellen herumstöbern kann, um herauszufinden, wer am meisten Bandbreite verbraucht oder zu Rechnern in dubiosen Ländern Kontakt aufnimmt. Leider gibt's keine offizielle API zur GUI, nur eine sogenannte FauxAPI ([5]), die als Zusatzpaket auf der pfSense-Distro läuft und eingeschränkten Zugriff auf Interna bereitstellt.

Schlüsseli fürs Protectli

Um nun in regelmäßigen Zeitabständen zu kontrollieren, was auf dem Protectli-Kästchen abgeht, dachte ich mir, es wäre ganz einfach, einen Screen-Scraper zu schreiben, der sich periodisch und automatisch auf der Login-Seite des Kästchens einloggt (Abbildung 3), die im sogenannten Dashboard dargestellten Daten einscannt und per Email verschickt. Als erste Hürde liegt aber zwischen einem Kommandozeilentool und den fruchtigen Netzwerkdaten die von pfSense trotzig präsentierte Login-Seite. Ein Blick auf den HTML-Code (Abbildung 4) offenbart, dass die beiden Felder zur Entgegennahme des Usernamens und des Passworts usernamefld und passwordfld heißen und der Button zum Absenden auf den Namen login hört.

Abbildung 3: Erste Hürde: Das Login

Abbildung 4: Im Source-Code der Login-Seite finden sich die Felder für Usernamen und Passwort.

Der rasch zusammengeklopfte Python-Scraper in Listing 1, der das mittels pip3 installiete Modul selenium nutzt, um einen Browser zu simulieren, sucht und findet diese Elemente mit der Funktion find_element_by_name(). Damit der Aufruf webdriver.Firefox() auch funktioniert und den Firefox-Browser öffnet, braucht die Linux-Distro das Programm geckodriver, das es auf [6] als Tarfile gibt, das der Admin entpackt und das herausfallende Binary in einem unter $PATH auffindbaren Pfad ablegt.

Listing 1: dash-scraper.py

    01 #!/usr/bin/python3
    02 
    03 from selenium import webdriver
    04 import yaml
    05 
    06 driver = webdriver.Firefox()
    07 
    08 creds = yaml.safe_load(open('creds.yaml', 'r'))
    09 
    10 driver.get('https://192.168.241.1')
    11 
    12 user_field = driver.find_element_by_name("usernamefld")
    13 pass_field = driver.find_element_by_name("passwordfld")
    14 
    15 user_field.send_keys(creds['pfsense_user'])
    16 pass_field.send_keys(creds['pfsense_password'])
    17 
    18 driver.find_element_by_name('login').click()
    19 
    20 driver.save_screenshot('saved.png')
    21 driver.close

Abbildung 5: Status-Informationen der Pfsense-Firewall

Das Skript öffnet den Browser, fährt zur Login-Seite, befüllt die Formularfelder und klickt dann auf den "Login"-Button. Das Modul selenium kommt häufig zum Testen von Web-GUIs zum Einsatz und macht es wirklich einfach, einen vor dem Browser sitzenden User zu simulieren. Die nach der Login-Screen von pfSense angezeigte Dashboard-Seite mit den Übersichtsdaten zur Firewall speichert der Aufruf von save_screenshot() als Abzug in der Datei "saved.png" (Abbildung 5). Die einzufüllenden Werte liest das Skript aus der Yaml-Datei creds.yaml ein und im Dictionary creds stehen anschließend Username und Passwort.

Abbildung 6: Sensitive Daten in der Yaml-Datei creds.yaml.

Hoch auf dem gelben Wagen

Um die eingesammelten Daten periodisch dem Admin zuzuschicken, kommt Listing 2 zum Zug, das die von Listing 1 erzeugte PNG-Datei als Attachment in eine Email packt und diese über einen SMTP-Server verschickt. Um die sicherheitsrelevanten Variablen für den SMTP-Server, den Usernamen und das Passwort dort einzulesen, holt Zeile 10 die gleiche Yaml-Datei von vorher ein und legt ihren Inhalt im Dictionary creds ab.

Abbildung 7: Kuchengrafiken für Server und Client-Ports

Das Skript baut anschließend einen HTML-Body zusammen, der einen einleitenden Text und einen IMG-Link auf das anhängende Image enthält, damit es der Webmail-Client auch grafisch darstellt. Ab Zeile 31 nimmt Listing 2 Verbindung zum SMTP-Server auf, dessen Adresse ebenfalls in creds.yaml unter dem Schlüssel smtp_server: mail.provider.net liegt. Es nutzt Port 587 des Servers und überträgt die Daten verschlüsselt nach dem TLS-Protokoll. Den Abzug selbst hängt es im MIME-Format an und fügt noch einen Email-üblichen "Content-ID"-Header mit dem Namen der Image-Datei in eckigen Klammern hinzu.

Abbildung 8: Das vom Bildschirm gekratzte Dashboard kommt als Email an.

Listing 2: mail.py

    01 #!/usr/bin/python3
    02 
    03 import smtplib
    04 import yaml
    05 from email.mime.multipart import MIMEMultipart
    06 from email.mime.text import MIMEText
    07 from email.mime.image import MIMEImage
    08 from email import encoders
    09 
    10 creds = yaml.safe_load(open('creds.yaml', 'r'))
    11 
    12 attachment    = 'saved.png'
    13 body          = "The latest pfSense Dashboard."
    14 
    15 msg = MIMEMultipart()
    16 msg['From']=creds['from_email']
    17 msg['To']=creds['to_email']
    18 msg['Subject']='pfSense Status'
    19 
    20 msgText = MIMEText(
    21         '<b>%s</b><br><img src="cid:%s"><br>'
    22         % (body, attachment), 'html') 
    23 msg.attach(msgText)
    24 
    25 fp = open(attachment, 'rb')
    26 img = MIMEImage(fp.read())
    27 fp.close
    28 img.add_header('Content-ID', "<{}>".format(attachment))
    29 msg.attach(img)
    30 
    31 server = smtplib.SMTP(creds['smtp_server'], 587)
    32 server.starttls()
    33 server.login(creds['smtp_user'],creds['smtp_password'])
    34 server.sendmail(creds['from_email'], 
    35         creds['to_email'], msg.as_string())
    36 server.quit()

Abbildung 6 zeigt, wie die Email auf Gmail ankam. Als Cronjob einmal täglich aufgerufen, bleibt der Admin auch außer Haus auf dem Laufenden über das, was im heimischen Netz abläuft.

Infos

[1]

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

[2]

pfSsense Community Edition Download: https://www.pfsense.org/download/

[3]

"Python Web Scraping (2nd edition)", Katharine Jarmul, Richard Lawson, Packt 2017

[4]

"Firewall Micro Appliance With 4x Gigabit Intel LAN Ports, Barebone", https://www.amazon.com/gp/product/B01GIVQI3M

[5]

"pfSense FauxAPI", https://github.com/ndejong/pfsense_fauxapi

[6]

Gecko Driver zum Hochfahren des Firefox: https://github.com/mozilla/geckodriver/releases/tag/v0.19.0

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.

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 5:

Unknown directive: =desc