Digital > Fefes Blog 2.0 > a5f47809
  Leserreporter: Wer schöne Verschwörungslinks für mich hat: ab an felix-bloginput (at) fefe.de!
[zurück][ältere Posting][neuere Posting]  Sonntag, 27 Mai 2018 | Blog: 1 | No: 40944     feed-image

ob sich Privilege Separation überhaupt lohnt, wenn man dieses Problem mit sich rumschleppt?

Sorry, das wird jetzt eine halbe Stunde Tech-Rambling und für viele Leser wahrscheinlich nicht weiterführend. :-)
Ich habe ja vor inzwischen vielen Jahren eine Abstraktionsschicht über diverse Netzwerk-Event-APIs der verschiedenen Plattformen geschrieben, und darauf meinen Webserver gatling aufgebaut. Seit dem hat sich im Internet einiges verschoben. Web ohne TLS spricht niemand mehr, also habe ich TLS nachgerüstet. Für Web mit TLS ist aber das Skalierungsproblem ein ganz anderes. Da geht es nicht mehr darum, dass jemand viele Verbindungen aufbaut und dein Server damit nicht klarkommt, sondern es geht darum, dass jemand viele TLS-Handshakes lostritt und dein Server die ganze Zeit mit 100% CPU läuft.
Seit dem hat sich auch eine andere Sache verändert: CPUs mit nur einem Core gibt es nicht mehr. Es liegt also auf der Hand, dass ich mal das Modell von gatling und der Abstraktionsschicht ändern sollte, damit es sich auf mehrere Cores verteilen lässt. Das ist leider kein Selbstläufer, weil die unterliegenden APIs teilweise nervige Probleme haben. So gibt es einmal die fundamentale Frage, ob man edge triggered oder level triggered arbeiten soll. Level Triggering ist, was poll macht. Wenn du nach Deskriptor 3 fragst, und 3 ist lesbar, und du tust nichts mit ihm sondern rufst wieder poll auf und fragst nach Deskriptor 3, dann sagt dir poll wieder, dass er lesbar ist. epoll und kqueue arbeiten genau so, lassen sich aber umschalten. Edge Triggering heißt, dass das API dir nur einmal Bescheid sagt, dass ein Deskriptor lesbar ist. So funktioniert der Vorläufer von epoll, SIGIO, den meine Abstraktion unterstützt. Das verkompliziert aber das Programmiermodell massiv, weil ich jetzt darüber Buch führen muss, welche Deskriptoren lesbar sind und welche nicht. Dafür hat es aber den großen Vorteil, dass multithreading sozusagen von alleine geht.
Nehmen wir mal an, du hast vier Threads, und alle rufen epoll auf. Deskriptor 3 wird lesbar. Dann kommen alle Threads zurück und melden das. Das ist natürlich Unsinn, weil nur einer zum Zuge kommt und die anderen sinnlos Strom verbraucht haben.
Daher macht man das entweder so, dass nur ein Thread epoll aufruft und eine Datenstruktur befüllt, die dann von Worker Threads abgearbeitet wird. Oder man macht es so, dass jeder Thread einen eigenen Pool aus disjunkten Verbindungen verwaltet und die jeweils mit einem eigenen epoll bearbeitet. Die bessere Skalierbarkeit hat auf jeden Fall Variante 2, aber dann sollte man keine Threads sondern Prozesse nehmen.
gatling hat Fälle, z.B. CGI oder Proxy-Modus, bei denen mehr als ein Deskriptor zu einer Verbindung gehört. Das naive aufteilen der Deskriptoren auf Pools funktioniert also nicht, denn zusammengehörende Verbindungen müssen auch im selben Pool sein. Oder man baut ein API, um nachträglich Deskriptoren hin- und herzuschieben. Das wäre auch die bessere Lastverteilung gut, aber zieht einen Rattenschwanz an Komplexität nach sich. Ist daher aus meiner Sicht gerade erstmal unattraktiv.
Ich habe mich daher entschieden, lieber jeweils nur einen Thread zu haben, der epoll macht (oder halt kqueue, whatever), und der befüllt eine Datenstruktur, und aus der bedienen sich dann die Worker-Threads. Die Threads handeln das on the fly untereinander aus, wer gerade für Event-Abholen zuständig ist, und wer auf die Datenstruktur wartet. Damit das API nach außen einfach bleibt.
Das sieht ganz gut aus und scheint auch schön zu flutschen und vor allem ist das API viel einfacher als was ich vorher hatte. Aber ich kam ins Grübeln. Wenn ich hier schon einen neuen Webserver mache, dann soll der aber auch nicht nur TLS machen, sondern TLS mit Privilege Separation. Wenn jemandem ein Angriff auf den Webserver gelingt, möchte ich gerne möglichst minimieren, was er damit anstellen kann. In erster Näherung stelle ich mir vor, dass der Private Key von dem Server in einem separaten Prozess liegt (und ich per seccomp-filter o.ä. verhindere, dass man da ptrace o.ä. machen kann). Aber je mehr ich darüber nachdenke, desto weniger gangbar sieht das aus. Das erste Problem ist, und dafür kann TLS jetzt nichts, wenn ich den Handshake-Teil in einen Prozess tue und nur die Session Keys in den anderen Teil rüberreiche, dann hat ein Angreifer immer noch alle Session Keys im Zugriff. Das ist immer noch ziemlich schlimm. Gut, dank Forward Secrecy ist es kein Super-GAU, aber gut wäre es nicht.
Ein möglicher Ausweg wäre, dass man das gesamte TLS auslagert, und sozusagen durch einen TLS-Proxy-Prozess kommuniziert. Das riecht erstmal nach einem prohibitiv teuren Performance-Nachteil. Und gewonnen hätte man damit auch nicht viel, denn ein Angreifer könnte trotzdem allen Verkehr aller anderen Leute sehen und ihnen Müll senden, um sie anzugreifen, er könnte bloß nicht den schon gelaufenen Teil der Verbindung entschlüsseln. Ich glaube, das wäre die Kosten nicht wert.
Aber was, wenn man das TLS in den Kernel schieben kann. Linux hat seit kurzem ein API für Kernel-TLS. Da gibt man dem Kernel per setsockopt den Schlüssel und dann kann man auf dem Socket ganz normal Daten rausschreiben. Das klingt sehr attraktiv, denn den setsockopt könnte der TLS-Handshake-Prozess machen und dann den Socket rüberreichen zu dem Web-Prozess. Nachteil: Der Kernel-Support ist rudimentär und kann nur Daten rausschreiben. Kann nicht lesen, und kann keine Kontrollnachrichten schicken. Das muss man über ein krudes Spezial-API machen. Das müsste man dann zurück an den TLS-Handshake-Prozess delegieren. Gut, wäre denkbar. Schlimmer noch: Lesen kann der Kernel-TLS auch nicht. Das ist schon eher hinderlich.
OK, nehmen wir an, ich habe meinen Webserver aufgeteilt. Ein Prozess macht HTTP, einer macht TLS-Handshake. TLS hat aus Performance-Gründen ein Feature namens Session Resumption. Der Server behält die Krypto-Parameter für Verbindungen eine Weile vorrätig, und gibt dem Client ein Cookie. Wenn derselbe Client wieder kommt, kann er einfach den Cookie vorzeigen, und der Server findet dann die Krypto-Parameter und benutzt die weiter. Spart eine Menge teure Public-Key-Krypto und ist super für die Performance. Aber auf der anderen Seite heißt das eben, dass ein Angriff auf den Server auch diese ganzen gespeicherten Krypto-Kontexte im Speicher finden kann, und mit ihnen mitgesniffte Daten anderer Leute entschlüsseln kann. Das ist mal richtig doll Scheiße. Die Frage ist, ob sich Privilege Separation überhaupt lohnt, wenn man dieses Problem mit sich rumschleppt?
Und da denke ich mir gerade: Hey, was, wenn man die Tickets (so heißen die Cookies bei TLS) und die Krypto-Kontexte in einen dritten separaten Prozess packt. Grundsätzlich könnte das sogar sowas wie ein Redis auf einem anderen Server sein, dann könnten alle Frontends in einem Loadbalancer untereinander Session Resumption machen. Cloudflare hat vor ner Weile mal einen Hack in die Richtung angekündigt. Die Sicherheit von den Tokens basiert darauf, dass sie lang und zufällig und damit nicht vorhersagbar oder ratbar sind. Da müsste man gucken, wie man verhindert, dass ein Angreifer den Redis-Traffic mitsnifft, sonst hat man nichts gewonnen.
Naja und fundamental gibt es natürlich auch noch das Problem, dass die ganzen TLS-Libraries da draußen gar nicht vorsehen, dass man in ihren Interna herumpfuscht, und sowas macht wie nach einem Handshake die Krypto-Keys extrahieren und in einen anderen Prozess schieben, oder auf der anderen Seite einen bestehenden Krypto-Kontext entgegennehmen, aber ohne Public-Key-Teil des Kontexts, und dann damit symmetrisch Krypto zu machen.
Die nächste Frage ist natürlich auch, was eigentlich mehr CPU frisst, die Handshakes oder der symmetrische Teil. Das wird vermutlich von der Traffic-Load auf dem Server abhängen, ob der Videostreaming macht oder sowas wie mein Blog, was aus lauter kleinen kurzen Verbindungs-Bursts besteht.
Was ich sagen will: Ist alles nicht so einfach, und es ist auch nicht klar, ob sich der ganze Aufwand am Ende lohnen würde. Sollte aber glaube ich trotzdem mal jemand machen.

Update: Und es stellt sich natürlich die Frage, wen man hier eigentlich vor wem beschützen möchte. Muss man nicht eher befürchten, dass jemand den TLS-Stack hackt, als dass jemand den Webserver hackt?

Update: Vielleicht muss man sich aus Sicherheitsgesichtspunkten einfach generell von der Idee verabschieden, in einem Serverprozess mehrere Verbindungen zu multiplexen. Vielleicht sollte ich mal ein Extremkonzept implementieren, um zu gucken, wie schlimm das performt. Einfach damit man mal einen Datenpunkt hat. So pro Verbindung einen httpd-Prozess und einen TLS-Privilege-Separation-Prozess. Aber nicht abforken sondern schön fork+exec, damit die alle eigenes ASLR kriegen. Rein intuitiv kann das nicht gut performen, aber auf der anderen Seite gehen Rechnerarchitekturen ja gerade hin zu 256-Core-ARM-Servern. So würde man die ganzen Cores immerhin sinnvoll nutzen.




[zurück] [ältere Posting][neuere Posting]
[zurück] [ältere Posting][neuere Posting]

Fefes Latest Youtube Video Links