Freitag, 27. Februar 2009

mod_jk und SSL-Verbindungen hängen

Das Problem

Mehrere Probleme mit einer Ursache: Verbindungen von Apache über mod_jk zu einem JBoss (genauer zu dem darin enthaltenen Tomcat) hängen und SSL Verbindungen wollen sich einfach nicht aufbauen. Das Problem tritt manchmal auf, ein Apache-Neustart oder die Option "JkOptions +DisableReuse" scheinen manchmal zu helfen.

Die Ursache

Die Ursache ist aber eine ganz andere: fehlender Zufall! Linux stellt zwei Geräte zur verfügung, die Zufall liefern: /dev/random und /dev/urandom. Diese greifen beide auf eine Kernel-interne Entropie-Quelle zu, die sich aus recht unvorhersehbaren Ereignissen im System füllt, wie Festplattenzugriffe, Netzwerkaktivität, Tastatur- und Mauseingaben usw. Der Unterschied zwischen den beiden ist, dass /dev/random sich nur auf diese Ereignisse verlässt, während /dev/urandom auch einen Pseudozufallszahlengenerator (PRNG) verwendet. In der Praxis bedeutet dies, dass /dev/random "hängen" kann, wenn zuviel Zufall abgerufen wurde, während /dev/urandom immer ein Ergebnis liefert.

Man kann sich leicht anzeigen lassen, wieviel Zufall der Kernel gerade zur Verfügung hat:

cat /proc/sys/kernel/random/entropy_avail

Mit einem

cat /dev/random

kann man den Zufall auslesen. Der Befehl hängt recht schnell und kann mit Strg-C abgebrochen werden. Eventuell ist das Terminal dann verkonfiguriert, was man durch Eingabe von "reset" wieder korrigiert. Versucht man das Gleiche mit /dev/urandom, so erhält man ständig neue Werte.

Java verwendet standardmäßig /dev/random für java.secure.SecureRandom, was wiederum für SSL-Verbindungen u.ä. verwendet wird. JBoss Seam verwendet es für seine Session-IDs. Gibt es nicht genug Zufall, dann wartet Java auf /dev/random und deshalb teilweise recht lange, bis es weiter geht.

Die Lösung

Hängt ein Prozess aktuell, so muss man für mehr Zufall sorgen, indem man beispielsweise mit einem

ls -lR /

oder

hdparm -t /dev/sda

für Festplattenaktivität sorgt. Besser ist es, den Hänger gar nicht erst auftreten zu lassen. Hierzu muss man Java lediglich anweisen, statt /dev/random einfach /dev/urandom zu verwenden. Dies geht mit einer einfachen Kommandozeilenoption für die JVM:

-Djava.security.egd=file:/dev/urandom

oder direkt im Programmcode mit

System.setProperty("java.security.egd", "file:/dev/urandom");

Der Nachteil dieses Ansatzes ist, dass sich der Zufall von /dev/urandom leichter vorhersagen lässt und damit beispielsweise ein Angriff auf SSL-Verbindungen denkbar ist. Wer hier auf der sicheren Seite bleiben will, der muss über einen Hardware-Zufallszahlengenerator nachdenken. Ein leicht zu realisierender Ansatz ist ein Mikrofon vor einem Lüfter. Die damit aufgenommenen Samples aber noch etwas nachbearbeiten, aber das ist ein ganz anderes Thema.