Eine von sieben

09. September 2010 von Jonas Pasche

heise meldet:

Das Finanzgericht Köln hat in sieben Musterverfahren Klagen gegen die Steueridentifikationsnummer (Steuer-ID) abgewiesen. Der 2. Senat hat in den nun bekannt gegebenen Entscheidungen vom 7. Juli 2010 „erhebliche Zweifel“ an der Verfassungsmäßigkeit der Steuernummer geäußert (unter anderem 2 K 3093/08, 2 K 3986/08, 2 K 3265/08). Allerdings kam er nicht zu der Überzeugung, dass das Recht des einzelnen Bürgers auf informationelle Selbstbestimmung schwerer wiegt als das Interesse der Allgemeinheit an einer gleichmäßigen Besteuerung.

Die genannte 2 K 3265/08 war meine; insofern wird man sich denken können, dass ich im Ergebnis einigermaßen enttäuscht bin.

Erfreulich ist, dass der Senat in weiten Teilen der Argumentation der Kläger durchaus folgen konnte und bemerkt:

Ungeachtet dessen hat der Senat jedoch Bedenken daran, ob die Schwere des Eingriffs in das Recht auf informationelle Selbstbestimmung durch die Regelung der §§ 139a, 139b AO durch das Gemeinschaftsinteresse an der Gleichmäßigkeit der Besteuerung aufgewogen wird, wenngleich er diesbezüglich keine volle Überzeugung gewinnen konnte.

So begründet die Schaffung der steuerlichen Identifikationsnummer die Gefahr der Bildung eines „großen“ zentralen Datenpools durch den Staat.

[…]

Und diese Datenpools können mitunter auch Rückschlüsse auf Tatsachen zulassen, die keinen unmittelbaren steuerlichen Bezug haben. So können z.B. aus der Höhe des Krankenversicherungsbeitrags Rückschlüsse auf den Gesundheitszustand des Versicherungsnehmers oder gar dessen Ehefrau oder dessen Kinder gezogen werden […]

Zwar hat der Gesetzgeber die Bildung weiterer Datenpools unter der Steueridentifikationsnummer mit der Regelung des § 139b Abs. 2 Satz 2 Nr. 2 AO auf ein Minimum beschränkt, […]. Jedoch ändert dies nichts daran, dass – wenn auch auf ein bestimmtes Minimum beschränkt – weitere nach Steueridentifikationsnummern geordnete Datenpools entstehen, die möglicherweise miteinander vernetzt werden könnten.

Es ist also beileibe nicht so, dass meine bzw. unsere Sorgen bezüglich der „Personenkennziffer durch die Hintertür“ nicht ernst genommen worden wären – und das ist in einer Zeit, in der gesundes Misstrauen schnell als Paranoia interpretiert wird, nicht zu unterschätzen.

Ich möchte an dieser Stelle noch einmal betonen, dass vor dem Datenschutz erstmal die Datensparsamkeit kommt. Es ist fast als ungeschriebenes Gesetz anzusehen: Es wird Sicherheitslücken geben. Es wird Missbrauch geben. Keine noch so harsche Rechtsvorschrift wird das verhindern können, und dann wird das Geschrei groß sein. Daher noch einmal in aller Deutlichkeit: Der beste Schutz wäre, diese gigantische Datensammlung gar nicht erst anzulegen: Wo nichts ist, kann nichts kompromittiert und nichts missbraucht werden. Es ist ja nun nicht so, dass unser Staat im Moment nicht so recht wissen würde, wie er denn bitte seine Steuern eintreiben könnte. Es ist nur eben aufgrund fehlender Zentralisierung nicht ganz so einfach – und damit ist aber eben auch ein Missbrauch von Daten und eine Zusammenführung mit anderen Quellen ebenfalls nicht ganz so einfach. Ich persönlich halte das nach wie vor für eine gute Sache.

Und hier mag man mich dann doch als Paranoiker bezeichnen, aber ich prognostiziere neben Sicherheitslücken und Missbrauch noch etwas weiteres: Es wird Zusammenführungen mit anderen Daten geben. Die Steueridentifikationsnummer wird sich mittelfristig als Einstieg in die Personenkennziffer herausstellen, die das Bundesverfassungsgericht bereits als unzulässig gebrandmarkt hat. Es wäre wirklich eine große Überraschung, wenn angesichts der technisch fraglos bestehenden Möglichkeiten tatsächlich keinerlei Begehrlichkeiten geweckt würden. Wetten wir?

Zum guten Schluss möchte ich einfach noch einmal zitieren, wofür genau wir eigentlich in Kauf nehmen sollen, von der Wiege bis 20 Jahre nach der Bahre sämtliche Bundesbürger in einer riesigen Datei zu katalogisieren:

Der Zweck der Zuteilung und Verwendung der Steueridentifikationsnummer insbesondere zur Datenspeicherung nach §§ 139a, 139b AO besteht im Wesentlichen in der gleichmäßigen Besteuerung, die durch einen gleichmäßigen Gesetzsvollzug sichergestellt sein muss, und in der Vereinfachung des Besteuerungsverfahrens.

Gleichmäßiger. Einfacher. Das ist alles.

Wie gesagt: Dass ich mit dem Ergebnis nicht zufrieden bin, wird sich jeder denken können. In den nächsten Tagen werde ich mich daher erstmal intensiver mit der Urteilsbegründung auseinandersetzen und mich dann mit meinem Anwalt beraten, ob und wie wir weiter verfahren werden.

Endlich verstehen: Unicode mit PHP5 und MySQL

06. August 2010 von Jonas Pasche

Wenn es um PHP und Unicode geht, am Besten auch noch in Zusammenhang mit MySQL, gibt es eine Vielzahl an Tipps und Howtos. Manche davon sind falsch, die meisten zum Glück zwar richtig, doch selbst die, die Richtiges vermitteln, tun das manchmal nur durch Zufall, und oftmals fehlt das, was für eine verlässliche Handhabung am wichtigsten ist: Eine Erklärung, warum man dieses und jenes so und nicht anders machen soll.

Dieser Artikel hat den hehren Anspruch, Erklärungen zu liefern, Verständnis für Zusammenhänge zu wecken und schließlich dabei unterstützen, Unicode mit PHP richtig zu machen – ohne dass Sie dafür ein dickes Handbuch lesen müssen. Natürlich bleiben dabei viele Details auf der Strecke, deshalb sehen Sie diesen Artikel pragmatisch: Er sollte für den gewöhnlichen Alltag reichen und Sie dabei nicht ganz dumm sterben lassen. Für tiefergehende Informationen – lesen Sie ein Buch.

Zunächst einmal gilt es eine Sache zu verstehen, nämlich:

„Zeichen“ und „Bytes“ sind etwas völlig Verschiedenes.

Das ist vor allem wichtig für diejenigen, die bei diesem Thema erstmal gleich die gute alte Zeichensatztabelle im Kopf haben. Da haben wir nämlich gelernt: Ein Zeichen ist ein Byte; mit einem Byte kann man 256 verschiedene Zeichen darstellen. Wenn man gut aufgepasst hat, weiß man vielleicht auch noch, dass die ersten 128 Zeichen immer gleich waren („US-ASCII“) und die nächsten 128 Zeichen darüber zeichensatzspezifisch waren – dass das Zeichen mit der Nummer 188 z.B. in ISO-8859-1 „¼“ bedeutet, während es in ISO-8859-2 „ź“ bedeutet.

Lektion Nummer 1: Diese Zeichensatztabellen bitte alle sofort vergessen. Zu einem späteren Zeitpunkt, wenn das mit Unicode klar geworden ist, kann man sich gerne wieder daran erinnern und sie sauber in einen technischen Zusammenhang bringen, aber für den Moment: Weg damit. Und damit auch gleich weg mit der Vorstellung, dass „1 Byte“ in irgendeiner Art und Weise „1 Zeichen“ bedeuten würde. In der gebräuchlichen UTF-8-Kodierung kann ein Zeichen nämlich durchaus bis zu vier Bytes benötigen.

Hier nun daher der große, grundsätzliche Pferdefuß von PHP in allen Versionen < 6, direkt aus der offiziellen Quelle:

A string is series of characters, therefore, a character is the same as a byte. That is, there are exactly 256 different characters possible. This also implies that PHP has no native support of Unicode.

Bitte hier kurz innehalten, durchatmen und sich noch einmal ganz deutlich vor Augen führen: PHP hat keine Ahnung von Unicode.

Um das ad hoc etwas greifbarer zu machen, hier ein konkretes Beispiel für ein PHP-Script, das ich mit einem Texteditor erstellt habe, der die Datei UTF-8-kodiert abspeichert (was heutzutage eigentlich alle tun):

$ cat test-strlen.php
<?php echo strlen("ä"); ?>

$ php test-strlen.php
2

Überrascht? Wichtig ist das vor allem für Leute, die sonst in anderen Programmiersprachen entwickeln, die von Haus aus UTF-8-tauglich sind. Nehmen wir zum Beispiel Perl: Dort werden intern Strings als Zeichen verarbeitet; ein interner UTF-8-Marker hilft Perl dabei, zu verstehen, ob die im RAM liegenden Bytes einer Zeichenkette als UTF-8 interpretiert werden müssen, um sinnvolle Zeichen zu ergeben, oder nicht. Mit PHP muss in diesem Punkt grundsätzlich anders gearbeitet werden.

Eines ist aber so oder so wichtig zu verstehen: Erst die Angabe eines Zeichensatzes kann eine Folge von Bytes sinnvoll in eine Folge von Zeichen überführen, oder griffiger für einen Klebezettel an der Kühlschranktür ausgedrückt:

1-4 Bytes + Zeichensatzangabe = Zeichen

Wann immer also auf einer Website oder in einer E-Mail „kaputte“ Umlaute zu beobachten sind, so liegt das immer daran, dass entweder eine Zeichensatzangabe vorhanden ist, die aber nicht stimmt, oder dass gar keine vorhanden ist, der Client den Zeichensatz rät und dabei den falschen rät. Das kann man ihm aber kaum zum Vorwurf machen – es ist Aufgabe des Erstellers des Inhalts, sich um die korrekte Zeichensatzangabe zu kümmern.

Nun findet auf Websites aber jene Menge Interaktion statt, und so gibt es gleich eine ganze Reihe von Bereichen, in denen Unicode eine Rolle spielt:

  • ein PHP-Script sendet Daten an den Browser
  • ein Browser sendet Daten an ein PHP-Script
  • ein PHP-Script liest Daten aus einer Datenbank
  • ein PHP-Script schreibt Daten in eine Datenbank

Jegliche Art von Kommunikation basiert aber auf Bytes und nicht auf Zeichen, was in der Konsequenz bedeutet: Bei jeglicher Art von Kommunikation muss eine Zeichensatzangabe mitgegeben werden, damit nichts schiefgeht. Das klingt auf den ersten Blick aufwendig, aber um es ganz deutlich zu sagen: Daran führt kein Weg vorbei.

Nehmen wir den einfachsten Fall: Ein PHP-Script sendet Daten an den Browser. Ein PHP-Script ist eine Textdatei, insofern wird der Zeichensatz dieser Textdatei durch den Editor festgelegt, der sie speichert. Dann ist wichtig, dass der Browser über den Zeichensatz informiert wird. Da gibt es viele Möglichkeiten; die wichtigsten wären:

  • das PHP-Script führt header("Content-type: text/html; charset=utf-8"); aus
  • im HTML-Header wird <meta http-equiv="Content-type" content="text/html; charset=utf-8"> angegeben
  • in der php.ini wird default_charset = utf-8 angegeben
  • in der Apache-Konfiguration wird AddDefaultCharset UTF-8 angegeben

Der umgekehrte Weg, dass ein Browser Daten an ein Script sendet, ist überraschend unklar: HTTP-Requests sehen nämlich keinen Zeichensatz-Header vor. Dabei wäre es doch schon durchaus wichtig zu wissen, ob ein vom Website-Besucher in einem Formular angegebenes „ä“ nun als ein oder als zwei Bytes übermittelt wird! In der Praxis wird ein Browser die Formulardaten im gleichen Zeichensatz kodieren wie der der Seite, die er abgerufen hatte, mit einem Fallback auf ISO-8859-1. Für den Alltag reicht das aus. Entwickler von HTTP-basierenden Schnittstellen, bei denen ein Client initiativ Formulardaten an den Webserver schickt, sind aber gut beraten, in der Schnittstellendefinition kurzerhand ausdrücklich festzulegen, was erwartet wird.

Nun haben wir also Formulardaten erhalten, und diese Formulardaten haben irgendeinen Zeichensatz, idealerweise UTF-8, wenn wir dem Browser schon das Formular als UTF-8 präsentiert haben. So weit, so gut.

Nun müssen die Daten in eine Datenbank, wobei ich hier als Beispiel einfach mal MySQL verwende. Und auch hier gibt es eine „Grundwahrheit“, die man sich am Besten gleich neben den Klebezettel von vorhin an den Kühlschrank heften sollte:

Bei MySQL sind Zeichensatzangaben für zwei Dinge wichtig:

  1. für die Information, in welchem Zeichensatz Daten gespeichert werden sollen;
  2. für die Information, in welchem Zeichensatz Daten übermittelt werden sollen.

(Ja, das ist schnöde vereinfacht, denn schließlich gibt es in MySQL ein client character set, ein connection character set, ein results character set, ein system character set, ein database character set und ein server character set. Wir wollen es aber für den Moment nicht zu kompliziert machen und uns auf die Dinge konzentrieren, bei denen es normalerweise Probleme gibt. Ganz pragmatisch, wissenschon.)

Über den ersten Punkt ist sicher fast jeder schon mal gestolpert, der mit einer halbwegs aktuellen phpMyAdmin-Version hantiert hat: Für jede Spalte, in die textliche Angaben hineinkommen (CHAR, VARCHAR, TEXT, …), muss ein Zeichensatz angegeben werden, wobei der Defaultwert von der Tabelle und der wiederum von der Datenbank und der wiederum vom Server geerbt wird. Und eines sei an dieser Stelle direkt gesagt: Man mag von phpMyAdmin halten, was man will, aber das mit den Zeichensätzen macht es richtig. Wenn Sie also durch die Daten blättern und über Umlaute stolpern, die kaputt sind: Suchen Sie den Fehler bei sich. phpMyAdmin hat immer recht. Wiederholen Sie das.

Bevor es weitergeht, wenden wir uns dem Herren weiter hinten im Publikum zu, der schon seit einer Minute mit dem Finger schnippst und behauptet, das könne nicht stimmen, denn seine Applikation würde prima funktionieren, aber phpMyAdmin würde die Umlaute falsch anzeigen. Nun: Lassen Sie sich nicht foppen. Suchen Sie den Fehler bei sich, und erinnern Sie sich an das Mantra: phpMyAdmin hat immer recht. Es wird reiner Zufall, um nicht zu sagen: schieres Glück sein, dass es trotzdem funktioniert. Typischerweise passiert das dann, wenn Sie UTF-8-kodierte Daten in einer als Latin1 markierten Spalte ablegen. Hier werden die Zeichen dann zweimal falsch verarbeitet: Wenn MySQL die Bytes aus dem Datensatz entsprechend dem Zeichensatz der Spalte interpretiert, und wenn es dann dieses fehlerhafte Ergebnis in den gewünschten Zeichensatz der Verbindung konvertiert, der vermutlich Latin1 und nicht UTF-8 ist und somit die erhaltenen Bytes weitestgehend unbehelligt lässt. Und zweimal falsch ergibt in diesem speziellen Zusammenhang ausnahmsweise sogar mal richtig: Sie geben faktisch eine Reihe von Bytes aus, die in Latin1 ein krummes „Büttelborn“ ergeben und schreiben dann für den Browser eiskalt drüber: So, das ist jetzt aber UTF-8. Und erst damit kommt das Zeichensatzproblem wie auf magische Weise in Ordnung, und der Browser rendert „Büttelborn“ aus den schnöden Bytes.

Wenn Sie dann aber Ihre selbstgeschriebene Applikation später mal auf einen anderen Server übertragen, oder wenn Sie MySQL updaten, oder wenn Sie PHP updaten, … und plötzlich die Umlaute nicht mehr funktionieren, dann wissen Sie, dass Sie etwas falsch gemacht haben, und zwar nicht mit dem Update, sondern mit Ihrem Code, schon vor langer Zeit. Sagen Sie nicht, ich hätte Sie nicht gewarnt.

Es wird somit Zeit für den nächsten Kühlschrankzettel:

SET NAMES utf8;

Damit stellen Sie ein, in welchem Zeichensatz Sie die Daten zwischen PHP und MySQL übermitteln. Das betrifft sowohl die Daten, die Sie in die Datenbank schreiben, als auch die, die Sie aus der Datenbank beziehen. Und jetzt der Clou: Die Information, in welchem Zeichensatz MySQL die Daten speichert, hat damit absolut nichts zu tun. Mit SET NAMES utf8 bekommen Sie immer UTF-8 raus, egal welchen Zeichensatz die Strings in der Tabelle faktisch haben. Und auch umgekehrt schicken Sie einfach Bytes in dem Zeichensatz rüber, den Sie bei SET NAMES angegeben haben, und MySQL kümmert sich darum, diese Folge von Bytes anhand des angegebenen Zeichensatzes als Zeichen zu interpretieren und dann diese Zeichen anhand des Zeichensatzes der Spaltendefinition in eine Folge von Bytes zu konvertieren, dies es dann fröhlich auf die Festplatte schreiben kann. Um so besser, wenn die beiden Zeichensätze identisch sind: Dann muss nämlich überhaupt nichts konvertiert werden.

Das Mantra für PHP-Entwickler muss also lauten:

Es spielt für Ihr Script überhaupt keine Rolle, welchen Zeichensatz die Spalten in der Datenbank haben.

Es spielt ausschließlich eine Rolle, in welchem Zeichensatz Sie mit MySQL kommunizieren.

(Na gut, erwischt. Das ist natürlich nicht die volle Wahrheit. Es spielt insofern doch eine Rolle, als dass es nicht jedes Zeichen in jedem Zeichensatz gibt. Übermitteln Sie also das Zeichen „✌“ UTF-8-kodiert an MySQL und verlangen vom Datenbankserver, dass er dieses Zeichen in eine als Latin1 formatierte Spalte schreibt, obwohl Latin1 das Zeichen gar nicht kennt, dann haben Sie schlechte Karten. Wunder kann MySQL nicht vollbringen. Insofern: Formatieren Sie Ihre Spalten lieber gleich als UTF-8.)

Gewöhnen Sie sich also einfach an, nach jedem mysql_connect(...) direkt ein mysql_query("SET NAMES utf8"); auszuführen. Wenn Sie zu den verantwortungsvollen Entwicklern gehören, die den Aufbau der Datenbankverbindung an einer zentralen Stelle untergebracht haben, sollte das ein Klacks sein.

Nun sind sie schon mal ziemlich weit: Sie kriegen die Daten aus Ihrer Datenbank als Bytes, die – wenn man UTF-8-Kodierung auf sie anwendet – hübsch korrekte Zeichen ergeben. Wenn Sie diese Zeichen mit echo ausgeben und dazu einen Content-type-Header mit „charset=utf-8“ (siehe oben), wird ein Browser die Zeichen richtig darstellen. Sie bekommen bei einem abgeschickten Formular Bytes rein, und wenn Sie diese Bytes an MySQL schicken, nachdem Sie ihm mit SET NAMES utf8; mitgeteilt haben, dass diese Bytes, als UTF-8 interpretiert, hübsche Zeichen ergeben, wird MySQL das auch verstehen und dann schließlich ganz ohne Ihr Zutun die Zeichenkette auf die Festplatte schreiben, in dem Zeichensatz, der in der Spaltendefinition steht.

Was noch fehlt, ist PHP selbst. Denken Sie an das obige Beispiel mit dem strlen("ä"), das 2 als – nun gar nicht mehr so – überraschendes Ergebnis liefert, weil ein „ä“ in UTF-8 eben zwei Bytes entspricht. Denken Sie an die unzähligen Vorkommen von strlen, strpos oder auch strtoupper: Letztere Funktion kann natürlich aus einem „u“ ein „U“ machen. Aber kann sie auch aus einem „ü“ ein „Ü“ machen? Wenn Sie bedenken, dass PHP einen String als eine Folge von Bytes ansieht, dann sollten Sie sich vor Augen halten, dass die zwei Bytes, die in der UTF-8-Kodierung ein „ü“ ergeben, im Latin1-Zeichensatz für zwei einzelne Zeichen stehen, nämlich für ein „Ó und ein „¼“. Diese Zeichen in Großbuchstaben umzuwandeln, dürfte schwierig sein. Das findet dann auch PHP:

$ cat test-strtoupper.php
<?php echo strtoupper("über"); ?>

$ php test-strtoupper.php
üBER

Ihnen wird schon ganz übel? Gut so, das ist der erste Schritt zur Heilung.

Hier kommt die mbstring-Erweiterung ins Spiel. Wenn Sie PHP nicht gerade selbst kompiliert haben (wo mbstring gerne vergessen wird), ist es typischerweise bei allen Distributionen mit einkompiliert. Schon mit phpMyAdmin werden Sie zeichensatzmäßig keine große Freude haben, wenn mbstring fehlt – so wenig, dass schon auf der Startseite deutlich gewarnt wird, dass Sie sich gar nicht einbilden müssen, irgendwie korrekt dargestellte Zeichen zu erwarten.

Mit der mbstring-Erweiterung bekommen Sie für nahezu alle stringverarbeitenden Funktionen Pendants mit dem Präfix mb_, der genau das gleiche tut, aber zeichensatzsensibel ist:

$ cat test-mb_strlen.php
<?php
echo strlen("ä") . "\n";
echo mb_strlen("ä", "utf8") . "\n";
?>

$ php test-mb_strlen.php
2
1

Ja, genau: Sie müssen den mb_-Funktionen den Zeichensatz mitteilen, in dem es den String aus Bytes interpretieren soll. Das sollte Sie inzwischen aber nicht mehr wundern. Sie können alternativ in der php.ini (oder mit ini_set()) auch mbstring.internal_encoding auf den gewünschten Zeichensatz setzen und sich die Angabe sparen.

Bleibt aber noch, überhaupt erstmal überall den mb_-Präfix einzustreuen.

Möglicherweise stöhnen Sie jetzt und lassen vor dem inneren Auge Tausende von Codezeilen vorbeiwandern, die anzupassen sind. Dafür hat sich das PHP-Team mbstring.func_overload ausgedacht. Das bedeutet nichts anderes, als dass die wichtigsten nicht multibytefähigen Stringfunktionen kurzerhand auf ihre multibytefähigen Pendants „umgebogen“ werden. Das kann allerdings nur in der php.ini, einer .htaccess-Datei oder in der httpd.conf gesetzt werden – nicht mit ini_set() im PHP-Script. Zu den nur verzeichnisspezifischen Einstellungen bemerkt die Dokumentation dann aber auch gleich:

It is not recommended to use the function overloading option in the per-directory context, because it’s not confirmed yet to be stable enough in a production environment and may lead to undefined behaviour.

Es bleibt also eigentlich nur der Einsatz in der php.ini – und genau dort ist er prädestiniert für „fix one, break another“: Vielleicht funktioniert Ihre Applikation danach problemlos; eine andere geht dafür kaputt. Sofern Sie den gesamten Server mit allen PHP-Scripts selbst kontrollieren, kann function overloading eine Option für Sie sein, unter Vorbehalt. Aber spätestens dann, wenn Sie Ihre Scripts z.B. auf eine andere Maschine portieren möchten oder gar auf einen Webspace, bei dem Sie gar keinen Zugriff auf die php.ini haben, stehen Sie dumm da und wünschen sich, doch lieber überall die Funktionen mit dem mb_-Präfix verwendet zu haben.

Bei PHP 6 wird dann übrigens alles anders. Dort wird es dann für Unicode-Strings und für Byte-Strings zwei verschiedene Typen geben, und die ganzen stringverarbeitenden Funktionen werden sich automatisch entsprechend des Typs des Strings korrekt verhalten. Soll heißen: Was in der guten alten Zeiten von Latin1 noch „einfach so“ funktionierte, wird mit PHP 6 dann vermutlich auch „einfach so“ funktionieren, mit schickem Unicode-Support.

Nur heute – heute ist es ein K(r)ampf. Nehmen Sie lieber Perl.

Update: Christian hat die Aufforderungen des Artikels erfreulich ernst genommen, bemerkt aber auch zerknirscht, dass seine Frau die Zettel wieder vom Kühlschrank weg haben möchte. Aber der Gedanke zählt!

Ist die Seite echt?

29. Juli 2010 von Jonas Pasche

Über den Newsletter unseres Domainregistrars, der unter anderem auch SSL-Zertifikate anbietet, wurde ich auf die Website www.ist-die-seite-echt.de gestoßen, einer werblichen Website für Extended-Validation-SSL-Zertifikate (im Folgenden kurz EV-Zertifikate genannt), die ich absichtlich nicht direkt verlinke, um ihr nicht noch mehr unverdiente Hits zu verschaffen. Wer nicht weiß, was EV-Zertifikate sind: Das sind die SSL-Zertifikate, die dafür sorgen, dass im Browser die Adressleiste grün wird, und von denen die wenigsten wissen, warum überhaupt. Doch dazu gleich mehr.

Die Werbe-Website soll den Blick dafür öffnen, Phishing-Sites von echten Websites unterscheiden zu können. Dazu werden 10 Mal je zwei Screenshots parallel gezeigt und man soll beurteilen, welche davon die Phishing-Site ist. Nur die ersten fünf sind ein Ratespiel: Bei den zweiten fünf geht es um genau die selben Sites, aber eine davon hat eine grüne Adressleiste – Marketing mit dem Holzhammer.

Im ersten Fall tragen beide Screenshots eine https://-URL unter der identischen Domain. Die zweite trägt einen lustigen Text, der mit „Wir haben einen kleinen Fehler in Ihrem Konto entdeckt. Sie müssen links auf den Anmeldelink klicken“ beginnt, und die Erläuterung verrät: „Auf einer echten Website werden Sie niemals zur Abgabe von Benutzernamen und Passwörtern gezwungen“. Äh, wie meinen? Natürlich zwingen mich auch echte Websites dazu, unter anderem nämlich das Onlinebanking meiner Bank. Wie sonst sollte ich mich auch autorisieren? Mich hätte hier in diesem Fall eher der Sprachduktus des Texts stutzig gemacht, aber wie gesagt: Beide Screenshots zeigen eine Seite unter korrekter URL mit korrektem SSL-Zertifikat – nur eben keinem EV. Fragwürdiges Beispiel.

Im zweiten Fall war die URL gefälscht („http://www.quickmoos.de@12.21.101.4“). Nicht mal SSL-verschlüsselt. Das war leicht.

Im dritten Fall habe ich nun wirklich lange suchen müssen: Beide Sites sahen auf den ersten Blick völlig identisch aus. Beide Sites mit korrekter Domain; beide Sites mit korrektem Zertifikat – nur eben keinem EV. Die Begründung überrascht: Man hätte den linken Screenshot als Phishing-Site identifizieren sollen, weil … dort Rechtschreibfehler auf der Seite sind. Das ist doch nicht euer ernst, VeriSign. Nachdem ich in der Vergangenheit nicht nur Rechtschreibfehler, sondern zum Beispiel auch überhaupt nicht existierende E-Mail-Adressen auf der EV-zertifizierten (!) Website einer Zertifizierungsstelle (!!) der VeriSign-Gruppe (!!!) fand und noch dazu dann mit deren Support zu tun hatte, der überhaupt nicht verstand, wo hier das Problem lag, soll das ein Erkennungsmerkmal für Phishing-Sites sein?!

Beispiel Nummer vier ist nicht SSL-verschlüsselt. Simpel.

Beispiel Nummer fünf ist SSL-verschlüsselt, unter der korrekten Domain, und die Sites zeigen keinen Unterschied. Überraschung: VeriSign tut kund, dass manchmal Phisher so gut darin seien, Seiten zu fälschen, dass man tatsächlich überhaupt keinen Unterschied wahrnehmen könnte, und der einzige Schutz dagegen sei … der Einsatz von EV-Zertifikaten. Whoa!

Nun kann man natürlich zu dem Schluss kommen: Wow, EV-Zertifikate sind das neue geschnitten Brot, und ohne ist eigentlich alles andere unsicher – nicht zuletzt Beispiel Nummer 5 legte ja auch durchaus nahe, dass VeriSign der Meinung ist, ein „normales“ SSL-Zertifikat würde einen offensichtlich nicht vor Phishing schützen.

Unabhängig davon, ob das nun stimmt oder nicht, so ist es doch vor allem eins: Ein Armutszeugnis. Denn VeriSign hört natürlich in keinster Weise damit auf, auch weiterhin genau die Nicht-EV-Zertifikate zu verkloppen, von denen es auf seiner werblichen Website sagt, dass die überhaupt nichts taugen, wenn es darum geht, eine Phishing-Website von einer echten zu unterscheiden. Und zwar zu einem Nettopreis von, festhalten, 349,00 € für ein „Secure Site“- und 895,00 € für ein „Secure Site Pro“-Zertifikat. Und zwar für ein Jahr. Dazu fällt mir nur eins ein: Schamlos. Ein Produkt taugt was, das andere nicht; für diejenigen, denen das, was etwas taugt, aber vielleicht zu teuer ist, bieten wir das, was nichts taugt, aber ruhig weiterhin an. Das hat zwar vielleicht sehr viel mit Betriebswirtschaft zu tun – mit Sicherheit und Vertrauen aber gewiss nichts.

Davon abgesehen stellt sich doch die Frage: Was haben wir denn die ganzen letzten Jahre überhaupt mit SSL gemacht, wenn erst EV der Heilsbringer schlechthin ist und vorher jeglicher Schutz ganz offensichtlich wirkungslos?

Die Wikipedia fasst die Ansichten verschiedenster Quellen prägnant zusammen:

Die Ausgabe von SSL-Zertifikaten ist zwar grundsätzlich an eine Überprüfung des Antragstellers gebunden, der Preisdruck unter den Anbietern hat aber zu einer teilweise laxen Vergabepraxis und zu vereinfachten Zertifikaten geführt, die nicht mehr als den Domain-Namen zertifizieren. […] Kritiker sehen in dem neuen Standard teils den Versuch der Zertifizierungsstellen, sich dem Preiskampf in der SSL-Zertifikat-Ausgabe durch die Einführung eines neuen Premium-Produktes zu entziehen, das dem Benutzer wenig zusätzliche Sicherheit bringt, und das auch mit anderen Mitteln zu erreichen wäre.

Aha. Es ist also die laxe Vergabepraxis der Zertifizierungsstellen gewesen, die die Argumentation nährt, dass heutzutage angeblich alle Nicht-EV-Zertifikate nur Augenwischerei wären. Nur dass VeriSign wie gesagt selbst die laut ihrer eigenen Werbekampagne nicht als sicher erkennbaren Zertifikate weiterhin für sehr teures Geld verkauft (und die EV-Zertifikate dann für extrem teures Geld), während es gleichzeitig Website-Besuchern beibringt, solchen Zertifikaten nicht zu vertrauen, sondern nur denen mit der grünen Adressleiste.

Dazu muss man ein bisschen wissen, wie das alles mit SSL funktioniert. Kurz gesagt: Jeder kann SSL-Zertifikate ausstellen, zum Beispiel mit OpenSSL. Die eigentliche Wertigkeit bekommt ein Zertifikat dadurch, dass es von einer Stelle signiert wird, die überprüft, ob derjenige, der via Zertifikat behauptet, eine bestimmte Person oder ein bestimmtes Unternehmen zu sein, auch derjenige ist. Das kann zum Beispiel anhand der Vorlage eines Personalausweises geprüft werden oder auch anhand eines Handelsregisterauszugs. In einem Client (also zum Beispiel in Webbrowsern, genauso aber auch in Mailclients, in Handys, oder auch ganz allgemein in Betriebssystemen) ist dann eine Liste von Zertifizierungsstellen hinterlegt, die als vertrauenswürdig gelten. Soll heißen: Die Herausgeber jener Clients haben die Entscheidung über die Vertrauenswürdigkeit für den Anwender vorab getroffen. Dafür geben sie sich gewisse Richtlinien; hier als Beispiel die Mozilla CA Certificate Policy. Inweit es gut und richtig ist, Anwendern diese Entscheidung abzunehmen, kann diskutiert werden, aber es dient ohne Frage durchaus der Sicherheit, dem Anwender eine Liste vorzuschlagen (die er selbst ja noch anpassen kann), als ihm die Akzeptanz bestimmter Zertifizierungsstellen grundsätzlich ohne jegliches „Vorschussvertrauen“ selbst zu überlassen, was – Hand aufs Herz – vermutlich dazu führen würde, dass die meisten Anwender einfach ungesehen auf den Ja-und-Amen-Button klicken würden, wenn eine neue, bisher unbekannte Zertifizierungsstelle ins Spiel käme. Insofern muss man realistisch bleiben und sagen: Auch wenn die Vorgabe einer Liste von vertrauenswürdigen Zertifizierungsstellen nicht optimal ist; ohne solche Listen wäre das Sicherheitsdebakel heute sicherlich noch wesentlich schlimmer (oder hätte möglicherweise zu ganz anderen, vielleicht auch besseren Lösungen geführt – aber das ist nur Spekulation).

Festzuhalten bleibt jedenfalls eins: Auch wenn man darüber streiten mag, dass vielleicht die Policies der Browserhersteller schlicht nicht streng genug waren oder schwarze Schafe nicht aggressiv genug entfernt worden sind: Letztlich haben es die Zertifizierungsstellen selbst verbockt. Da finden sich Fälle, wo die Validierung ausschließlich darin besteht, eine Mail unter einer der vorgegebenen Adressen unter der zu zertifizierenden Domain empfangen zu können, wobei die Liste der erlaubten Adressen so umfangreich ist, dass man sich etliche davon einfach bei Freemailern registrieren kann, um flugs ein SSL-Zertifikat zu bekommen, das auf die Domain des Freemailers ausgestellt ist. Aber warum die Mühe, gibt es doch auch „Zertifizierungsstellen“, die Zertifikate gleich ganz ohne irgendeine Art von Prüfung ausstellten – wie hier im prominent gewordenen Fall für mozilla.com. Zwei äußerst lesenswerte Beiträge zu genau dieser Thematik finden sich auch bei mitternachtshacking.de: Teil 1, der sehr griffig die verschiedenen Stufen von Validierung erläutert, und Teil 2, der eine gesunde Portion Paranoia ergänzt.

Zu all diesen Beispielen für Verfehlungen von Zertifizierungsstellen fällt mir nicht mehr viel ein. Außer vielleicht eins: Liebe Zertifizierungsstellen, ihr seid die Letzen, von denen ich mir etwas verkaufen lassen würde, was angeblich einem Mehr an Sicherheit dienen soll. Das, was ihr nun großspurig als Extended Validation verkauft, ist das, was ihr eigentlich schon immer bei allen SSL-Zertifikaten hättet tun sollen. Dass ihr da in der Vergangenheit geschlampt habt und das nun zum Anlass nehmt, den Kunden noch viel teurere Zertifikate zu verkaufen, ist wirklich eine bodenlose Frechheit.

Und noch eins verstehe ich nicht: Nämlich, wieso Browserhersteller diesen „Spaß“ auch noch mitmachen und mit einer besonderen Hervorhebung wie der grünen Adressleiste würdigen, statt einfach eiskalt alle Zertifizierungsstellen aus der Liste zu werfen, die keine vertrauenswürdige Validierung durchführen. Diese Zwei-Klassen-Validierung hat nämlich ein Problem: Normale Anwender verstehen sie sowieso nicht. Kaum ein Anwender kann mir erklären, was denn nun bei einem grünen Zertifikat so besonders geprüft worden sei, dass erklärt wäre, warum es so besonders aussieht. Es sieht einfach nur besser aus. Und der perfide Umkehrschluss ist nämlich folgender: Die Zertifizierungsstellen können jetzt lauthals tönen, dass das einfache Schloss-Symbol im Browser nicht mehr aussagekräftig sei; es müsse schon die grüne Adressleiste sein – und damit den Druck auf Webmaster zu erhöhen, sich dieses überteuerte Schlangenöl zuzulegen. Damit diejenigen, die neben all den technischen Sicherheitsproblemen von SSL die mit Abstand größte Sicherheitslücke im System darstellen, künftig noch mehr Geld verdienen.

Christopher ergänzte, dass es früher wohl üblich war, dass die Zertifizierungsstellen den Browserherstellern eine nicht unerhebliche Menge Geld dafür in den Rachen warfen, um auf die Liste der vertrauenswürdigen Zertifizierungsstellen zu kommen, was mit der Folgerung „Finanzielles Interesse!“ eine veritable Erklärung für die im vorherigen Absatz aufgeworfene Frage darstellt:

Back in the early days of SSL Netscape charged CAs rather large fees to have their root certificates included in Netscape products.

Allerdings ist das heute offensichtlich nicht der Fall; weder Apple noch Opera noch Microsoft nehmen Geld für die Aufnahme in die Liste, und communitybasierte Produkte wie Firefox und Thunderbird erwartungsgemäß auch nicht. Damit bleibt die Frage des vorherigen Absatzes weiterhin offen.

Ach ja: An die Werbe-Website schließt sich übrigens ein „Gewinnspiel“ namens „SSL EV Race“ ein. Zu gewinnen gibt’s ein iPad. Bei einem Gewinnspiel wird typischerweise ein Preis ausgelost. Das ist hier aber überhaupt nicht der Fall. Das iPad bekommt nämlich derjenige, der in der vorgegeben Zeit die meisten EV-Zertifikate verkloppt hat. Insofern ist die Angelegenheit dann doch wirklich eher ein Rennen als ein Gewinnspiel. Und an diesem werden wir definitiv nicht teilnehmen – wollen.

In der Plesk-Hölle: qmail-smtpd, SSL … und spamdyke

27. Juli 2010 von Jonas Pasche

Wenn man etwas weiter hinter die Kulissen schaut, findet man manchmal ganz erstaunliche Dinge. Heute soll es mal um verschlüsseltes SMTP mit dem qmail-Paket von Plesk gehen. Für Verschlüsselung gibt es, um das ganz kurz zu erläutern, zwei Verfahren: TLS und SSL. TLS bedeutet: Man verbindet sich mit dem unverschlüsselten Port (normalerweise 25), der Server annonciert, dass er STARTTLS beherrscht, der Client sendet jenes STARTTLS, und die Verbindung ist verschlüsselt. Alternativ benutzt man SSL: Hier stellt der Server einen separaten Port bereit (normalerweise 465), auf dem dann direkt eine verschlüsselte Verbindung entgegengenommen wird.

Der aufmerksame Leser wird bereits eins ahnen: SSL über einen separaten Port kann man in der Art eines Wrappers um beliebige Dienste „drumherumstricken“, z.B. mit stunnel oder ucspi-ssl, ohne dass man den Dienst selbst anpassen muss. Dass ein Dienst hingegen ein Schlüsselwort wie STARTTLS versteht und daraufhin die Verschlüsselung einschaltet – da dürfte auf der Hand liegen, dass das nur mit einem Patch geht. Für qmail gibt es dafür seit langem einen etablierten Patch von Frederik Vermeulen, der (unter anderem) qmail-smtpd beibringt, auf STARTTLS zu reagieren. Im Vorbeigehen unterstützt er auch noch die traditionelle Variante mit dem separaten Port – ganz ohne Wrapper: Es reicht aus, die Umgebungsvariable SMTPS zu setzen, bevor qmail-smtpd aufgerufen wird, und das war’s.

Nun schauen wir uns mal an, wie das bei Plesk aussieht. Ich habe die Konfigurationsdateien jeweils auf das Wesentliche gekürzt, also bitte nicht wundern. Erstmal für den Standard-SMTP-Port 25:

# cat /etc/xinetd.d/smtp_psa
service smtp
{
  [...]
  server      = /var/qmail/bin/tcp-env
  server_args = /var/qmail/bin/relaylock /var/qmail/bin/qmail-smtpd [...]
}

… und schließlich für den Standard-SMTPS-Port 465:

# cat /etc/xinetd.d/smtps_psa
service smtps
{
  [...]
  server      = /var/qmail/bin/tcp-env
  server_args = /var/qmail/bin/relaylock /var/qmail/bin/qmail-smtpd [...]
}

Der xinetd erkennt an der service-Bezeichnung den numerischen Port, in dem er ihn über /etc/services auflöst. So weit, so gut.

Nun stellt sich aber doch eine Frage: Woher weiß qmail-smtpd im zweiten Fall denn, dass es bitte SSL sprechen möge und nicht Plaintext? Plesk arbeitet hier genauso mit dem etablierten TLS-Patch, der eigentlich verlangt, dass die Umgebungsvariable SMTPS gesetzt sein muss. Die wird hier aber doch gar nicht gesetzt..?!

Des Rätsels Lösung ist, dass Plesk hier ganz gewaltig trickst. Nicht etwa, dass es ein Leichtes gewesen wäre, in der /etc/xinet.d/smtpd_psa kurzerhand env = SMTPS=1 einzutragen: Nein, das wäre doch viel zu nachvollziehbar gewesen.

Stattdessen patcht Plesk. Und patcht. Und patcht. Details dazu finden sich in der Plesk-Knowledgebase. Wir schnappen uns das erste der Patche-Archive und schauen mal rein. Darin findet sich ein Haufen Patches mit so klangvollen Namen wie patch-pb, patch-pe oder auch patch-ps. Oder, und darum geht’s: patch-pu-tls. Darin steckt so eine Art Variante des etablierten qmail-TLS-Patches, „natürlich“ bereinigt um jegliche Kommentare oder Dokumentation. Und ganz am Ende, da findet sich plötzlich ein Teil, den es im Original nicht gibt, nämlich diesen hier:

--- ./tcp-env.c.orig    Thu Feb 20 11:33:43 2003
+++ ./tcp-env.c Thu Feb 20 11:37:05 2003
@@ -70,6 +70,9 @@
    temp[fmt_ulong(temp,localport)] = 0;
    if (!env_put2("TCPLOCALPORT",temp)) die();
 
+   if (localport == 465)
+      if (!env_put2("SMTPS","true")) die();
+
    byte_copy(&iplocal,4,&salocal.sin_addr);
    temp[ip_fmt(temp,&iplocal)] = 0;
    if (!env_put2("TCPLOCALIP",temp)) die();

Dass qmail-smtpd unter Plesk also auf „magische“ Weise plötzlich SSL spricht, liegt daran, dass das vorgeschaltete tcp-env die Variable SMTPS kurzerhand setzt, sobald es feststellt: Oh, ich laufe unter Port 465! Unnötig zu sagen, dass Plesk die man page von tcp-env nicht angerührt hat – die verrät von diesem Verhalten nichts. Da muss man sich dann auch nicht wundern, wenn in Foren Fragen wie diese auftauchen – und unbeantwortet bleiben (Hand aufs Herz, die Frage ist von 2007, und in dem Forum müsste ich mich auch erstmal registrieren, um antworten zu können):

When i copy smtps_psa to another port / service, SSL does not work. Is SSL hard-coded somewhere in Plesk’s qmail ? For instance : i would like to run smtps on port 665

Aber nun gut, lassen wir das für einen Moment so stehen und wenden uns dem Thema zu, das mich erst auf diese ganze Thematik gebracht hat, und das für mich vor allem deshalb so ein „Aufreger“ ist, weil es dazu Howtos über Howtos gibt und praktisch alle in puncto SSL falsch sind – wahrscheinlich schreiben alle voneinander ab. Hier findet sich in zig Howtos der Hinweis, dass Spamdyke auf Plesk-Systemen in /etc/xinetd.d/smtp_psa und /etc/xinetd.d/smtps_psa eingetragen werden muss. Das ist so auch nicht ganz falsch – allerdings geben fast alle Howtos an, dass man dort das Gleiche eintragen soll; viele liefern sogar konkrete Konfigurationsbeispiele, die aber vor Veröffentlichung definitiv nie getestet worden sein können. Dann hätte nämlich auffallen müssen, dass sie in der Realität überhaupt nicht funktionieren – nicht mit SSL.

Und so finden wir falsche Howtos im offiziellen Plesk-Forum, im inoffiziellen Plesk-Forum, im Serversupport-Forum, nochmal dort, im Howto des RootForums, in der Anleitung von huschi.net, bei Blue Orix oder bei Haggybear, um nur eine kleine Auswahl an Quellen zu nennen. Hier und da (aber Ausnahmen bestätigen ja bekanntlich die Regel) findet sich auch jemand, der es richtig macht, zum Beispiel The BOFH. Andere kriegen es auch hin, so wie Alexander Koch, wenngleich auf ganz andere Weise (nämlich, in dem gar nicht von der SMTPS-Funktionalität Gebrauch gemacht wird, sondern in dem stunnel als Wrapper eingesetzt wird – was aber fraglos auch eine legitime Lösung ist, nur eben eine aufwendige).

Im Endeffekt laufen alle Howtos darauf hinaus, dass man spamdyke vor den Aufruf von qmail-smtpd setzen soll. Aus

server_args = [...] /var/qmail/bin/qmail-smtpd [...]

soll also werden:

server_args = [...] /usr/local/bin/spamdyke -f /etc/spamdyke.conf \
              /var/qmail/bin/qmail-smtpd [...]

Und genau da liegt der Knackpunkt. Die TCP-Verbindung kommt jetzt ja nun nicht mehr bei qmail-smtpd an, sondern bei spamdyke. Und so schön und gut es auch ist, dass qmail-smtpd eine Umgebungsvariable namens SMTPS interpretiert, und so tricky und gruselig es ist auch ist, dass Plesks tcp-env diese Umgebungsvariable heimlich setzt: spamdyke hat deswegen noch lange keine Ahnung davon – und spricht deshalb auch auf Port 465 schlicht und einfach Plaintext. Warum um alles in der Welt sollte es auch etwas anderes tun?

Praktischerweise kann man spamdyke sagen, dass es SSL sprechen soll. Nämlich, in dem man tls-level=smtps setzt anstelle des Defaults tls-level=smtp. Nur tut das keins der oben genannten Howtos! Es kann also überhaupt nicht funktionieren.

Die Abhilfe ist dann letztlich trivial. Entweder man legt sich eine zweite Konfigurationsdatei für spamdyke an, zum Beispiel eine spamdyke-smtps.conf, die eine Kopie des Originals ist, aber tls-level=smtps beinhaltet. In der xinetd-Konfiguration sieht das dann so aus:

# cat /etc/xinetd.d/smtps_psa
service smtps
{
  [...]
  server      = /var/qmail/bin/tcp-env
  server_args = /var/qmail/bin/relaylock \
                /usr/local/bin/spamdyke -f /etc/spamdyke-smtps.conf \
                /var/qmail/bin/qmail-smtpd [...]
}

Oder man hat verständlicherweise keine Lust, eine zweite Konfigurationsdatei zu pflegen, und setzt die Angabe direkt als Parameter mit in den Programmaufruf ein:

# cat /etc/xinetd.d/smtps_psa
service smtps
{
  [...]
  server      = /var/qmail/bin/tcp-env
  server_args = /var/qmail/bin/relaylock \
                /usr/local/bin/spamdyke -f /etc/spamdyke.conf --tls-level=smtps \
                /var/qmail/bin/qmail-smtpd [...]
}

So, und damit wir jetzt zum Schluss alle nochmal herzhaft lachen können:

Alle, wir alle inklusive mir, hätten eigentlich nur die ganz offizielle INSTALL.txt von spamdyke lesen müssen. Dort lesen wir schnöde vier Zeilen:

   Plesk users can also use spamdyke for their SMTPS connections by adding it to
   the /etc/xinetd.d/smtps_psa file.  spamdyke's configuration in that file will
   need to include the options "tls-level" (set to "smtps") and
   "tls-certificate-file".

Das wär’s auch schon gewesen. Aber andererseits: Ansonsten wäre ich nie darauf gekommen, wie Plesk das mit dem SSL eigentlich macht. Insofern hat sich’s doch gelohnt …

Verschlüsseltes Logging übers Netzwerk mit syslog-ng

16. Juli 2010 von Christopher Hirschmann

Vor kurzem habe ich einige unserer Server so konfiguriert, daß sie einen Teil ihrer Log-Nachrichten nicht nur lokal in Dateien schreiben, sondern auch über’s Netzwerk an einen anderen Server schicken. Das hat diverse Vorteile, von denen die beiden größten wohl die Chance sind, daß ein abstürzendes System vielleicht noch letzte Log-Nachrichten über’s Netzwerk loswird, auch wenn es sie nicht mehr auf die Platte geschrieben bekommt (was bei der Fehlersuche helfen kann), sowie der potentielle Nutzen bei der Nachverfolgung von Angriffen, denn jemand der auf einem Server einbricht mag zwar dessen lokale Log-Dateien manipulieren können, um die bereits über’s Netzwerk geschickten Nachrichten (die Hinweise auf den Einbruch enthalten können) zu manipulieren, müßte er auch auf dem Server einbrechen, der die Nachrichten entgegennimmt.

Um hier den Hintergrund zu erklären, muß ich kurz ausholen: Wir haben uns vor einer Weile zwei dicke Storage-Systeme zugelegt. Das hat zum einen den Vorteil, daß ein großes, dediziertes RAID-System weitaus effizienter und billiger ist als in jedem einzelnen Server mindestens zwei Festplatten zu verbauen, zum anderen verringert es den Wartungsaufwand erheblich, da ein großer RAID natürlich Spares hat, so daß man nicht gleich bei jeder ausgefallenen Platte loshetzen und eine neue einbauen muß, und insgesamt die Menge der Platten einfach eine andere ist.

Seit wir diese Storage-Lösung haben, booten wir einige Server völlig ohne Festplatten (es sind nicht mal mehr welche eingebaut) über das Netzwerk per iSCSI. Wobei dieses Netzwerk natürlich nicht das wilde , gefährliche Internet ist, sondern ein schnelles, dediziertes Storage-Netzwerk, in dem nichts anderes stattfindet.

Ein so bootender Server ist nun aber davon abhängig, daß statt seiner Festplatten sein iSCSI-Subsystem immer funktioniert. Geht hier etwas schief, dann hat der Server ein ernstes Problem. Und hier ergibt sich eine Zwickmühle: Probleme mit einer Festplatte kann ich gut diagnostizieren, weil das Hardware ist, Probleme mit Software kann ich gut diagnostizieren, weil es Logs gibt. Aber was mache ich, wenn ein Server bei Problemen mit iSCSI (= Software) keine Logs mehr schreiben kann?

Für genau solche Fälle kann es nützlich sein, Log-Nachrichten über das Netzwerk zu schicken. (Wenn jemand ein Kabel zieht oder die Netzwerkschnittstelle oder ein Switch ausfällt, dann stürzt ein Server der von iSCSI abhängig ist natürlich auch ab und Log-Nachrichten können dann auch nicht mehr über’s Netzwerk gehen, aber in solchen Fällen ist der Fehler relativ leicht zu diagnostizieren, da brauche ich keine Log-Nachrichten für.)

Bei der Einrichtung bin ich am Anfang noch sehr minimalistisch vorgegangen und hab fast ausschließlich die Bordmittel von CentOS benutzt. Später wurde das Setup immer komplexer. Die einzelnen Schritte will ich im Folgenden erläutern:

Bei CentOS ist syslogd vorinstalliert und den kann man sehr leicht dazu bringen, Log-Nachrichten an einen anderen Host zu schicken oder Log-Nachrichten von anderen Hosts anzunehmen (oder sogar beides zu tun). Die zuständige Config heißt /etc/syslog.conf, dort kann man sich z.B. die Zeile schnappen mit der konfiguriert wird was in die Log-Datei /var/log/messages geschrieben wird und genau die gleichen Nachrichten nochmal über das Netzwerk an einen anderen Host schicken lassen:


# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
*.info;mail.none;authpriv.none;cron.none /var/log/messages
*.info;mail.none;authpriv.none;cron.none @centrallog.example.com

Um am anderen Ende mit syslogd Nachrichten aus dem Netzwerk entgegenzunehmen, muß man die Datei /etc/sysconfig/syslog anpassen:

# Options to syslogd
# -m 0 disables 'MARK' messages.
# -r enables logging from remote machines
# -x disables DNS lookups on messages recieved with -r
# See syslogd(8) for more details
SYSLOGD_OPTIONS="-m 0 -r -x"

Ob man die DNS-Auflösung an oder abschalten will, ist jedem selbst überlassen, für Testzwecke hab ich sie erstmal ausgeschaltet.

Mit syslogd zu arbeiten hat aber Nachteile:

  • Die Kommunikation erfolgt nur per UDP.
  • Es gibt keine Möglichkeit zur Authentifikation der Kommunikationspartner.
  • Es gibt keine Möglichkeit der Verschlüsselung.
  • Es besteht das Risiko einer DoS-Attacke gegen Hosts, die Log-Nachrichten aus dem Netzwerk annehmen, da diese Nachrichten von jedem Ursprung annehmen. (Hier kann man allenfalls mit einem Paketfilter bzw. einer Firewall wie iptables etwas Abhilfe schaffen.)
  • syslogd hat nur sehr eingeschränkte Möglichkeiten die erhaltenen Log-Nachrichten zu sortieren.

Bis auf die letzten beiden Punkte ist das aber nicht eine Schwäche von syslogd. Der Standard nach dem Remote Syslog — also das Loggen von Nachrichten über das Netzwerk — erfolgt gibt einfach nicht mehr her.

Ein paar Dienste, die Remote Syslog beherrschen, gehen deshalb mittlerweile über den Standard hinaus und bieten weitere Möglichkeiten, vor allem Kommunikation per TCP und Verschlüsselung mit SSL/TLS — hieran ist positiv anzumerken, daß die Erweiterung sich wiederum auf Standardtechnologien stützen und somit also nicht pro Dienst ein neues Sonderprotokoll eingeführt wird, das dann nur der jeweilige Dienst versteht, sondern von vornherein eine Lösung gewählt wurde, die Interoperabilität verspricht.

Zwei solche Alternativen zu syslogd sind syslog-ng und rsyslog. Ich habe mich für syslog-ng entschieden, da ich das bereits von früher her kenne.

syslog-ng ist nicht Teil der offiziellen CentOS-Paketquellen, steht aber in EPEL zur Verfügung. Der Nachteil der EPEL-Variante ist, daß es sich dabei um den 2.1er Branch von syslog-ng handelt, der noch keine Verschlüsselung beherrscht. Man kann dies z.B. mit stunnel umgehen und ich hab dies auch zunächst getan, mich dann aber später dafür entschieden, doch lieber den 3er Branch von syslog-ng zu nehmen, der TLS nativ beherrscht.

Allen CentOS-Nutzern sei an dieser Stelle empfohlen sich die Config aus dem EPEL-Paket zu extrahieren. Diese ist so geschrieben, daß syslog-ng sich nahezu genauso verhält wie syslogd es unter CentOS tut. Der Vorteil daran ist, daß man sich erstmal nicht umgewöhnen muß was die Position von Logfiles angeht und die Kompatibiltät mit logwatch und logrotate erhalten bleibt.

Eines der wichtigen Features von syslog-ng ist der Schalter „-s“ mit dem man ihn veranlassen kann seine Config zu prüfen und sonst nichts weiter zu tun. Auf diese Art und Weise findet man leicht alle möglichen Config-Fehler. Da das Format der Config von syslog-ng vor Klammern und Semikola strotz, ist das wirklich sehr nützlich. (Mein persönlicher Wunsch für einen zukünftigen 4er Branch: Bitte stellt doch die Config endlich mal auf sowas schönes und übersichtliches wie YAML um. Dieses Elend aus Klammern, Anführungszeichen und Semikola muß doch nun wirklich nicht sein.)

Wenn man nun aber die Config aus dem 2.1er Paket aus EPEL dem 3er syslog-ng vorwirft, wird er sich beschweren. Angenehmerweise sagt er aber nicht einfach, daß er die Config nicht lesen kann, sondern er gibt aus, daß er dies für eine 2.1er Config hält und listet dann detailiert auf, was ihm daran nicht schmeckt und macht sogar gute Änderungsvorschläge. Wer diese Hinweise kurz in der offiziellen Dokumentation nachschlägt, sollte sehr schnell zu einer Config kommen, die auch der 3er syslog-ng akzeptiert. (Ich hab übrigens mit der offiziellen Dokumentation im Web insgesamt bessere Erfahrungen gemacht als mit den Manpages, die an einigen Stellen einfach nicht präzise genug sind und zu arm an anschaulichen Beispielen daherkommen.)

An dieser Stelle noch ein Tipp für den allgemeinen Teil der Config: syslog-ng kann auf verschiedene Art und Weise für gutes Verhalten und gute Performance optimiert werden (siehe auch Best practices in der offiziellen Dokumentation). Zwei Dinge die sich sehr lohnen ist die Normalisierung der Zeitstempel auf ein Format, daß Zeitzonen-Informationen enthält, und die gepufferte Verarbeitung von Log-Nachrichten. Remote Syslog verwendet normalerweise einen Zeitstempel, der keine Zeitzoneninformation enthält. Zeitzonen geraten aber hin und wieder mal durcheinander und wenn man die Zeitstempel mit syslog-ng so umstellt, daß sie Zeitzonendaten enthalten, fallen solche Fehler wenn sie denn mal auftreten leichter auf. Dazu trägt man im allgemeinen Teil der Config (also unter options) einfach ts_format(iso); ein. Bei der gepufferten Verarbeitung geht es darum, nicht jede Log-Nachricht einzeln durch den internen Filter von syslog-ng zu jagen, sondern immer ein bißchen zu warten und es dann im Verbund mit weiteren Nachrichten zu tun, was insgesamt die CPU schont. Dazu trägt man wieder im allgemeinen Teil der Config time_sleep(20); ein.

Nun aber zum eigentlichen Logging:

Um mit syslog-ng nachrichten per UDP zu empfangen, kann man entweder UDP als allgemeine Nachrichtenquelle eintragen, der dafür nötige Eintrag ist auskommentiert schon in der Config aus dem EPEL-Paket vorgegeben:


source s_sys
{
    file ("/proc/kmsg" program_override("kernel"));
    unix-stream ("/dev/log");
    internal();
#  udp(ip(0.0.0.0) port(514));
};

Oder man legt sich eine Extra-Quelle an, was im weiteren die Konfiguration übersichtlicher machen kann:


source s_udp
{
    udp(ip(0.0.0.0) port(514));
};

Bei TCP ist es im Grunde genauso:


source s_tcp
{
    tcp(ip(0.0.0.0) port(514));
};

Man kann hierbei auch einschränken, an welcher IP syslog-ng lauschen soll, wenn man nicht über alle IP-Interfaces Nachrichten annehmen möchte:


source s_tcp
{
    tcp(ip(192.168.0.254) port(514));
};

Will man nun noch zusätzlich Verschlüsselung nutzen, dann muß man TCP verwenden:


source s_tcp_tls
{
    tcp
    (
        ip(192.168.0.254)
        port(514)
        tls
            (
                ca_dir("/opt/syslog-ng/etc/ca.d")
                cert_file("/opt/syslog-ng/etc/localhost-cert.pem")
                crl_dir("/opt/syslog-ng/etc/crl.d")
                key_file("/opt/syslog-ng/etc/localhost-key.pem")
            )
    );
};

Wie hier das Zertifikat und der dazugehörende Key angegeben wird ist ja deutlich sichtbar. Das ca_dir und das crl_dir bedürfen allerdings noch weiterer Erläuterung: syslog-ng akzeptiert an diesen Stellen nur Dateien im PEM-Format die nach einem ganz bestimmten Muster benannt sind, alle anderen irgnoriert es. Für Certificate Authorities müssen die Dateien nach dem Subject Hash des jeweiligen CA Zertifikats gefolgt von einem „.0“ benannt sein. Ich lege mir die Datei dazu mit einem normalen Dateinnamen in den Ordner und linke dann darauf:

ln -s my_cacert.pem `openssl x509 -noout -subject_hash -in my_cacert.pem`.0

Bei CRL ist es fast genauso, nur muß der Name hier auf „.r0“ enden.

CRLs sind übrigens optional, man kann auch gerne darauf verzichten. Ich habe nicht darauf verzichtet, in diesem Zusammenhang aber auch ein paar interessante Macken entdeckt. So gibt syslog-ng z.B. eine Fehlermeldung („CRL directory is set but nor CRLs found“) aus, wenn man nicht für jede einzelne CA, die man im ca_dir aufgeführt hat eine CRL hinterlegt — die vorhandenen CRLs werden aber berücksichtigt, auch wenn die Fehlermeldung etwas anderes vermuten läßt.

Nun muß noch dafür gesorgt werden, daß die empfangenen Nachrichten schön übersichtlich in extra Dateien sortiert werden. Dazu lege ich mir für jeden Host von dem ich Nachrichten annehmen will ein Ziel an, das in diesem Fall eine Datei ist:


destination d_examplelogger { file("/var/log/rsyslog/examplelogger.log"); };

Und einen Filter, mit dem ich später die Nachrichten dieses Host aus der Gesamtheit von Nachrichten herausfische:


filter f_examplelogger { host("examplelogger") or host("192.168.0.128"); };

Und schließlich füge ich alle Teile zusammen, damit syslog-ng weiß was er zu tun hat:


log { source(s_tcp_tls); filter(f_examplelogger); destination(d_examplelogger); flags(final); };

Mit flags(final); sage ich an, daß diese Nachrichten damit abgearbeitet sind und für andere Logs nicht mehr betrachtet werden müssen. Alternativ wäre es auch möglich Nachrichten über mehrere Dateien zu verteilen und sogar mehrmals in verschiedenen Dateien vorkommen zu lassen.

Auf dem Host der die Log-Nachrichten schicken soll sieht die Konfiguration zunächst ähnlich aus. Statt einer Nachrichtenquelle, definiert man hier aber ein Nachrichtenziel:


destination d_centrallogger_tls
{
    tcp
    (
        "centrallogger.example.com"
        port(514)
        tls
        (
            ca_dir("/opt/syslog-ng/etc/ca.d")
            cert_file("/opt/syslog-ng/etc/localhost-cert.pem")
            crl_dir("/opt/syslog-ng/etc/crl.d")
            key_file("/opt/syslog-ng/etc/localhost-key.pem")
        )
    );
};

Und dann suche ich mir unter den vorhandenen Log-Befehlen weiter unten diejenigen raus, deren Nachrichten ich zusätzlich über’s Netzwerk geschickt haben möchte, z.B.:


log { source(s_sys); filter(f_default); destination(d_mesg); destination(d_centrallogger_tls); };

Hier habe ich dem schon eingetragenen Ziel destination(d_mesg); einfach ein weiteres Ziel destination(d_centrallogger_tls); hinzugefügt.

Und das war’s.

Ein paar potentielle Schwächen bleiben natürlich noch. Auch syslog-ng ist nicht vor DoS-Attacken sicher. Da er er aber nicht alle Nachrichten auch loggt, sondern nur diejenigen für die ein Log-Statement vorhanden ist, sollte man durch gute Filter-Regeln zumindest ein Vollschreiben der Festplatte vermeiden können. TCP-Syn-Flooding bleibt allerdings ein Problem. Wenn man Remote Syslog aber nicht in den wilden Weiten des Internets sondern in einem internen Netz einsetzt, hat man hier gute Chancen den Angriffsvektor zu verkleinern und dem Rest mit gutem Netzwerk-Monitoring und geeigneten Paketfiltern zu begegnen.

Webseiten komprimiert ausliefern

16. Juli 2010 von Christopher Hirschmann

Diverse Browser und sonstige HTTP-Clients sind in der Lage Datentransfers auch gzip-komprimiert entgegen zu nehmen, was besonders bei statischen Inhalten die Übertragung spürbar beschleunigen kann. Der Apache bringt dafür ein Modul mit, daß Inhalte on the fly komprimiert, wenn sie angefragt werden: mod_deflate (für die Apache 1.3er Serie übernimmt mod_gzip weitgehend dieselbe Aufgabe).

In der vorinstallierten httpd.conf unter CentOS steht dazu:


# AddEncoding allows you to have certain browsers uncompress
# information on the fly. Note: Not all browsers support this.
[…]
#
#AddEncoding x-compress .Z
#AddEncoding x-gzip .gz .tgz

# If the AddEncoding directives above are commented-out, then you
# probably should define those extensions to indicate media types:
#
AddType application/x-compress .Z
AddType application/vnd.adobe.air-application-installer-package+zip .air
AddType application/x-gzip .gz .tgz

Diese Erklärung ist ziemlich unintuitiv und entspricht dem Muster „von hinten durch die Brust ins Auge“. Was hier eigentlich gesagt wird ist, daß man Inhalte durchaus komprimiert ausliefern kann (wobei angedeutet wird, daß man sich eine Browserweiche oder etwas ähnliches bauen sollte, da es einige Browser gibt, die das immer noch nicht beherrschen), und daß man eigentlich nur zwei Zeilen auskommentieren bräuchte um dies zu tun. Darüber hinaus wird dann im nächsten Block erklärt, daß man die folgenden Zeilen nur stehen lassen sollte, wenn der Block darüber auskommentiert ist. (Ganz ehrlich: Wenn ich das lese, dann sehe ich schon den vorprogrammierten Konfigurations-Fehler der hier lauert. Wer schreibt solche Default-Configs?)

Was will uns der Autor also sagen: Wenn man die oberen beiden Direktiven aktiviert, dann sollte man die unteren drei auskommentieren und umgekehrt. Der Grund ist eigentlich naheliegend: Wenn der Apache einem Client mitteilen will, daß er ihm jetzt komprimierte Inhalte (die der Client aber darstellen soll/kann) schickt, dann ist der korrekte Weg dies zu tun, ihm ein entsprechendes Encoding zu melden. Wohingegen es falsch wäre, dem Client mitzuteilen diese Inhalte wären z.B. vom MIME-Type application/x-gzip, denn dann würde der Client annehmen er bekommt eine komprimierte Datei die er abspeichern und nicht darstellen soll.

Soweit so gut, aber wie schon erwähnt gibt es leider noch Browser die komprimierte Inhalte nicht vertragen. Außerdem wäre es durchaus nicht bei jedem Inhalt sinnvoll, diesen zu komprimieren (gzip kann z.B. bei schon komprimierten Bildern im PNG– oder JPEG-Format wenig ausrichten). Vielleicht ist dem einen oder anderen auch nicht ganz wohl bei dem Gedanken, den Apachen pauschal für alles was er so ausliefert Kompression anbieten zu lassen. Hier ist letztlich auch die Frage, ob die Nutzer das letztlich auch berücksichtigen, z.B. wenn man Webseiten hostet – in solch einem Fall müßte halt jeder Hosting-Kunde darauf achten, daß ihm die Kompression nirgendwo dazwischen funkt. Vor allem aber möchte man nicht alle möglichen Inhalte on the fly komprimieren lassen, weil dann die Last auf dem Webserver eventuell durch die Decke geht. In der Regel kennt man ja auch die Inhalte die sein Webserver ausliefert so halbwegs und kann selbst abschätzen, wo sich Kompression überhaupt lohnt, etwa bei Texten. Und aus Sicht eines Kompressionsalgorithmus sind HTML-, CSS– und JavaScript-Dateien letztlich nichts anders als unkomprimierter Text.

Es wäre doch ideal, wenn man solche Inhalte vorher (und nur einmalig) komprimiert und so bereitstellt, daß der Webserver einem Client der es unterstützt die komprimierte Version liefern kann. An sich ist auch das kein Problem, man muß aber ein paar kleine Klimmzüge machen:

Zunächst sollte man also die Finger von den oben zitierten Teilen der httpd.conf lassen, denn wie wir ja festgestellt haben, hätten diese alles andere als den gewünschten Effekt. Statt dessen muß man sich zunächst daran machen die entsprechenden Inhalte zu komprimieren. Hierbei sollte man darauf achten, daß die komprimierten Dateien nachher dem richtigen User und der richtigen Gruppe gehören und die richtigen Zugriffsrechte haben. Ich gehe darauf im weiteren nicht näher ein, sondern gehe einfach davon aus, daß diese Befehle mit einem Useraccount ausgeführt werden, bei dem die Rechte korrekt gesetzt werden.

Man wechselt also in den Ordner mit seinen Webinhalten und läßt z.B. alle CSS– und JavaScript-Dateien komprimieren (statische HTML-Seiten lasse ich hier mal außen vor). Dabei hat man das kleine Problem, daß gzip im Gegensatz bzip die Option –keep nicht versteht und also nicht in der Lage ist eine Datei zu komprimieren und das Original liegen zu lassen (anders ausgedrückt: wenn man mit gzip eine Datei komprimiert, ist die Originaldatei danach weg und es liegt nur noch die komprimierte Variante vor). Man muß sich daher eines Tricks bedienen, nämlich die Originaldatei an STDIN von gzip schicken und die komprimierte Variante über STDOUT entgegennehmen und in eine Datei umleiten. Die folgenden Befehle suchen alle JavaScript- und CSS Dateien in allen Unterordnern und legen jeweils eine komprimierte Variante daneben.


for i in `find . -name *.js`; do gzip --best --rsyncable --stdout "${i}" > "${i}".gz; done
for i in `find . -name *.css`; do gzip --best --rsyncable --stdout "${i}" > "${i}".gz; done

Hierbei habe ich für gzip zusätzlich die Optionen –best und –rsyncable gewählt. Die erste sorgt für maximale Kompression (da wir nur einmal komprimieren müssen, darf es ruhig etwas länger dauern), die zweite sorgt dafür, daß die komprimierten Daten rsync-freundlich komprimiert werden. (Da wir unsere Backups mit rsync machen, kann das ja nur von Vorteil sein.)

Nachdem das nun erledigt ist, bieten sich nun verschiedene Möglichkeiten an. Man könnte z.B. den MultiView-Mechanismus vom Apachen ausnutzen, und die komprimierte Variante quasi als View bereitstellen. Dieser Weg ist aber etwas kompliziert und birgt viele Gelegenheiten sich mehr Probleme zu machen als nötig. Viel einfacher (und daher in meinen Augen eleganter) ist es daher, wenn man mit .htaccess-Dateien arbeitet. So behält der einzelne Nutzer jeweils die Kontrolle über das Geschehen, man kann die Komprimierung auf die Ordner beschränken in denen es einem sinnvoll erscheint und Änderungen erfordern keinen Neustart des Apachen.

So eine .htaccess kann z.B. so aussehen:


RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} .*gzip.*
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule (.*)\.(js|css)$ $1.$2.gz [L]

<FilesMatch .*\.js\.gz>
AddEncoding x-gzip .gz
AddType application/x-javascript .gz
</FilesMatch>

<FilesMatch .*\.css\.gz>
AddEncoding x-gzip .gz
AddType text/css .gz
</FilesMatch>

Hiermit wird die RewriteEngine aktiviert. Als Bedingungen für einen Rewrite definiere ich, daß der Client die Unterstützung von gzip signalisiert haben muß und auch eine mit gzip komprimierte Variante der Datei vorliegen muß. Sollte also mal keine komprimierte Variante vorliegen (etwa weil später eine CSS-Datei dazu kommt und jemand vergißt die komprimierte Variante zu erstellen), dann nimmt Apache einfach die unkomprimierte Variante. Treffen beide Bedingungen zu, dann werden Requests für Dateien die auf .js und .css Enden so umgeschrieben, daß sie bei der komprimierten Variante heraus kommen. Die nächsten zwei Blöcke legen dann darüber hinaus fest, daß diese Dateien nicht mit dem MIME-Type x-gzip übertragen werden dürfen, sondern mit dem originalen MIME-Type (also application/x-javascript oder text/css, aber dem Hinweis auf die Komprimierung im Encoding.

Damit das alles auch funktioniert, muß es aber erlaubt sein, dies alles in .htaccess-Dateien zu definieren. Für das entsprechende Verzeichnis muß man also mindestens das folgende erlauben:


<Directory /var/www/html>
AllowOverride FileInfo
</Directory>

Ab diesem Punkt gilt es, sich an diese Sache zu erinnern. Wenn man jetzt an einer JavaScript- oder CSS-Datei mal was ändert, dann sollte man nicht vergessen auch die komprimierte Variante neu zu erzeugen, sonst wundert man sich am Ende, warum die Änderung nicht überall ankommt.

mkinitrd und Filesystem-Labels

09. Juli 2010 von Jonas Pasche

Au Backe, das hätte deutlich schiefer gehen können.

Nicht selten betreiben wir Server mit einem Xen-Kernel als Dom0 und binden die DomU aber nicht von einer lokalen Festplatte ein, sondern via iSCSI. Das stellt sich dann so dar, dass die Dom0 eine /dev/sda hat (z.B. eine eingebaute Festplatte); das iSCSI-Target wird dann eingebunden und steht als /dev/sdb bereit. Von diesem Device werden dann aber keine Partionen direkt gemountet, sondern das Device wird in der Xen-Konfiguration der DomU als phy:-Device angesprochen. So weit, so gut – klappt auch einwandfrei.

Nun hat der anaconda-Installer von CentOS 5 die Angewohnheit, für die Root- und die /boot-Partition Labels zu vergeben. Diese kann man auf der Shell z. B. mit e2label einsehen und auch nachträglich jederzeit ändern. In der /etc/fstab wird dann die Root-Partition nicht mit /dev/sda2 referenziert, sondern mit LABEL=/, weil die Partition eben das Label namens „/“ erhalten hat. Der Gedanke dahinter ist eigentlich gar nicht mal schlecht: Ändert ein Device seine Bezeichnung, beispielsweise weil ein zusätzliches Gerät am SCSI-Bus hängt und eine sda plötzlich eine sdb ist, so kann es dennoch unverändert angesprochen werden, weil es sein Label behält. Und das funktioniert auch prima.

Bis auf eins.

Wir führten auf einer Dom0 ein Kernel-Update durch. In diesem Zuge wurde auch eine neue initrd erstellt. Hierbei stellt mkinitrd alle für den Bootvorgang nötigen Module zusammen und strickt ein minimales init-Script. Das hat auch tausend Mal prima funktioniert – bis heute. Heute war nämlich gerade ein iSCSI-Target eingebunden – was an sich auch kein Problem ist, denn mkinitrd interessiert sich, was Treiber für Blockdevices angeht, eigentlich nur für die Devices, die in der /etc/fstab als zum Booten nötig referenziert werden. Nun nennt die /etc/fstab aber LABEL=/ als Root-Device, und damit nimmt das Unheil seinen Lauf. Zunächst einen Blick auf die Konfiguration:

# head -1 /etc/fstab 
LABEL=/ / ext3 defaults 1 1

# e2label /dev/sda2
/

# df -h | head -2
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda2              31G  1.9G   27G   7% /

Und jetzt das Problem. Es ist wie gesagt gerade ein iSCSI-Target eingebunden, das vom Kernel auf /dev/sdb gemappt wird und das nur eine Partition hat:

# ls -l /dev/disk/by-path/ip-*
lrwxrwxrwx 1 root root  9 Jul  8 22:32 /dev/disk/by-path/[...]-lun-0 -> ../../sdb
lrwxrwxrwx 1 root root 10 Jul  8 22:32 /dev/disk/by-path/[...]-lun-0-part1 -> ../../sdb1

Und eben jene Partition … hat auch das Label „/“:

# e2label /dev/sdb1
/

mkinitrd -v zeigt dann, dass es zwar versucht, das Richtige zu tun (nämlich herauszubekommen, was für eine Art Device die Root-Partition ist, damit es die dafür nötigen Treiber in die initrd einbinden kann, löst das Label aber prompt auf genau das falsche Device auf:

...
Found root device sdb1 for LABEL=/
Looking for driver for device sdb1
Found iscsi component /sys/devices/platform/host6/session5/target6:0:0/6:0:0:0
...

Damit erstellt es letztlich eine initrd mit dem falschen Root-Device – und baut dazu den gesamten iSCSI-Support mit ein, bis hin zu einem iscsistart-Aufruf, der die Zugangsdaten beinhaltet, die nötig sind, um das Target vom iSCSI-Host einbinden zu können. Ohne das -v dabei sieht man davon … nix. Vor allem sahen wir schon deshalb nichts davon, weil wir nicht mal mkinitrd selbst aufgerufen hatten – das hatte new-kernel-pkg aufgerufen, und auch das wiederum hatten wir nicht aktiv benutzt, sondern es befindet sich im %postinstall-Abschnitt des Kernel-RPMs, das wir gerade mit yum upgrade kernel-xen auf den neuesten Stand gebracht haben. Wie schon einige Male davor, immer ohne Probleme. Davor war eben zum Zeitpunkt der Kernel-Updates auch noch kein iSCSI-Target eingebunden.

Nach einem Reboot zeigte sich ein absurdes Bild. Beim Booten war deutlich erkennbar, wie sich der Server faktisch das iSCSI-Device holte und als /dev/sda vorzeigte; die lokale Festplatte war dadurch nun plötzlich /dev/sdb (in diesem Zusammenhang mag man ein zynisches „Ja und, dafür gibt’s doch die Labels!“ einwerfen. Sehr witzig, haha). Merkwürdigerweise war der Inhalt der Root-Partition definitiv der, der von der Root-Partition der lokalen Platte kam – obwohl jene laut mount das iSCSI-Device sein sollte. Kurz, das System befand sich in nicht wirklich definiertem Zustand, und darüberhinaus mussten wir uns darum sorgen, dass das fragliche iSCSI-Device, das gerade angeblich unsere Root-Partition darstellte (aber irgendwie eben auch wieder nicht) Schaden davontragen könnte, weil es nämlich parallel dazu auf einem anderen Xen-Host bereits eingebunden und aktiv in Benutzung war. Wir können uns bislang nicht erklären, wie es zu dieser merkwürdigen Differenz zwischen angeblichem und realem Mount-Zustand kommen konnte, aber auf wundersame Weise blieb das iSCSI-Target von diesem Schindluder völlig unbeeindruckt. Puh..!

Der Bugfix war letztlich trivial: Wir haben in der /etc/fstab der Dom0 alle Labels durch echte Devicenamen ersetzt und die initrd neu erstellt. Ein Reboot, und das System befand sich wieder in definiertem Zustand. Für heute: Mit einer großen Portion Glück davongekommen. Und viel über mkinitrd gelernt.

Die Telekom ist sich auch für nichts zu schade

02. Juli 2010 von Christopher Hirschmann

Neulich klingelt es an meiner Tür und als ich öffne, steht vor mir ein Telekom-Schlipsträger. Man habe neue Leitungen verlegt und „wir holen jetzt alle Anschlüsse hier im Haus zurück“.

Bevor ich mich darüber auslasse, hier ein paar Hintergrundinformationen: Ich hab mir beim letzten Umzug sehr genau überlegt, wo ich mir einen DSL- und einen ISDN-Anschluß herhole. Letzteres war nicht weiter schwer, da es nur eine kleine Auswahl an Anbietern gibt, von denen man noch ISDN-Anschlüsse bekommt. Bei den DSL-Anschlüssen war die Auswahl schon schwieriger, vor allem weil es neben den vielen Anbietern auch einen unglaublichen, meiner Ansicht nach schon kriminell kundenfeindlichen Wirrwar aus Tarifen und Vertragskonditionen gibt. Was ich da an Notizen, Übersichten und Gegenüberstellungen zusammengekritzelt habe… sagen wir einfach es endete schließlich darin, daß ich mir vorgenommen habe, beim nächsten Mal mit Computerunterstützung an diese Entscheidung heranzugehen. So oder so, die Telekom kam trotz meiner in über zehn Jahren aufgebauten, begründeten Abneigung gegen diesen Konzern letztlich in die engere Auswahl der vier Anbieter, bei denen ich tatsächlich angerufen habe, um mich nochmal beraten zu lassen und die Verfügbarkeit von ISDN und DSL in meiner zukünftigen Wohnung zu klären. (Und ich kann nur jedem empfehlen, tatsächlich anzurufen. Wie man sich allein durch den Tarifwald allein eines Anbieters finden soll, ist mir schleierhaft. Die Hotline-Mitarbeiter aller Anbieter mit denen ich telephoniert habe, kannten sich da besser aus und kannten sogar Tarifoptionen die ich auf den Webseiten der Anbieter nichtmal gefunden habe.)

Ergebnis: Damals konnte mir die Telekom in meiner neuen Wohnung kein VDSL anbieten (alle Anschlüsse in der Gegend sollen belegt gewesen sein – was mich in der Berliner Innenstadt, in der ich ja vor einigen Jahren Zeuge des großen VDSL-Ausbaus wurde, etwas komisch vorkam), und bei normalem DSL konnte mir die Telekom damals maximal etwa 2000 kbit/s im Downstream anbieten. Da ich von zu Hause aus arbeite und hin und wieder auch mal größere Datenmengen bei mir anfallen, war mir das zu wenig, zumal andere Anbieter für weniger Geld mehr geboten haben. Ein weiteres Problem war, daß ich bei der Telekom auch keine für meinen Bedarf optimale Tarifoption finden konnte. Ich hätte z.B. auch gerne für ein paar Euro im Monat die Option genommen, ihre recht weit verbreiteten WLAN-Router in Restaurants mitnutzen zu können, was sie aber nur in Verbindung mit Mobilfunkverträgen anbieten, wohlbemerkt auch nur mit den teuren Verträgen. Diese Firma will einfach keine Kunden haben.)

Zurück zum Telekom-Schlipsträger vor der Wohnungstür: Man wollte meinen Anschluß also „zurückholen“. Ich bräuchte das nur kurz zu bestätigen, dann werde alles sofort in die Wege geleitet und ich müßte mich um gar nichts kümmern. Der Mann erwähnte natürlich mit keiner Silbe, daß eventuelle Vertragslaufzeiten mit meinem derzeitigen Anbieter bestehen können und die Telekom die Kosten selbstverständlich nicht übernimmt (hab ich später recherchiert). Aber er erwähnte mehrmals, daß man mir großzügigerweise sehr künstige Konditionen machen würde, wenn es ums Mieten des VDSL-Modems geht. (Das hat mir fast die Schuhe ausgezogen. Man muß das Modem mieten? Man bekommt es nicht verkauft? Wie gesagt arbeite ich über meinen DSL-Anschluß, deswegen habe ich hier sogar ein Ersatz-DSL-Modem und einen Ersatz-Splitter rumliegen, falls mal was kaputt geht.)

Insgesamt hat der Mann von der Telekom sich große Mühe gegeben alles so klingen zu lassen, als wenn die Telekom mir ein sehr großzügiges und gnädiges Angebot macht und so oder so meinen Anschluß zu sich „zurückholt“. Er hat das gezielt so formuliert, daß es fast so klang als wenn ich da gar nichts gegen machen könnte. Ich kann mir gut vorstellen, daß das manchen Kunden verängstigt.

Nun gut, ich „bin ja vom Fach“ und weiß sehr genau, was ich brauche. Ich hab also direkt gefragt, wie es mit dem Anschluß mehrerer Endgeräte, ISDN, IPv6 und natürlich mit den Latenzen aussieht. Vor allem fragte ich selbstverständlich auch nach Traffic Shaping. Die meisten dieser Fragen hat er letztlich nicht beantwortet sondern ist mir ausgewichen. Immerhin erfuhr ich, daß ISDN extra kostet. Aber er hat fleißig weiter versucht mit mir ein Geschäft zu machen. Auch der Hinweis, daß mein Arbeitgeber den Anschluß bezahlt und ich das in jedem Fall mit ihm besprechen müßte, hat den Vertreter nicht gebremst. Das Zauberwort war dann letztlich die feste IP-Adresse. Kaum war es gefallen, verlor er sichtlich das Interesse und verwies darauf, daß ich dafür einen Geschäfts-Anschluß bräuchte und deutete an, daß dafür das Angebot nicht gilt. Hätte ich doch nur früher danach gefragt…

(Nebenbei kenne ich durch Kunden das Webinterface, über das man bei der Telekom eine feste IP-Adresse und den dazugehörigen PTR Resource Record im DNS verwaltet… und kann davon nur dringend abraten. Es ist unübersichtlich, unverständlich und mutet an als wäre es seit 1995 nicht mehr angefaßt worden.)

OK, der Schlipsträger zog ab und machte sich daran, meine übrigen Nachbarn zu belästigen. Ich machte die Tür zu und hörte noch das „wir holen hier im Haus alle Anschlüsse zurück“ das er meiner verschlafenen Nachbarin (Nachtschicht) entgegen schleuderte.

In der Rückschau war die ganze Episode recht amüsant. Aber ich frage mich halt, wie viel tiefer eine Firma eigentlich noch sinken kann. Haustürgeschäfte sind in meinen Augen wirklich das allerletzte. Aber wenn die Telekom sich in Gesellschaft der Zeugen Jehovas, Esotherik-Spinnern mit Wurzelhaarbürsten aus garantiert ökologischem Anbau und anderem Geschmeiß wohl fühlt, meinetwegen. Wer schon mal versucht hat, einen Vertrag mit der Telekom zu kündigen, wir sie in der Nähe von Sekten vermutlich auch ganz gut aufgehoben finden. Wer weiß, vielleicht baut die Telekom auch bald in Fußgängerzonen Tischlein auf und „mißt“ mit einen schlechten Hautwiderstandsmessgerät wie zufrieden ich mit meinem derzeitigen Anbieter bin.

Aus Sicht des Datenschutzes ist meines Erachtens nach durchaus interessant, daß die Telekom die Information, daß einer ihrer Konkurrenten über eine ihrer Leitungen (geht ja nicht anders) einen bestimmten Haushalt mit Telephon und DSL versorgt, letztlich für die Kundenaquise nutzt. Würde mich mal interessieren, ob das in die Rubrik Nutzung meiner persönlichen Daten zu Werbezwecken fällt. Ich weiß, daß es technisch nicht vermieden werden kann, daß die Telekom weiß, daß ich meinen Anschluß nicht bei ihr habe, aber dürfen sie mich deswegen besuchen oder anrufen? (Und falls jetzt jemand denkt, die Telekom würde genauso wie die Zeugen vorgehen und einfach an jeder Tür klingeln, den muß ich enttäuschen: Wie ich später erfragte, hat er nicht bei all meinen Nachbarn geklingelt, nur bei denen die keinen Telekomanschluß haben – was in meinem Haus nachvollziehbar viele zu sein scheinen.)

RAID1 mit neuen Platten vergrößern

24. Juni 2010 von Jonas Pasche

In einem Kundenserver, der schon ein paar Jahre auf dem Buckel hat, sollte der Speicherplatz erweitert werden – eingebaut waren zwei Festplatten mit je 160 GB; für heutige Verhältnisse in der Tat nicht mehr zeitgemäß. Möglichkeiten gibt es viele: Zusätzliche Platten einbauen (dann verbraucht die Kiste aber mehr Strom und wird lauter); eine externe Festplatte anschließen (die ist dann aber nicht redundant); die Festplatten austauschen und das System umkopieren (das dauert dann aber ewig). Schließlich haben wir uns für die schickste Möglichkeit mit minimaler Downtime entschieden: Die Festplatten nacheinander im RAID auswechseln. Downtime: Etwa zweimal fünf Minuten jeweils für den Austausch einer Platte. Plus ein bisschen Arbeitsaufwand, der aber im laufenden Betrieb erledigt werden kann.

Zum Nachmachen hier nun das Protokoll. Die Festplatten sind Platten nach S-ATA-Standard; stehen im System also als /dev/sda und /dev/sdb bereit. Sie haben jeweils drei Partionen, die per Software-RAID zusammengefasst sind:

# cat /proc/mdstat 
Personalities : [raid1] 
md1 : active raid1 sda1[0] sdb1[1]
      200704 blocks [2/2] [UU]
      
md2 : active raid1 sda3[0] sdb3[1]
      974550976 blocks [2/2] [UU]
      
md0 : active raid1 sda2[0] sdb2[1]
      2008000 blocks [2/2] [UU]
      
unused devices: 

Dabei fungiert /dev/md0 als Swap-Partition; /dev/md1 als /boot und /dev/md2 als Root-Partition. So, und jetzt geht’s los.

Erstmal wird kurz und schmerzlos eine der beiden Platten ausgebaut – in unserem Fall /dev/sdb, aber welche zuerst kommt, spielt keine Rolle. An ihrer Stelle wurde eine unpartitionierte Platte eingebaut. Der Server wurde wieder hochgefahren; alles kein Problem – dafür ist ein RAID1 ja da. Anschließend galt es, die neue Festplatte zu partitionieren. Das haben wir hier ganz traditionell mit fdisk gemacht und dabei für die ersten beiden kleinen Partitionen (Swap und /boot) die gleiche Zylinderzahl verwendet; die dritte Partition wurde dann entsprechend ausgeweitet. So sieht’s nun aus:

# fdisk -l /dev/sdb

Disk /dev/sdb: 1000.2 GB, 1000204886016 bytes
255 heads, 63 sectors/track, 121601 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *           1          25      200781   fd  Linux raid autodetect
/dev/sdb2              26         275     2008125   fd  Linux raid autodetect
/dev/sdb3             276      121601   974551095   fd  Linux raid autodetect

Wichtig ist, hier den korrekten Partitionstyp zu vergeben (in diesem Fall fd für Linux raid autodetect) sowie die erste Partition bootfähig zu machen.

Nun fehlt den drei /dev/md*-Devices ja die Redundanz; die stellen wir also erstmal wieder her, in dem wir mittels mdadm die frisch angelegten Partitionen hinzufügen:

mdadm /dev/md1 -a /dev/sdb1
mdadm /dev/md0 -a /dev/sdb2
mdadm /dev/md2 -a /dev/sdb3

Die ersten beiden synchronisieren in unter einer Minute; für /dev/md2 wurde gut eine halbe Stunde fällig. Mittels

watch cat /proc/mdstat

lässt sich die Synchronisation gut beobachten. Dieser Vorgang muss vollständig und fehlerfrei abgeschlossen sein, denn wir haben ja vor, gleich die Platte, von der synchronisiert wird, ebenfalls zu wechseln, und dafür muss die neu eingebaute natürlich komplett auf dem neuesten Stand sein!

Nun möchte man meinen, das wär’s, aber eine ganz wichtige Sache fehlt noch: Auch wenn die Daten jetzt wieder synchron sind, so fehlt noch der Bootloader – sprich, wenn jetzt die verbliebene Platte ausgewechselt würde, könnte das System nicht mehr booten. Es gilt also, GRUB auf /dev/sdb zu installieren, und das ist nicht ganz so einfach, wie es auf den ersten Blick scheint. Die RAID1-Redundanz liegt ja auf Partitionsebene – betrifft also nicht den Bootsektor selbst.

Der Haken an der Sache ist: GRUB führt eine device.map, in der die physischen Festplatten auf die GRUB-internen Bezeichnungen hd0 und hd1 gemappt werden. Kein Problem, solange es die zweite Festplatte ist, die ausfällt – fällt allerdings die erste aus, wird /dev/sdb als einzige verbleibende Platte plötzlich hd0 statt hd1 – und das schmeckt GRUB nun überhaupt nicht. Man kann nun in der device.map zwar beide Platten von Hand als hd0 definieren; das mag jedoch grub-install nicht und quittiert mit:

The drive (hd0) is defined multiple times in the device map /boot/grub/device.map

Es bleibt also letztlich nur, das Spiel von Hand zu spielen, und zwar so:

grub> device (hd0) /dev/sdb
grub> root (hd0,0)
grub> setup (hd0)
grub> quit

Mit anderen Worten: Man verkauft GRUB die zweite Platte als hd0, also als erste, denn wenn die echte erste Platte einmal ausfällt – dann ist die zweite Platte ja auch faktisch die erste. Das klappt prima, und das System ist bereit zum Austausch der verbliebenen kleinen Platte gegen das größere Modell. Hat man alles richtig gemacht, sollte der Server problemlos mit der einen neuen großen Platte booten (die nun ihrerseits auch nur eine Hälfte des RAID1 darstellt).

Nun sind die obigen Schritte für die verbliebene Platte, in unserem Fall /dev/sda, zu wiederholen: Partitionieren; mittels mdadm zum RAID1 hinzufügen; GRUB von Hand in den Bootsektor packen.

Nun haben wir einen Server mit neuen Festplatten, bei denen die Root-Partition deutlich größer ist, aber bisher haben wir davon noch nichts – es fehlen noch zwei entscheidende Schritte. Zunächst muss die Partition /dev/md2, die jetzt nur noch einen Bruchteil des physisch vorhandenen Speicherplatzes nutzt, auf die neue Größe ausgedehnt werden. Das erledigen wir mit:

mdadm --grow -z max /dev/md2

Hiermit wird für den zusätzlichen Speicherplatz ein Resync angestoßen – sprich, dieser Vorgang zieht sich eine ganze Weile, zumal wir ja hier auf Terabyte-Platten aufgerüstet haben. Im konkreten Fall mussten wir rund zwei Stunden dafür einkalkulieren.

Nun sind wir aber immer noch nicht fertig. Zwar ist das Device jetzt endlich so groß, wie wir es haben wollen, aber das Dateisystem weiß davon noch nichts – und das muss schließlich die neu hinzugekommenen Blöcke auch ansprechen können.

Erfreulicherweise gibt es seit einigen Jahren die Möglichkeit, ein ext2-/ext3-Dateisystem im laufenden Betrieb, sprich: im gemounteten Zustand zu vergrößern. Bei CentOS 4, das auf diesem Server noch läuft (kein Problem, wird ja noch bis Anfang 2012 supportet), übernimmt diesen Job noch ext2online; bei neueren Distributionen ist der Live-Resizing-Code in resize2fs aufgegangen, das bis dahin nur eine Größenanpassung von nicht aktiv verwendeten Devices realisierte. Auf geht’s:

ext2online -v /dev/md2

Der Vorgang zieht sich ein bisschen, aber man kann sehr schön beobachten, wie sich die Größe im laufenden Betrieb ändert – und sich der prozentuale Füllstand der Root-Partition entsprechend nach und nach verringert:

# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/md2              178G  123G   46G  73% /
/dev/md1              190M   15M  166M   8% /boot
none                  505M     0  505M   0% /dev/shm

… warten …

# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/md2              203G  123G   69G  65% /
/dev/md1              190M   15M  166M   8% /boot
none                  505M     0  505M   0% /dev/shm

… warten …

# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/md2              213G  123G   79G  62% /
/dev/md1              190M   15M  166M   8% /boot
none                  505M     0  505M   0% /dev/shm

Wenn dieser Vorgang abschlossen ist – war’s das. Viel Spaß mit dem neu hinzugewonnenen Speicherplatz!

Nicht mal rechnen können die Spammer

24. Juni 2010 von Jonas Pasche

Da passt ja nun mal überhaupt keine Summe zur anderen:

Ausschnitt aus der gefälschten Amazon-Bestellbestätigung

Jegliche Links in der – selbstverständlich frei erfundenen – Bestellbestätigung verweisen dann auf einen der bekannten Online-Pillenverkäufer.

Interessant: Die Bestellbestätigung ahmt das Original von Amazon wirklich bis ins Detail nach – selbst die zusätzlichen X-AMAZON-Header finden sich auch hier. Die Bilder in der HTML-Mail sind im Übrigen nicht eingebettet, sondern würden (sofern man sowas zulässt) vom Amazon.com-Server nachgeladen. Damit haben die Spammer zumindest eins geschafft: Sie haben sich durch mein DSPAM gekämpft, das ansonsten mit einer Treffergenauigkeit von derzeit 99,84% für meinen Mailaccount aufwarten kann. Dafür gebührt den Spammern dann schon ein wenig Respekt, trotz Rechenschwäche. ;-)


Impressum