Visual Regression Testing mit Puppeteer und Resemble.js (Teil 2)

In meinem letzten Artikel habe ich mit einem Beispiel beschrieben, wie man mittels Puppeteer automatisert Screenshots von Websites erstellt. Das Problem stellte sich für uns, weil wir im großen Umfang CMS-Unterseiten auf Frontend-Probleme nach einem Upgrade testen wollten.

Ziel war es, ein Skript zu erstellen, welches vor einem Upgrade gestartet werden kann und zunächst den Status Quo von Websites in Screenshots festhält.
Nachdem die Upgrades an den Websites durchgeführt wurden, kann das Skript erneut gestartet werden und es werden automatisch visuelle Vergleichtests durchgeführt.

Test-Logik

Zum Ablauf der Visual Regression Tests habe ich folgende kleine Test-Logik entwickelt:

Flowchart

Wenn ich keinen Screenshot finde, erstelle ich einen für einen späteren Vergleich. Finde ich einen vor, dann erstelle ich einen neuen und vergleiche ihn direkt.

Visual Regression Testing mit Puppeteer und Resemble.js

Um den obigen Test-Algorithmus abzubilden, muss die app.js aus meinem letzten Artikel um eine Testschleife, die zusätzliche Library Resemble.js und das File System-Modul von Node.js erweitert werden.

Die folgende Vorgehenswiese lässt sich auch anhand meiner Commits im Github-Repository nachverfolgen.

Dateizugriff einrichten

Für den Zugriff auf das Dateisystem stellt Node.js das fs-Modul bereit. Damit lassen sich klassische Dateioperationen durchführen (copy, move etc.).

Um das Modul zu verwenden, muss es mittels require in der app.js eingebunden werden:

const fs = require('fs')

Bisher war der Pfad- und Dateiname der Screenshots, die in der takeScreenshot()-Funktion erstellt werden noch hard coded. Weil die Funktion zukünftig sowohl Vorher- als auch Nachher-Screenshots festhalten soll, werden folgende Änderungen vorgenommen:

const screenshotsFolder = './screenshots/'

und

await page.screenshot({ path: filename, fullPage: true })

Test-Logik aufbauen

Jetzt kann die eigentliche Test-Logik aufgebaut werden. Ziel ist es, die Screenshots noch nicht zu vergleichen, aber schon die nötige Schleife zusammenzubasteln. Ich habe dafür eine Funktion erstellt, welche den Test startet. Hierfür eignet sich in diesem Fall die Verwendung der Immediately-invoked Function Expression.

Die Immediately-invoked Function Expression ist eine Möglichkeit, Funktionen sofort auszuführen, sobald sie erstellt werden:

(() => {
  /* Befehle */
})()

Unsere asynchrone Funktion sieht dann so aus:

// Immediately-invoked arrow function after launch
(async () => { 
    // Create screenshots folder if it does not exist
    if (!fs.existsSync(screenshotsFolder)) {
        fs.mkdir(screenshotsFolder, (err) => {
            if (err) throw err
        })
    }

    for (const website of websites) {
        const orgScreenshotPath = screenshotsFolder + website.filename + '.png'
        const testScreenshotPath = screenshotsFolder + website.filename + '_test.png'
        // Check if both original and testing screenshot already exist
        if (fs.existsSync(orgScreenshotPath) && fs.existsSync(testScreenshotPath)) {
            // Both exist run regressionTest()
        } else {
            if (fs.existsSync(orgScreenshotPath)) {
                // Original exists create test screenshot
                await takeScreenshot(website.url, testScreenshotPath)
                    .then(console.log('Created test: ' + website.filename))
                // run regressionTest()
            } else {
                // No Original exists, let's create a new one
                await takeScreenshot(website.url, orgScreenshotPath)
                    .then(console.log('Created original: ' + website.filename))
            }
        }
    }
})()

Mit fs.existsSync() wird geprüft, ob eine Datei unter dem angegeben Pfad existiert. Dies könnte auch mittels Promises/await asynchron und ohne Callbacks gemacht werden (Momentan noch experimental).

Vergleichen von Screenshots mit Resemble.js

Jetzt fehlt nur noch die regressionTest()-Funktion, damit wir unsere Tests durchführen können.

Hierfür muss zunächst Resemble.js mittels npm installiert und eingebunden werden:

$ npm install resemblejs --save

In unserer app.js:

const resemble = require('resemblejs')

Die asynchrone regressionTest()-Funktion sieht wie folgt aus:

const regressionTest = async (filename, orgScreenshotPath, testScreenshotPath) => {
    console.log('Visual Regression: ' +  filename)

    const diffFolder = screenshotsFolder + 'diff/'

    resemble(orgScreenshotPath).compareTo(testScreenshotPath).onComplete(data => {
        if (data.misMatchPercentage > 0) {
            console.log('Missmatch of ' + data.misMatchPercentage + '%')

            // Create screenshots/diff folder only when needed
            if (!fs.existsSync(diffFolder)) {
                fs.mkdir(diffFolder, (err) => {
                    if (err) throw err
                })
            }

            // Set filename and folder for Diff file
            const diffScreenshotPath = diffFolder + filename + '_' + data.misMatchPercentage + '_diff.png'
            fs.writeFile(diffScreenshotPath, data.getBuffer(), (err) => {
                if (err) throw err
            })
        }
    })
}

Die Funktion erhält die Parameter filename aus dem websites-Array, sowie den zusammengesetzten Pfad zum Original. Dazu kommt ein Vergleichsscreenshot (orgScreenshotPath, testScreenshotPath).

Diese werden nun durch resemble verglichen:

resemble(orgScreenshotPath).compareTo(testScreenshotPath)

Wenn ein Unterschied zwischen orgScreenshotPath und testScreenshotPath besteht wird eine Differenzgrafik erstellt. Diese zeigt standardmäßig die Unterschiede in Magenta an. Damit Fehler schneller gefunden werden können, werden diese Differenzbilder im Unterverzeichnisscreenshots/diff abgelegt.

In folgenden Screenshots fehlen Seiteninhalte. Resemble.js findet den Unterschied und stellt ihn gut sichtbar dar:

Resemble.js Animation

Schneller Vergleichen von einzelnen Screenshots

Wenn eine einzelne URL verglichen werden soll, ist es etwas mühselig diese immer in das websites-Array in app.js einzufügen. Deshalb habe ich in dem Skript die Möglichkeit ergänzt, beim Aufruf URL(s) als Kommandozeilenargumente anzuhängen:

$ node app.js https://rrzk.uni-koeln.de/

let websites = []

process.argv = process.argv.slice(2) // Slice away the first two command line arguments

if (process.argv.length == 0) { 
    // If no command line arguments are given add hardcoded examples
    websites = [
        { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
        { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
        { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings' },
        { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downloads' }
    ]
} else {
    process.argv.forEach((val, index) => {
        try { // Check if argument is a URL
            let screenshotURL = new URL(val)
            // Add URL to websites array if valid and create filename
            websites.push({ url: screenshotURL.href, filename: index + '_' + screenshotURL.host})
        } catch (err) {
            console.error('"' + val + '" Is not a valid URL!')
        }
    })
}

Mittels der Klasse URL, wird die Zeichenkette in ein URL-Objekt konvertiert. Wenn dies fehlschlägt, enthält die Zeichenkette keine gültige URL.

Das finale app.js-Skript:

Das finale app.js Skript ist nun fertig app.js Download

Zum Starten einfach app.js ausführen: $ node app.js.

Wenn Screenshots von einzelnen Websites verglichen werden sollen, kann dies folgendermaßen gemacht werden:

$ node app.js https://rrzk.uni-koeln.de/
Created test: 0_rrzk.uni-koeln.de
Visual Regression: 0_rrzk.uni-koeln.de
Missmatch of 58.22%

Ausführen mittels Docker

Dieses Skript kann auch in einer containerbasierten Umgebung ausgeführt werden. Als Basis-Image verwende ich zenato/puppeteer. Das Image enthält einen standardisierten „Chrome“-Browser und stellt eine Umgebung bereit, in der Screenshots in konsistenter Weise erstellt werden.

Mein Dockerfile dafür sieht so aus:

FROM zenato/puppeteer:latest
USER root
COPY package.json /app/
COPY app.js /app/
RUN cd /app && npm install --quiet
WORKDIR /app
ENTRYPOINT [ "node" , "app.js" ]

Image erstellen:

$ docker build -t movd/puppeteer-resemble-testing:latest .

Container ausführen:

$ docker run --rm -v "${PWD}/screenshots:/app/screenshots" movd/puppeteer-resemble-testing:latest http://example.com

Das Image kann auch direkt vorgebaut von „Docker Hub“ geladen werden:

$ docker pull movd/puppeteer-resemble-testing:latest

Die hier erstellte Lösung basiert auf den Anforderungen im RRZK. Ich freue mich über Feedback und weitere Use-Cases, weil die Test-Logik eine einfache Schleife ist, ließe sich diese auch für andere Testreihenfolgen anpassen. Resemble.js ließe sich auch besonders gut in automatisierte Test mit Mocha oder Jest verwenden.

Visuelle Tests von Websites nach Updates und Änderungen

An der Uni Köln wird seit mehreren Jahren auf das Content-Managment-System (CMS) TYPO3 gesetzt. Immer mehr Institute und universitäre Einrichtungen greifen dafür auf die TYPO3-Angebote des RRZK zurück, sodass aktuell über 50.000 Seiten, auf über 960 Domains bereitgestellt werden. CMS und Webapplikationen erfordern regelmäßige Wartung und Bugfixes. Jedoch birgt jede Änderung oder Korrektur die Gefahr, dass Nebenwirkungen auftreten und Fehler vielleicht an einer unerwarteten Stelle im System verursacht werden. Regressionstests haben die Aufgabe dies vorzubeugen und Fehler zu finden. Das Ziel lautet: Das, was vorher funktioniert hat, soll auch nach einem Upgrade funktionieren.

Als im vergangenen Sommer die Upgrades unserer Systeme auf die Long Term Support Version v8 des CMS anstanden, haben wir deshalb nach einer Lösung gesucht. Ziel war es ein Programm zu finden, mit der die Front-Ends der Websites auf mögliche “Macken” nach den Upgrades getestet werden können.

Im Web-Bereich wurden in den letzten Jahren einige Libraries und Tools für visuelle Regressionstest entwickelt. Ein visueller Regressionstest führt Front-End- oder User-Interfacetests durch, indem es die Screenshots des User-Interface erfasst und mit den Originalbildern vergleicht. Wenn ein neuer Screenshot vom Referenzscreenshot abweicht, warnt das visuelle Regressionstool.

Visual Regression Testing mit Puppeteer und Resemble.js

“Puppeteer Logo” erstellt von Google, geteilt gemäß der CC BY 3.0-Lizenz

Seit 2017 stellt das EntwicklerInnen-Team des Chrome-Browsers mit Puppeteer eine Open-Source Node.js Library bereit, mit welcher ein Chrome Browser ohne eine grafische Oberfläche über eine API angesteuert und automatisiert werden kann. Mittels Puppeteer lassen sich z.B. Screenshots von Websites erstellen aber auch Interaktionen mit dem Front-End einer Website simulieren.

Die mit Puppeteer erstellten Screenshots, gilt es zu vergleichen. Hierfür bietet sich die gut gepflegte Library Resemble.js an. Diese ist darauf spezialisiert Bilddateien abzugleichen und Unterschiede auszugeben.

Teil 1: Screenshots erzeugen

Mit folgenden Tutorial will ich zeigen wie man mit Puppeteer Screenshots mehrerer Seiten erstellt. Ziel ist es am Ende einen Ordner mit Screenshots zu haben, welche anschließend als Ausgangsbilder für Tests dienen werden.

In einem weiteren Artikel werde ich darauf eingehen, wie diese Screenshots mit einem späteren Zustand verglichen werden können.

Der Code zu dieser Anleitung ist auch auf GitHub zu finden. Link zum Stand dieses Artikels

Node.js und Puppeteer installieren

Vorraussetzung: Zum Ausführen wird die JavaScript Runtime node samt der mitgelieferten Packetverwaltung npm benötigt.

Installationsanleitung für verschiedene Betriebssysteme gibt es auf nodejs.org. Für die Verwendung in Linux-Distributionen bietet NodeSource Repositories mit kompilierten Binaries an.

Nachdem nun Node.js installiert ist, muss noch ein Ordner erstellt und in das Verzeichnis gewechselt werden:

mkdir puppeteer-resemble-testing
cd puppeteer-resemble-testing

Als nächstes muss ein neues Projekt erstellt werden:

npm init

Für den Zweck dieser Anleitung, kann jede Frage mittels der Return-Taste bestätigt werden. Dabei wird eine neue package.json erstellt.

Nun muss Puppeteer installiert werden:

npm install puppeteer --save

Die Installation kann eine kurze Weile dauern. Denn es wird auch automatisch eine Version von Chromium heruntergeladen und innerhalb des Projekts abgespeichert.

Einzelnen Screenshot erstellen

Puppeteer steht jetzt bereit und kann verwendet werden. Unser erstes Skript erzeugt einen Screenshot der kompletten RRZK-Startseite. Das Ergebnis wird als screenshot.png abgelegt.

Inhalt von app.js:

const puppeteer = require('puppeteer')

const takeScreenshot = async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto('https://rrzk.uni-koeln.de/')
  await page.screenshot({ path: './screenshot.png', fullPage: true })
  await page.close()
  await browser.close()
}

takeScreenshot()

Puppeteer hat eine umfassende API-Dokumentation, alle obigen Parameter sind dort ausführlich beschrieben.

Puppeteer ist eine Promise-basierte Bibliothek, in diesem Fall bedeutet dies, dass die Aufrufe an die Chrome-Instanz asynchron durchgeführt werden. Damit der Code des Skript einfach zu lesen ist, wird async/await verwendet. Unsere takeScreenshot() Pfeilfunktion muss deshalb als async definiert werden.

Das Skript wird ausgeführt mit dem Befehl:

node app.js

Im folgenden werden wir auf dieses Skript aufbauen und nach und nach mehr Features hinzufügen.

Mehrere Screenshots in Stapelverarbeitung

Ziel ist es, weiterhin Screenshots vieler Seiten zu erstellen. Um etwas Ordnung zu waren, erstellen wir deshalb zunächst das Unterverzeichnis “screenshots”:

mkdir screenshots

Danach legen wir in app.js ein Array aus Objekten an. In einem Objekt wird jeweils die URL und der Screenshot-Dateiname hinterlegt.

const websites = [
  { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
  { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
  { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings'},
  { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downloads'}
]

Zum Test kann dieses Array nun durchlaufen werden:

for (const website of websites) {
  console.log(website.url)
  console.log(website.filename)
}

Beim erneuten Ausführen des Skripts werden nun die Inhalte des websites-Arrays ausgegeben.

Bisher ist in unserer takeScreenshot() Funktion die URL und der Dateiname hartkodiert. Die Funktion muss jetzt mit url und filename gefüttert werden. Daraus ergeben sich folgende Änderungen:

const takeScreenshot = async (url, filename) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto(url)
  await page.screenshot({ path: './screenshots/' + filename + '.png', fullPage: true })
    .then(console.log('Screenshot: ' + filename))
  await page.close()
  await browser.close()
}

Fast geschafft, nun noch beim durchlaufen des Arrays die takeScreenshot()-Funktion aufrufen und die Werte übergeben:

for (const website of websites) {
  // console.log(website.url)
  // console.log(website.filename)
  takeScreenshot(website.url, website.filename)
}

Unsere finale kompakte app.js sieht dann wie folgt aus:

const puppeteer = require('puppeteer')

const websites = [
  { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
  { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
  { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings'},
  { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downlods'}
]

const takeScreenshot = async (url, filename) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto(url)
  await page.screenshot({ path: './screenshots/' + filename + '.png', fullPage: true })
    .then(console.log('Screenshot: ' + filename))
  await page.close()
  await browser.close()
}

for (const website of websites) {
  // console.log(website.url)
  // console.log(website.filename)
  takeScreenshot(website.url, website.filename)
}

Nach dem Ausführen liegen nun unter „/screenshots“ vier PNG-Dateien.

Durch den .then Handler wird nach einlösen des Promise screenshot kurz Rückmeldung gegeben. Hiermit zeigt sich auch schön die asynchrone Arbeitsweise von Puppeteer.

In meinem nächsten Artikel werde ich das Skript um einen Vorher-Nachher-Vergleich mit Resemble.js erweitern, sowie das ganze mittels Docker so abpacken, dass man es einfach auf einem Linux-Server ausführen kann.

UniNow – Bequem durch’s Studium?

UniNow liefert genau das, was viele Studierende sich schon lange wünschen: Eine App fürs Studium, die alles kann, unter einer Oberfläche. Sich nicht mehr an gefühlt tausend unterschiedlichen Systemen anmelden zu müssen, die zudem für eine mobile Ansicht häufig nicht geeignet sind. Toll.

Der Haken an der Sache? Die App stammt von keiner Uni, sondern von einem Drittanbieter. Diesem Anbieter müsst Ihr die Zugangsdaten zu Eurem Studierendenaccount anvertrauen, an dem mittlerweile eine ganze Reihe von Diensten hängen: Mail, Klips, Ilias, Wlan, VPN, Eduroam, SOFS, Softwareshop, lizenzierte Zugänge u.s.w. Dienste also, die sehr persönliche Daten beinhalten bzw. Dienste mit hohem Missbrauchspotenzial. Mit Euren Zugangsdaten wird sich UniNow dann mittels seiner Server bei den unterschiedlichen Diensten der Uni anmelden und Eure Daten abfischen. Dass die Weitergabe der Accountdaten gegen jegliche Benutzungsordnungen verstoßen, eine Exmatrikulation bzw. fristlose Kündigung zur Folge haben kann, soll hier jetzt gar nicht das Thema sein. Vielmehr soll es um den Schutz Eurer Daten gehen und Euren eigenverantwortlichen Umgang mit diesen.

Die Uni Köln betreibt ziemlich viel Aufwand, um Eure Daten vor unberechtigten Zugriffen zu schützen. Es ist zudem penibel reglementiert, was gespeichert werden darf und wer zu welchem Zweck Einsicht in Eure Daten erhält. Sobald an der Uni irgendetwas mit personenbezogenen Daten passieren soll, müssen Datenschutzbeauftragter und ggf. Personalrat in langen Verfahren diesem zustimmen. Ein Beispiel dazu aus dem Tagesgeschäft des Helpdesk: Diesen erreichen häufig Anfragen, Euch Euer Passwort des Studierendenaccounts zuzumailen, da Ihr dieses vergessen habt. Das wird aus vielerlei Gründen nicht passieren: Der Helpdesk hat keine Einsicht auf diese Daten. Selbst wenn, wäre das nutzlos, da das Passwort nur verschlüsselt vorliegt. Und zu guter Letzt würde der Helpdesk solch sensiblen Daten nie über eine ungesicherte Mailverbindung hinauspusten.

All solche Bemühungen um Datenschutz würden natürlich ad absurdum geführt, wenn Ihr freiwillig Eure Daten aus der Hand gebt. Als Ausweg aus diesem Dilemma bliebe letztendlich nur der Rückgriff auf eine „vertrauenswürdige“ App bzw. einer App, die wenigstens den gleichen Datenschutzbestimmungen unterliegt, wie alle anderen Dienste der Uni auch. Eine solche App gibt es aber noch nicht. Die Gründe hierfür sind vielfältig. Positiv ist zu erwähnen, dass die Uni Köln – über das RRZK sowie das Prorektorat für Lehre und Studium – zur Zeit gemeinsam mit anderen Hochschulen am Projekt „StApps“ arbeitet, welches die Erstellung eigener Uni-Apps zum Ziel hat. Mehrere Mitarbeiter wollen dafür sorgen, dass wir Euch diese App möglichst bald bereitstellen können.

Verschlüsselung dank Let’s Encrypt

Dank der Initiative „Let’s Encrypt“ kann nun jede(r) Betreiber(in) von Webseiten diese kostenfrei auch per https bereitstellen, also den Transportweg mit SSL verschlüsseln. Dies war zwar bislang auch mit anderen Anbietern möglich, aber die damit erstellten Zertifikate waren nicht in den gängigen Browsern vertreten, sodass beim ersten Besuch der Seite stets eine Warnmeldung erschien. Bei „Let’s Encrypt“ ist dies nicht mehr so.

Dementsprechend habe ich auch die Domains auf meinem eigenen privaten Server nun per https verfügbar gemacht. Eigentlich wollte ich an dieser Stelle eine kleine Anleitung verfassen, da ich mir meinen Erfolgsweg bei diversen Blog- und Foreneinträgen zusammengesucht hatte. Inzwischen habe ich aber einen Beitrag entdeckt, der exakt meine Vorgehensweise beschreibt, incl. mehrerer Domains in einem Server und Auto-Renew per Cronjob. Daher verweise ich unten auf diesen Artikel von Dominic Pratt und ergänze lediglich, dass man inzwischen das Auto-Renew auch einfacher machen kann, nämlich per Crontab-Eintrag á la:

30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log

Zudem sollte man unbedingt darauf achten, ausschließlich sichere Protokolle und Cipher Suites zu aktivieren. Leider unterstützen nicht alle Server und Clients automatisch die neuesten und besten Einstellungen. Ein guter Kompromiss ist derzeit im Apache z.B.:

SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!ADH:!MD5:!RC4

Damit erreicht man im SSL-Test (https://www.ssllabs.com/ssltest) immerhin Grade A. Besser ginge es noch bspw. mit der Aktivierung von Forward Secrecy.

Hier nun der angesprochene Artikel:

Let’s Encrypt nutzen – eine Anleitung

Web-Browser und -Server… was reden die da eigentlich??? Server-Sockets mit nc

Wer sich mit Webservern und -browsern beschäftigt, wird irgendwann an den Punkt kommen, wo man einfach mal wissen will, „Was zur Hölle der Browser und der Server da genau bereden“

Tools wie wget helfen einem zwar, zu überprüfen, ob ein Server eine Datei wirklich ausliefert, aber viele Funktionen werden im HTTP-Header ausgehandelt und die Server reagieren dabei speziell auf die unterschiedlichen, browserspezifischen Anfragen.

Wenn es also so aussieht, als würde der Server einem Firefox andere Daten schicken, als einem Chrome, möchte man sicherlich wissen, ob es wirklich so ist und wie sich die Kommunikation generell unterscheidet.

Hier kann ein Trick helfen, über den ich kürzlich gestolpert bin. Er beruht auf dem Befehl nc (netcat), der in vielen Konstellationen hilfreich sein kann.

nc (netcat)

nc öffnet entweder eine Verbindung zu einem Server-Port (z.b. dem HTTP-Port (80) eines Webservers) und verhält sich dabei sehr ähnlich zu telnet.
Im Gegensatz zu telnet kann nc jedoch auch selbst einen Socket öffnen und auf eingehende Anfragen antworten. Hierfür wird der Switch -l verwendet.

Egal, in welcher Richtung die Kommunikation aufgebaut wird, verbindet nc immer die Standard-Eingabe (stdin) mit dem Kommunikationspartner.
Wird der Befehl also einfach so verwendet, können Sie direkt mit der Tastatur eingeben, was dem Kommunikationspartner gesendet werden soll.
Startet man auf einem Rechner ein lauschenden Server-Socket auf Port 54321:

# nc -l 54321

… kann man sich von einem anderen Rechner aus damit verbinden:

# nc hostname.des.servers 54321

Nun hat man eine Verbindung, die man zum Chatten verwenden kann.

Auf dem üblichen Weg lassen sich per Pipe auch die Ausgaben anderer Befehle über diese Leitung zu übertragen, statt die Tastatur Eingabe zu verbinden.
Startet man den Server z.B. so:

# ls -l | nc -l 54321

… bekommt man beim Verbinden mit dem Socket das aktuelle Verzeichnis, in dem der Server gestartet wurde angezeigt. Danach schließt sich die Verbindung und sowohl Server- als auch Client-Prozesse werden gestoppt.

Sehen was der Browser sendet:

So ist nc also schon nützlich: Man kann einen Server-Socket öffnen und im Webbrowser dessen Adresse eingeben. Im obigen Beispiel also http://localhost:54321/. Zwar wird im Browser so nicht unbedingt etwas sinnvolles angezeigt, weil unser nc-Server kein HTTP spricht, aber auf der Konsole können wir nun sehen, welche Anfrage genau der Browser an unseren Server geschickt hat:

GET / HTTP/1.1
Host: localhost:54321
Connecdern: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36 Vivaldi/1.0.403.20
Accept-Encoding: gzip, deflate, sdch
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: _pk_id.2478.1fff=bda69bf5b5894886.1457526065.1.1457526065.1457526065.

Dies ist nun aber nur eine einzelne Anfrage und wir können nicht die Antwort des Servers sehen, der uns eigentlich interessiert.

Umleiten einer Verbindung

Hier kommt nun eine weitere praktische Zutat ins Spiel: Named Pipes. Während wir mit dem Pipe-Symbol (Senkrechter Strich: |) nur die Ausgabe eines Programms mit der Eingabe des direkt folgenden Programms verbinden können, werden Named Pipes im Filesystem abgelegt und können auf diese Weise angesprochen. Diese Pipes werden mit dem Befehl mkfifo angelegt, sie verhalten sich dabei weitgehend wie Dateien.

Named Pipes können wir nun dazu verwenden, zwei nc-Prozesse miteinander zu verbinden: Einen, der auf Verbindungen vom Browser wartet, einen der eine Verbindung zu einem Webserver herstellt:

Anlegen der Named Pipe (der Name und Pfad können frei gewählt werden):

# mkfifo /tmp/proxyfifo

Zwei nc-Prozesse starten und verbinden

# nc -l -p 54321 </tmp/proxyfifo | nc server.domain.de 80 >/tmp/proxyfifo

Löschen der Pipe:

#rm /tmp/proxyfifo

Statt http://server.domain.de geben wir nun im Browser ein: http://localhost:54321.

Der Browser zeigt uns nun die Webseite des Servers an. Da unsere nc-Prozesse aber nur jeweils eine Verbindung aufbauen und sich danach beenden, werden wahrscheinlich Bilder und CSS-Dateien nicht vollständig geladen. Beherrschen Browser und Webserver beide „Keep-Alive“ werden jedoch durchaus mehrere Ressourcen übertragen bevor die Verbindung beendet wird – nur halt nicht alle.

Dies ist bis jetzt noch wenig sinnvoll, aber da die Verbindung nun durch unsere Hände geleitet wird, können wir uns einklinken, und mitlesen, was die beiden Gesprächspartner miteinander austauschen.

Belauschen der Verbindung:

# mkfifo /tmp/proxyfifo
# nc -l -p 8080 </tmp/proxyfifo | tee /dev/stderr | nc server.domain.de 8480 | tee /dev/stderr >/tmp/proxyfifo
# rm /tmp/proxyfifo

Der Einfachheit halber werden hier beide Kommunikationsrichtungen per tee zusätzlich auf der Standard-Fehlerausgabe (stderr) ausgegeben, was zum Mitlesen ausreicht.

In manchen Fällen möchte man darüber hinaus in die Kommunikation Eingreifen und die übertragenen Daten „on-the-fly“ manipulieren, auch dies ist mit diesem Ansatz möglich.

Da der Browser im obigen Beispiel glaubt mit „localhost“ zu kommunizieren, überträgt er in seinem HTTP-Request einen falschen Host, dies könnte man beispielsweise mit sed korrigieren wollen:

# mkfifo /tmp/proxyfifo
# nc -l -p 8080 </tmp/proxyfifo | sed -u "s/Host: localhost:8080/Host: rrzk.uni-koeln.de/g" | tee /dev/stderr | nc rrzk.uni-koeln.de 80
# rm /tmp/proxyfifo

Mit diesem Beispiel kann man die Webseite des RRZK über den Aufruf http://localhost:8080 laden und sich anschauen welche Daten ausgetauscht werden. (Wie erwähnt wird die Seite so nicht vollständig geladen).

Aufräumen:

Damit keine Bruchstücke liegenbleiben sollte man eine Named Pipe immer wieder neu anlegen und nach dem Aufruf direkt löschen.

Andere Anwendungsgebiete:

nc kann hier mit jeder Art TCP/IP-Verbindung umgehen, es ist also nicht auf Webkommunikation begrenzt. Besonders bei Anwendungen mit dauerhaft gehaltenen Verbindungen kann nc hier sein volles Potenzial ausspielen. Dies kann besonders Interessant sein bei:

  • Mail-Servern
  • Chat-Servern
  • Spiele-Servern
  • generell allem, bei dem man eine Serveradresse angeben muss

Sie können so rausfinden, welches Protokoll Ihr Netzwerkdrucker spricht und ob eine angeblich verschlüsselte Verbindung wirklich verschlüsselt ist (nc zeigt Binärdaten, wie verschlüsselte oder komprimierte Verbindungen und Dateiinhalte natürlich nur in Form von Datenmüll an).

Fazit:

Mit nc lassen sich auf viele Weisen Daten sammeln, die einem bei der Fehlersuche und -analyse hilfreich sein können.
Obige Beispiele sind nur ein erster Ansatz, sicherlich gibt es noch raffiniertere Kombinationen. Schreiben Sie gerne Ihre eignen Tricks zu diesem Thema in die Kommentare.

Die Zwickmühle beim Betreiben von verschlüsselten Netzdiensten aus Betreibersicht

Angesichts der Schwachstellen rund um bestimmte Verschlüsselungsarten (Poodle und Logjam waren die bekanntesten in letzter Zeit) hat man es als Betreiber schwer, die „optimale“ Konfiguration für Netzdienste zu finden, die verschlüsselte Kommunikation anbieten. Auf der einen Seite möchte man nach Möglichkeit Benutzer mit älteren Geräten, die noch nicht die neuesten Verfahren implementiert haben, nicht völlig von der Nutzung ausschließen, auf der anderen Seite gelten bestimmte Verfahren inzwischen als so unsicher, dass man diese als Serverbetreiber auf gar keinen Fall mehr anbieten möchte, um Nutzer nicht in trügerischer Sicherheit zu wägen.

Das gilt insbesondere dann, wenn als Szenario droht, dass ein Angreifer sich in die Verbindung zwischen Server und Client einklinken kann und dann dafür sorgt, dass die beiden eigentlichen Kommunikationspartner sich auf eine Primitiv-Verschlüsselung einlassen, weil der Angreifer vortäuscht, der jeweils andere Partner würde keine starke Verschlüsselung unterstützen („Downgrade-Attack“). In einem solchen Fall braucht der Angreifer dann nur noch die Primitiv-Verschlüsselung zu knacken anstelle der starken Verschlüsselung, die die beiden Kommpunikationspartner im Normalfall untereinander vereinbart hätten.

Webserver und andere Dienste

Weiter verkompliziert wird die Lage dadurch, dass Angriffsszenarien bei verschiedenen Netzdiensten unterschiedlich zu beurteilen sind. So sind Angriffe, die darauf beruhen, dass einer der Kommunikationspartner unfreiwillig dazu gebracht werden kann, häufig Datenpakete mit annähernd gleichem Inhalt zu senden, bei Webseiten bis zu einem gewissen Grad realistisch. Schafft es der Angreifer, das Opfer auf eine Webseite unter seiner Kontrolle zu locken, so sind automatisierte Datenabrufe durch eingebetteten Skriptcode denkbar, ohne dass es direkt auffällt. Bei anderen Diensten, beispielsweise E-Mail mit einem Mailclient, gibt es hingegen kein ernstzunehmendes Szenario, wie ein Angreifer von außerhalb den Computer des Opfers automatisiert dazu bringen kann, selbständig mit gewisser Regelmäßigkeit gleichartige Datenpakete zu verschicken. Das ist umso mehr eine Besonderheit des Webs gegenüber anderen Netzdiensten, weil das Angriffsziel „Session-Cookie“ auch nur im WWW-Bereich wirklich existiert.

Alte Software überall

Als wäre dieses Spannungsfeld nicht schon vertrackt genug, kann als weitere Schwierigkeitsstufe noch dazukommen, dass man nicht nur bei den fremden Kommunikationspartnern nicht die neuesten Verschlüsselungsverfahren voraussetzen kann, sondern dass man auch als Anbieter nicht all das nutzen kann, was theoretisch verfügbar ist. So steht am RRZK für den Betrieb von Webservern i.d.R. als Betriebsystem Red Hat Enterprise Linux 6 (RHEL6) zur Verfügung. Dieses bringt aber nicht die jeweils neuesten Programmversionen mit, sondern im Regelfall diejenigen Versionen, die bei Erscheinen des Betriebssystems aktuell waren, jedoch natürlich um in der Zwischenzeit bekanntgewordene Sicherheitslücken bereinigt.

Für RHEL6 bedeutet das ganz konkret, dass nur die Funktionen von openssl 1.0.1 zur Verfügung stehen (anstelle von openssl 1.0.2) sowie der Apache-Webserver aus der 2.2er Versionslinie (anstatt 2.4). Damit sind einige der Empfehlungen für Serverbereiter, wie Sie beispielsweise von den Entdeckern des Logjam-Problems gegeben werden, nicht umsetzbar. Das Definieren individueller sog. Diffie-Hellman-Parameter etwa ist erst mit den neuesten Versionen des Apache-Webservers (ab 2.4.8) möglich.

Empfehlung für Apache 2.2 unter RHEL6

Um nun unter RHEL6 einen Apache-Webserver zu betreiben, der Webseiten ausschließlich verschlüsselt (per https) ausliefert, und das unter den gegebenen Umständen mit einem als möglichst sicher geltenden Kompromiss, kann man folgende Einstellungen in der allgemeinen Apache-Konfiguration verwenden:


SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:MEDIUM:!ADH:!MD5:!RC4
SSLHonorCipherOrder on

Das ist zunächst einmal wenig spektakulär und kaum anders als das, was von den Logjam-Entdeckern empfohlen wird. In der Praxis läuft es darauf hinaus, dass die hier vorgeschlagene Konfiguration keine Präferenz für AES-128-Verschlüsselung enthält, so dass von den meisten heutigen Clients eine AES-256-Verschlüsselung verwendet werden wird. Ob man in dem gegebenen Kontext (Abruf von Webseiten) nun AES-128 oder AES-256 den Vorzug geben sollte ist für mich nicht zu beurteilen und wird in der Praxis wohl auch kaum eine Rolle spielen. Die theoretische Angreifbarkeit von AES-256 gegenüber AES-128 (hier ein alter und bekannter Blogartikel von Bruce Schneider dazu) bezieht sich auf mögliche Related-Key-Angriffe und ist in dieser Frage von untergeordneter Bedeutung.

Lässt man die oben genannte Einstellung SSLHonorCipherOrder hingegen auf dem Standardwert off, statt sie wie empfohlen auf on zu setzen, so ändert sich in der Praxis bei den meisten Browsern wenig. Durch das Setzen von SSLHonorCipherOrder on sorgt man de facto nur dafür, dass Benutzer mancher Versionen des Internet Explorers unter Windows 7 zu ihrem Glück gezwungen werden und auch bei ihnen eine Verbindung mit Forward Secrecy aufgebaut wird. Auf die Belange von Nutzern, die auch heute noch die extrem veraltete und per se unsichere Kombination von Internet Explorer 6 und Windows XP einsetzen, wird hier keine Rücksicht mehr genommen. Diese früher nicht ganz kleine Gruppe an Webseitenbesuchern machte in der Vergangenheit viele faule Kompromisse notwendig.

Dem Browser den Weg zur Verschlüsselung weisen

Legt man Wert darauf, in dem beliebten SSL-Servertest von Qualys besonders gut abzuschneiden, ist SSLHonorCipherOrder on unbedingt notwendig. Jedoch reicht das Setzen dieser und der anderen allgemeinen Verschlüsselungseinstellungen nicht aus. Für ein gutes Testergebnis muss man auch dafür sorgen, dass Besucher ausschließlich verschlüsselt mit dem Server kommunizieren. Solange unverschlüsselte Kommunikation im Web der Normalfall ist, sind dazu noch ein paar weitere Anstrengungen in der Apache-Konfiguration notwendig.

Webseitenbesucher, die – aus welchem Grund auch immer – eine unverschlüsselte Verbindung zum Server aufbauen möchten, müssen also von diesem Vorhaben abgehalten werden. Verwendet man dazu virtuelle Hosts in der Apache-Konfiguration, so kann man die Besucher, die keine Verschlüsselung verwenden, per


<VirtualHost *:80>
  RedirectMatch permanent (.*) https://meinhostname.uni-koeln.de$1
</VirtualHost>

umlenken. Für Named Virtual Hosts wäre hier natürlich noch eine weitergehende Konfiguration nötig. Gezeigt werden soll anhand dieses Konfigurationsschnipsels eigentlich nur, dass man die Besucher am besten mit einer dauerhaften Umleitung (permanent) auf die verschlüsselte Version der Website lenkt.

https und nur https, in guten wie in schlechten Zeiten

Als Kür sendet man dann noch die Information an die Browser der Webseitenbesucher, es in Zukunft gar nicht erst über eine unverschlüsselte Verbindung zu probieren, sondern ausschließlich per https mit dem Server zu kommunizieren:


<VirtualHost *:443>
  # hier stehen normalerweise Einstellungen wie ServerName, DocumentRoot etc.
  
  # vvv Einschalten von HSTS aka HTTP Strict Transport Security, RFC 6797
  Header always set Strict-Transport-Security: max-age=31536000
  # ^^^ Strict Transport Security, funktioniert mit Apache 2.2.15
</VirtualHost>

In diesem Beispiel wird der abrufende Browser instruiert, dass die Information, mit diesem Server nur verschlüsselt zu kommunizieren, ca. ein Jahr lang (31536000 Sekunden) gültig ist. Dies kann man zum Glück auch schon mit dem bei RHEL6 mitgelieferten Webserver Apache 2.2.15 so einstellen.

Mit den auf diese Weise optimierten Einstellungen erhält der Webserver dann von dem erwähnten SSL-Test die Bestnote A+ attestiert, vorausgesetzt die übrigen Einstellungen (zum Zertifikat und der Kette der Zwischenzertifizierungsstellen etc.) sind ebenfalls in Ordnung. Diese Einstellungen sind aber nicht spezifisch für RHEL6, daher soll hier darauf nicht weiter eingegangen werden.

Streaming auf AppleTV über Subnetzgrenzen hinweg

airplay_appletvMit dem Update auf Softwareversion 6.1 bekam das AppleTV in Verbindung mit iOS 7.1 und höher ein neues Feature: AirPlay Discovery via Bluetooth. Dies ermöglicht nun viel einfacher die Verbindung zwischen AppleTV und AirPlay-Klienten, wenn sich diese nicht im selben IP-Subnetz befinden. Einrichtungen mit vielen IT-Anwendern sind gezwungen, einzelnen Abteilungen oder Arbeitsgruppen eigene getrennte IP-Subnetze zuzuordnen. Oft wird dem WLAN-Netz auch ein eigener IP-Bereich zugewiesen. Befindet sich das AppleTV dann netztechnisch im IP-Bereich einer Arbeitsgruppe, müssen die Klienten einige Barrieren überwinden.

Haben beide Geräte Bluetooth aktiviert und die aktuelle Softwareversion installiert, wird auf dem iPad/iPod/iPhone das AppleTV angezeigt. Unter Umständen ist jedoch keine Kommunikation zwischen den Klienten möglich. AirPlay erfordert, dass vom AppleTV eine neue TCP/UDP-Session zum Endgerät aufgebaut werden kann, obwohl die ursprüngliche Session vom Endgerät initiiert wurde.

Damit schließt AirPlay Umgebungen aus, die NAT mittels Port-Addresstranslation durchführen. Diese Hürde kann man jedoch durch Aufbau einer VPN-Verbindung überwinden. Verteilt das VPN-Gateway jedem Endgerät eine eigene IP, kann so doch AirPlay eingesetzt werden.

Nun müssen gegebenenfalls noch Freigaben eingerichtet werden, wenn die Subnetze durch eigene Firewalls abgesichert sind. Leider verwendet AirPlay dynamische Ports, so dass ganze Bereiche freigegeben werden müssen. Dabei verläuft die Kommunikation von hohen Ports (49152 – 65535) zu hohen Ports.

Folgende Freigaben sollten eingerichtet werden:Firewallregeln

Je nach Firewall-Typ müssen auch die Rückantworten zu den fünf Freigaben eingetragen werden, also Quellnetz/-port und Zielnetz/-port vertauscht.

Mit diesen Freigaben sollte AirPlay funktionieren. Wir konnten mit den gewählten Einstellungen Videos, Musik und den Bildschirminhalt erfolgreich auf das AppleTV streamen.

Projekt Mepevea – D’r Zoch kütt!

Als umweltbewusster oder zumindest geiziger Mitarbeiter des Öffentlichen Dienstes verzichtet man in der Regel bei längerer Anfahrt zum Arbeitsplatz auf den privaten PKW und nimmt freudig am öffentlichen Personennahverkehr (ÖPNV) teil. Sprich: Die Deutsche Bahn (und in Köln auch die KVB) ist unser Freund! Gerüchteweise sind die bereitgestellten Verkehrsmittel nicht immer dann vor Ort, wenn man es laut Fahrplan erwarten könnte. Damit man die daraus resultierende Wartezeit nicht am Bahnsteig, sondern am Frühstückstisch bzw. im bequemen Bürosessel verbringen kann, sind aktuelle Informationen über die Verspätungen unerlässlich.

Nun hat sich in der Vergangenheit der Service der DB dahingehend deutlich verbessert. So sind die Verspätungsinformationen inzwischen minutengenau und in Realzeit sowohl im Web als auch mittels der App „DB Navigator“ abrufbar. Der o.g. Mitarbeiter des Ö.D. ist allerdings nicht nur geizig (jaja, und umweltbewusst), sondern auch klickfaul und noch dazu ein Spielkind. So kam ich auf die Idee, sowohl in meinem trauten Heim als auch im Büro mittels ohnehin vorhandener Technik einen (für mich) optimalen Anzeigebildschirm zu basteln.

Dieser sollte nicht nur die aktuellen Verspätungen meiner Zugverbindungen, sondern auch weitere interessante Informationen anzeigen, genauer gesagt: Aktuelle Nachrichten, Wettervorhersage und (zuhause) zusätzlich das Kamerabild einer per WLAN verbundenen IP-Kamera. Als Hardware kamen ein günstiger und dank Notebook-Anschaffung ohnehin kaum noch gebrauchter PC-Bildschirm sowie zeitgemäß ein Raspberry Pi zum Einsatz. Das System sollte in jedem Fall ohne weitere Peripherie, speziell ohne Maus und Tastatur, auskommen. Softwareseitig setzte ich daher auf Google Chrome im Kiosk-Modus. Mittels der Erweiterung „Easy Auto Refresh“ kann man dafür sorgen, dass Chrome die angezeigte Seite automatisch einmal pro Minute neu lädt. Das Kamerabild läuft ohnehin im Streaming-Mode.

Der graphische Desktop des Raspi musste so eingestellt werden, dass er sich nicht automatisch abschaltet. Die Kontrolle über die Anzeige sollte ausschließlich per Ein/Aus-Knopf des Monitors ablaufen. Dies erreicht man über die eine Einstellung in LightDM.

Da ich mir die Installation und Konfiguration eines Webservers sparen wollte, verwende ich eine einfache lokale HTML-Seite auf dem Raspi. Die beiden gewünschten Elemente „Aktuelle Nachrichten“ und „Wettervorhersage“ sind sehr leicht über passende Widgets realisierbar. Ich habe hierzu die Angebote von wetterdienst.de und rp-online genutzt, es gibt jedoch zahlreiche weitere Anbieter.

mepevea

Richtig interessant wurde es dann bei der Einbindung der Verspätungsanzeige. Wie ich feststellen musste, bietet die Bahn leider keine geeignete API zu diesem Zweck. Mir blieb nichts anderes übrig als die entsprechende Webseite zu parsen. Diese Erkenntnis war die Geburtsstunde von Projekt „Mepevea“ (MEin PErsönlicher VErspätungsAnzeiger).

Wie erwähnt wollte ich auf die Installation und den Betrieb eines Webservers verzichten. Die Anzeige soll ja ohnehin nur für mich persönlich laufen. Daher musste ich die eigentliche Logik nebst Parser in ein Pythonskript packen, welches per Cronjob aufgerufen wird (ja, ich arbeite unter Linux und ignoriere Windows seit Jahren – die Portierung sollte aber kein großes Problem darstellen). Als Basismodul für den Parser dient natürlich „BeautifulSoup“, darüber hinaus werden urllib zum Abruf der Seite und einige weitere Module benötigt. Der Start lautet also:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import bs4, urllib2, time, fileinput, sys, urllib

„fileinput“ verwende ich, um später den <div>-Block im HTML durch die korrekten Daten auszutauschen, z.B.:

for line in fileinput.FileInput("/home/pi/anzeige/bahnlinks.html",inplace=1):
if line.startswith('<div id="bahn">'):
   line = text
   sys.stdout.write(line)

Natürlich macht es Sinn, abhängig vom Wochentag und der Tageszeit die Anzeige zu variieren (Hinfahrt, Rückfahrt, Abend/Wochenende), also z.B.:

timestamp = time.localtime(time.time())
if timestamp[6] > 4:
   textlist.append("<b>Bahnanzeige erst am Montag wieder! Schönes Wochenende!</b>")

Hier wird schon klar: Individuelle Anpassung ist unerlässlich und ich kann die Beispiele nur anreißen. Keine Sorge: Am Ende werde ich als „großes Beispiel“ mein komplettes Skript bereitstellen.

Zentrales Element des Skriptes ist die Parserfunktion. Sie erhält als Parameter die URL der Bahn (dazu später) und jagt sie durch BeautifulSoup:

def parser(url):
   page = urllib2.urlopen(url).read()
   soup = bs4.BeautifulSoup(page)

Man möge mir an dieser Stelle glauben, dass wir die spannenden Inhalte erhalten, wenn wir nach den Keywords, genauer gesagt den <td>-Klassen „overview timelink“ und „overview tprt“ suchen:


zeilen = soup.find_all('td', {"class" : "overview timelink"})
verspaetungen = soup.find_all('td', {"class" : "overview tprt"})

Schon hier erkannt man, wo das größte Problem unserer schönen Bastelei liegt: Sollte die Bahn die Klassennamen aus irgendwelchen Gründen ändern, funktioniert natürlich nichts mehr. Das gleiche gilt für die URLs und die HTML-Struktur. Genau aus diesem Grund gibt es ja i.d.R. kapselnde APIs, aber die stehen hier wie gesagt nicht zur Verfügung.

Standardmäßig erhält man von der Bahn die nächsten drei Züge ab dem definierten Zeitpunkt. Ich habe die finale Version noch so erweitert, dass man dies variieren kann, aber das würde hier zu weit führen. Ebenso müsste ich nun eigentlich auf die Details von BeautifulSoup eingehen, um den folgenden Codeblock zu erläutern. Aber auch dies möchte ich mir sparen und auf die gute Online-Dokumentation des Moduls verweisen. Unsere Verbindungen sowie die aktuellen Verspätungen erhalten wir so:


parsedtext = ''
zaehler = 0
for zeile in zeilen:
   for zelle in zeile.children:
      parsedtext += zelle.contents[0]
   parsedtext += '<span style="color: red;">'
   for verspaetung in verspaetungen[zaehler].children:
      if str(verspaetungen[zaehler]).count("okmsg") > 1 or str(verspaetungen[zaehler]).count("red") > 1:
         parsedtext += verspaetung.contents[0]
         break
   parsedtext += '</span>'
   zaehler += 1

Ich bin mir zu 99% sicher, dass dies nicht die eleganteste Version ist, um die Informationen zu erhalten und aufzubereiten. Aber sie funktioniert. Wer das Ganze kürzer, schöner und verständlicher hinbekommt, ohne dass die Funktionalität leidet, möge sich bei mir melden.

Kommen wir nun zu den benötigten URLs. In einer ersten Version hatte ich pro Zug eine URL auf Basis des Bahntools „query2.exe“ verwendet, die auch deutlich einfacher zu parsen war (Anmerkung: Bitte von der Endung „.exe“ nicht täuschen lassen: Es handelt sich um einen Webservice, nicht um ein lokales Programm.). Leider musste ich feststellen, dass die Bahn bei jeder (geplanten) Mini-Fahrplanänderung die URL komplett verändert. Auf Dauer war das also leider keine Lösung. Stattdessen verwende ich nun die „Vorstufe“ namens „query.exe“. Diese hat klar definierte und – hoffentlich – dauerhaft beständige Parameter. Als Parameter benötigen wir den Code des Startbahnhofs, den Code des Zielbahnhofs und die Startzeit.

Während die Startzeit natürlich jedem selbst überlassen bleibt und einfach in der Form hh:mm verwendet wird, muss man sich die Codes (sog. IBNR) der Bahnhöfe einmalig heraussuchen. Dies geht zum Glück sehr einfach mittels einer Onlinesuche.

Lautet die IBNR des Startbahnhofs bspw. 8000208, die des Zielbahnhofs 8000133 und die gewünschte Startzeit ist 17:00 Uhr, lautet die gesuchte URL:

http://reiseauskunft.bahn.de/bin/query.exe/dox?S=8000208&Z=8000133&time=17:00&start=1

Damit lässt sich nun für jede beliebige Verbindung und Kombination von Tageszeiten ein passender Anzeiger (eben ein „Mepevea“) bauen.

Für weitere Ideen, Verbesserungsvorschläge etc. bin ich jederzeit dankbar. Und wenn jemand die Bahn überreden könnte, doch mal eine entsprechende API bereitzustellen, das wäre ein Traum. 😉

Wie versprochen: Den vollständigen Text des Skriptes sowie eine Beispiel-HTML-Seite findet man unter http://dl.distinguish.de/mepevea.zip

Passwörter mit Dashlane aufbewahren, einsetzen und synchronisieren

Meine Ausgangspunkt

Mein digitales Leben ist „geschützt“ durch gefühlt Zillionen von Passwörtern – faktisch sind es einige Hundert, aber mit Erinnerungsvermögen ist da nichts mehr zu machen. Bisher nutzte ich meinen Browser sowie eine lokale Anwendung auf meinem Smartphone zur Verwahrung dieser Credentials. Das war mehr schlecht als recht, denn mein Gerätebestand umfasst OS X, Android, Windows7 und iOS — alles in allem ein mittlerer Alptraum.

Dashlane „betritt“ mein digitales Leben

Ein Artikel von David Pogue [1][2] machte mich auf Dashlane aufmerksam. Das Tool behebt mein Problem inzwischen wirklich recht gut. Der „Migrationsaufwand“ war bei mir nicht wirklich vernachlässigbar, weil die Import-Funktion bei mir nicht so genial funktioniert hat – also knapp unter „Do What I Think“-Niveau. Auch das Sortieren der einzelnen Records nach Kategorien nervt (auch wenn es vernünftig ist).

Nach dieser Einstiegshürde ist Dashlane aber wirklich nützlich. Ich nutze die Anwendung nicht lokal, sondern synchronisiere darüber mein gutes halbes Dutzend Devices. Das funktioniert kostenlos für 30 Tage, danach nur mit einer kostenpflichtigen Subscription – Eine schwierige Abwägung zwischen Geiz und Komfort!

Meine Konfiguration ist nun so, dass ich Dashlane auf meinem Notebook, Arbeitsplatzrechner, Android-Smartphone, iPad und den privaten Rechnern nutze. Das Smartphone nutze ich auch als One-Time-Password-Generator (Google Authenticator).

Erfahrungen

Dashlane ist schon sehr pfiffig darin, Formulare richtig auszufüllen, aber David Pogues Beschreibung erscheint mir etwas zu euphorisch. Mir sind schon etliche Seiten untergekommen, bei denen die Logik nicht funktioniert. Bei englischsprachigen Seiten funktioniert das Auto-Ausfüllen aber gut.

Wirklich cool ist, dass man das Ausfüllen auf „Subdomains“ ausdehnen bzw. einschränken kann und die Möglichkeit mit mehreren Accounts für eine Website umgehen zu können. Nett ist auch die Möglichkeit, sichere Passwörter erzeugen zu lassen; nützlich sind die Hinweise auf vermutliche kompomitierte Web-Site, bei denen man Accounts hat. Das Wallet habe ich bisher wenig (aber erfolgreich) genutzt. Die anderen Features wie Nachrichten-Austausch und so nutze ich nicht.

Risiken und Nebenwirkungen

Ach ja, das Synchronisieren geht natürlich über die Server von Dashlane. Das Sicherheitsmodell scheint mir wasserdicht zu sein, solange man auf keinem seiner Endgeräte einen Keylogger eingefangen hat oder sonst irgendwie das Masterpasswort verliert. Im Normalfall geht also „unbrauchbarer“ AES256-Datenschrott über den Austauschpunkt. Für die Sicherheit von AES256 gibt es sogar eine Zusicherung der NSA – super!

Um etwas Schutz gegen die Keylogger-Problematik zu haben, nutze ich die Zwei-Faktor-Authentifizierung. Dann kann niemand ein Endgerät mit meinem Dashlane-Datenaustausch-Account verknüpfen. Wenn ich mein Android-Smartphone UND mein Masterpasswort verliere, wäre das natürlich ganz schlecht.

Nun gut, die Prism-Tempora-Sonstwas-Maschinen wissen nun wirklich ganz genau, welche Endgeräte ich nutze. Vermutlich ist dies ja auch für jene Leute die wirklich interessante Information – Passwörter von Individuen brauchen die eh nicht so dringend. Also: „Ja, man gibt Futter für diese Maschinen“, damit das Korrelieren im Big-Data-Wust dann besser geht.

Zusammenfassung

Angesichts des Nutzens finde ich das Werkzeug sehr interessant und werde es weiterhin einsetzen. Unberührt von der technischen Coolness ist der Umstand, dass beispielsweise eine Unternehmspolicy das Hinterlegen bestimmter Credentials in Password-Manager-Anwendungen untersagen kann.

Prüfung von Sicherheitszertifikaten

Die Prüfung der von Servern verwendeten Zertifikate hinsichtlich ihrer Gültigkeit  und Vertrauenswürdigkeit ist heutzutage ein elementarer Bestandteil der persönlichen Sicherheit im Internet. Viel zu oft werden solche Hinweise zwar einfach weggeklickt, spätestens bei Shops, die die Angabe der Konto- oder Kreditkartendaten erfordern, und erst recht beim Online-Banking sollten aber wirklich alle Internetnutzer misstrauisch werden, sobald der Browser eine entsprechende Warnung anzeigt.

Aus diesem Grund ist auch die Uni Köln seit Jahren einer Certificate Authority angeschlossen, die in allen gängigen Browsern und Mailclients als vertrauenswürdig bekannt ist, namentlich der Root-CA der Deutschen Telekom. Denn auch an der Uni müssen z.T. sehr vertrauliche Daten über das Netz geschickt werden, z.B. Prüfungsergebnisse, Accountdaten oder auch E-Mails.

Umso wichtiger ist es, dass die entsprechende Prüfung der gemeldeten Sicherheitszertifikate möglichst automatisiert und zuverlässig erfolgt. Hierfür gibt es ein eigenes Netzprotokoll, das „Online Certificate Status Protocol“ (OCSP). Es prüft automatisch die Gültigkeit des vom aufgerufenen Server gesendeten Zertifikates. Dies ist leider nicht bei allen Browsern und Mailclients korrekt voreingestellt. Zum Teil wird nur auf den Servernamen geprüft, zum Teil aber auch gar nicht. Wir möchten daher hier für die wichtigsten Programme kurz beschreiben, wo die korrekten Einstellungen vorzunehmen sind.

Mozilla Firefox / Mozilla Thunderbird
Bei den beiden Mozilla-Programmen finden sich die Einstellung in einem übersichtlichen Menü unter „Einstellungen“ → „Zertifikate“ → „Validierung“.

Google Chrome
In Chrome muss unter „Einstellungen“ → „Erweiterte Einstellungen anzeigen“ → „HTTPS/SSL“ ein Häkchen bei „Serverzertifikate auf Sperrung prüfen“ gesetzt werden.

Internet Explorer / Windows Mail / Outlook
Alle drei sind fest mit dem Windows-Zertikatspeicher verbunden, den man im Internet Explorer unter „Extras“ → „Internetoptionen“ → „Inhalte“ → „Zertifikate“ erreicht (die Menüfolge mag in den verschiedenen Versionen etwas anders sein). OCSP wird von Windows erst seit Vista – und damit auch in Windows 7 und 8 – unterstützt, für XP fehlt die Funktion leider. Die Aktivierung erfolgt in „Extras“ → „Internetoptionen“ → „Erweitert“ und umfasst die Einstellungen „Auf gesperrte Zertifikate überprüfen“ und „Auf gesperrte Zertifikate von Herausgebern überprüfen“.

Apple Safari
Safari nutzt den zentralen Schlüsselbund von MacOSX. Diesen erreicht man in der Regel über das Anwendungsmenü im Bereich „Utilities“ oder „Werkzeuge“. Innerhalb des Schlüsselbunds kann man im Menü „Einstellungen“, Karteireiter „Zertifikate“ die passenden Einstellungen wählen. In aller Regel ist dies bei OCSP und CRL jeweils „Bester Versuch“, die Priorität sollte auf OCSP liegen.

Opera
In Opera (ab Version 9) muss man den Umweg über die Seite „about:config“ im Browser gehen. In den „Security Prefs“ kann man den Eintrag bei „OCSP Validate Certificates“ ändern. Da dieser aber standardmäßig aktiviert ist, muss man in der Regel nichts ändern.