Viele Organisationen, insbesondere finanzielle, müssen sich mit verschiedenen Sicherheitsstandards auseinandersetzen - zum Beispiel PCI DSS. Solche Zertifizierungen erfordern eine Datenverschlüsselung. Transparente Datenverschlüsselung auf der Festplatte Die transparente Datenverschlüsselung ist in vielen industriellen DBMS implementiert.
Apache Ignite wird in Banken verwendet, daher wurde beschlossen, TDE darin zu implementieren.
Ich werde beschreiben, wie wir TDE durch die Community, öffentlich, durch Apachev-Prozesse entwickelt haben.
Unten finden Sie eine Textversion des Berichts:
Ich werde versuchen, über Architektur zu sprechen, über die Komplexität der Entwicklung, wie sie in Open Source wirklich aussieht.
Was wurde getan und was bleibt noch zu tun?
Derzeit implementiertes Apache Ignite TDE. Phase 1.
Es enthält die grundlegenden Funktionen zum Arbeiten mit verschlüsselten Caches:
- Schlüsselverwaltung
- Verschlüsselte Caches erstellen
- Speichern aller Cache-Daten auf der Festplatte in verschlüsselter Form
In Phase 2 ist geplant, die Möglichkeit der Drehung (Änderung) des Hauptschlüssels zu ermöglichen.
In Phase 3 die Möglichkeit, Cache-Schlüssel zu drehen.
Terminologie
- Transparente Datenverschlüsselung - Transparente (für den Benutzer) Datenverschlüsselung beim Speichern auf der Festplatte. Im Fall von Ignite Cache-Verschlüsselung, da es bei Ignite um Caches geht.
- Cache entzünden - Schlüsselwert-Cache in Apache Ignite. Cache-Daten können auf der Festplatte gespeichert werden
- Seiten - Datenseiten. In Ignite werden alle Daten paginiert. Seiten werden auf die Festplatte geschrieben und müssen verschlüsselt werden.
- WAL - Protokoll vorausschreiben. Dort werden alle Datenänderungen in Ignite gespeichert, alle Aktionen, die wir für alle Caches ausgeführt haben.
- Keystore - Standard-Java-Keystore, der von Keytool Javascript generiert wird. Es funktioniert und ist überall zertifiziert, wir haben es benutzt.
- Hauptschlüssel - Hauptschlüssel. Damit werden Schlüssel für Tabellen verschlüsselt, Cache-Verschlüsselungsschlüssel. Im Java Keystore gespeichert.
- Cache-Schlüssel - Schlüssel, mit denen Daten tatsächlich verschlüsselt werden. Zusammen mit dem Hauptschlüssel wird eine zweistufige Struktur erhalten. Der Hauptschlüssel wird getrennt vom Schlüsselcache und den Stammdaten gespeichert - aus Sicherheitsgründen, zur Trennung von Zugriffsrechten usw.
Architektur
Alles wird nach folgendem Schema implementiert:
- Alle Cache-Daten werden mit dem neuen Encryption SPI verschlüsselt.
- Standardmäßig wird AES verwendet - ein industrieller Verschlüsselungsalgorithmus.
- Der Hauptschlüssel wird in einer JKS-Datei gespeichert - einer Standard-Java-Datei für Schlüssel.
Banken und andere Organisationen verwenden ihre eigenen Verschlüsselungsalgorithmen: GOST und andere. Es ist klar, dass wir die Möglichkeit geboten haben, unser Encryption SPI - die Verschlüsselungsimplementierung, die ein bestimmter Benutzer benötigt - zu löschen.
Arbeitsschema

Wir haben also RAM - Direktzugriffsspeicher mit Seiten, die reine Daten enthalten. Die Verwendung von RAM bedeutet, dass wir nicht vor einem Hacker geschützt sind, der Root-Zugriff erhalten und den gesamten Speicher gesichert hat. Wir schützen uns vor dem Administrator, der die Festplatte nimmt und auf dem Tushino-Markt verkauft (oder wo derzeit ähnliche Daten verkauft werden).
Zusätzlich zu den Seiten mit dem Cache werden die Daten auch in einem Vorausschreibprotokoll gespeichert, das das Delta der in der Transaktion geänderten Datensätze auf die Festplatte schreibt. Der Metastore speichert Cache-Verschlüsselungsschlüssel. Und in einer separaten Datei - einem Hauptschlüssel.
Jedes Mal, wenn ein Schlüssel für den Cache erstellt wird, bevor wir in das Netzwerk schreiben oder in das Netzwerk übertragen, verschlüsseln wir diesen Schlüssel mit einem Hauptschlüssel. Damit niemand den Cache-Schlüssel nach dem Empfang von Ignite-Daten erhalten kann. Nur wenn Sie sowohl den Hauptschlüssel als auch die Daten stehlen, können Sie darauf zugreifen. Dies ist unwahrscheinlich, da für den Zugriff auf diese Dateien verschiedene Rechte erforderlich sind.
Der Aktionsalgorithmus lautet wie folgt:
- Subtrahieren Sie zu Beginn des Knotens den Hauptschlüssel von jks.
- Lesen Sie zu Beginn der Knoten den Metaspeicher und entschlüsseln Sie die Cache-Schlüssel.
- Beim Verbinden von Knoten in einem Cluster:
- Überprüfen Sie die Hauptschlüssel-Hashes.
- Überprüfen Sie die Schlüssel auf gemeinsam genutzte Caches.
- Speichern Sie die Schlüssel für neue Caches.
- Wenn Sie einen Cache dynamisch erstellen, generieren wir einen Schlüssel und speichern ihn im Metaspeicher.
- Beim Lesen / Schreiben einer Seite entschlüsseln / verschlüsseln wir sie.
- Jeder WAL-Eintrag für den verschlüsselten Cache wird ebenfalls verschlüsselt.
Jetzt genauer:
Am Anfang des Knotens haben wir einen Rückruf, der unsere EncryptionSPI startet. Entsprechend den Parametern subtrahieren wir den Hauptschlüssel von der jks-Datei.
Wenn der Metastore fertig ist, erhalten wir die gespeicherten Verschlüsselungsschlüssel. In diesem Fall haben wir bereits einen Hauptschlüssel, damit wir die Schlüssel entschlüsseln und Zugriff auf die Cache-Daten erhalten können.
Unabhängig davon gibt es einen sehr interessanten Prozess - wie verbinden wir einen neuen Knoten zu einem Cluster? Wir haben bereits ein verteiltes System, das aus mehreren Knoten besteht. Wie kann ich sicherstellen, dass der neue Knoten korrekt konfiguriert ist und kein Angreifer ist?
Wir führen diese Aktionen aus:
- Wenn ein neuer Knoten ankommt, sendet er einen Hash vom Hauptschlüssel. Wir sehen, dass es mit dem bestehenden übereinstimmt.
- Anschließend überprüfen wir die Schlüssel für gemeinsam genutzte Caches. Vom Knoten kommen die Cache-ID und der verschlüsselte Cache-Schlüssel. Wir überprüfen sie, um sicherzustellen, dass alle Daten auf allen Knoten mit demselben Schlüssel verschlüsselt sind. Wenn dies nicht der Fall ist, haben wir einfach nicht das Recht, den Knoten in den Cluster zu lassen, andernfalls wird er über Schlüssel und Daten übertragen.
- Wenn auf dem neuen Knoten neue Schlüssel und Caches vorhanden sind, speichern Sie diese für die zukünftige Verwendung.
- Beim dynamischen Erstellen eines Caches wird eine Schlüsselgenerierungsfunktion bereitgestellt. Wir generieren es, speichern es im Metaspeicher und können die beschriebenen Vorgänge weiter ausführen.
Der zweite Teil ist ein Überbau über E / A-Operationen. Seiten werden in die Partitionsdatei geschrieben. Unser Add-In überprüft den Seiten-Cache, verschlüsselt sie entsprechend und speichert sie.
Gleiches gilt für WAL. Es gibt einen Serializer, der WAL-Datensatzobjekte serialisiert. Und wenn der Datensatz für verschlüsselte Caches bestimmt ist, müssen wir ihn verschlüsseln und erst dann auf der Festplatte speichern.
Entwicklungsschwierigkeiten
Allen mehr oder weniger komplexen Open Source-Projekten gemeinsame Schwierigkeiten:
- Zuerst müssen Sie das Ignite-Gerät vollständig verstehen. Warum, was und wie es dort gemacht wurde, wie und an welchen Stellen Sie Ihre Handler anbringen sollten.
- Abwärtskompatibilität ist erforderlich. Dies kann ziemlich schwierig sein, nicht offensichtlich. Bei der Entwicklung eines Produkts, das andere verwenden, müssen Sie berücksichtigen, dass Benutzer problemlos aktualisiert werden möchten. Abwärtskompatibilität ist richtig und gut. Wenn Sie eine so große Verbesserung wie TDE vornehmen, ändern Sie die Regeln für das Speichern auf der Festplatte und verschlüsseln etwas. Und an der Abwärtskompatibilität muss gearbeitet werden.
- Ein weiterer nicht offensichtlicher Punkt betrifft die Verteilung unseres Systems. Wenn verschiedene Clients versuchen, denselben Cache zu erstellen, müssen Sie sich auf den Verschlüsselungsschlüssel einigen, da standardmäßig zwei verschiedene Clients generiert werden. Wir haben dieses Problem gelöst. Ich werde nicht näher darauf eingehen - die Lösung verdient einen separaten Beitrag. Jetzt verwenden wir garantiert einen Schlüssel.
- Das nächste wichtige Ding führte zu großen Verbesserungen, als es schien, dass alles fertig war (eine vertraute Geschichte?) :). Die Verschlüsselung hat Overhead. Wir haben einen Init-Vektor - Null-Zufallsdaten, die im AES-Algorithmus verwendet werden. Sie werden in offener Form gespeichert, und mit ihrer Hilfe erhöhen wir die Entropie: Dieselben Daten werden in verschiedenen Verschlüsselungssitzungen unterschiedlich verschlüsselt. Selbst wenn wir zwei Ivan Petrovs mit demselben Nachnamen haben, erhalten wir jedes Mal, wenn wir verschlüsseln, unterschiedliche verschlüsselte Daten. Dies verringert die Wahrscheinlichkeit von Hacking.
Die Verschlüsselung erfolgt in Blöcken von 16 Byte. Wenn die Daten nicht auf 16 Byte ausgerichtet sind, fügen wir Füllinformationen hinzu - wie viele Daten wir tatsächlich verschlüsselt haben. Auf einer Festplatte müssen Sie eine Seite schreiben, die ein Vielfaches von 2 KB ist. Dies sind die Leistungsanforderungen: Wir müssen den Plattenpuffer verwenden. Wenn wir nicht 2 KB schreiben (nicht 4 oder nicht 8, abhängig vom Festplattenpuffer), erhalten wir sofort eine große Drop-Leistung.
Wie haben wir das Problem gelöst? Ich musste in PageIO im RAM kriechen und 16 Bytes von jeder Seite abschneiden, die beim Schreiben auf die Festplatte verschlüsselt würden. In diese 16 Bytes schreiben wir den Init-Vektor.
- Eine andere Schwierigkeit besteht darin, nichts zu zerbrechen. Dies ist eine häufige Sache, wenn Sie kommen und einige Änderungen vornehmen. In Wirklichkeit ist es nicht so einfach, wie es scheint.
- In MVP stellte sich heraus, 6 Tausend Zeilen. Es ist schwierig zu überprüfen, und nur wenige Menschen möchten dies tun - insbesondere von Experten, die bereits keine Zeit haben. Wir haben verschiedene Teile - öffentliche API, Kernteil, SPI-Manager, persistenter Speicher für Seiten, WAL-Manager. Änderungen in verschiedenen Subsystemen erfordern, dass sie von verschiedenen Personen überprüft werden. Dies bringt auch zusätzliche Schwierigkeiten mit sich. Besonders wenn Sie in einer Community arbeiten, in der alle Menschen mit ihren Aufgaben beschäftigt sind. Trotzdem hat bei uns alles geklappt.
Was wird in TDE passieren? Phase 2 und 3
Phase 1 ist jetzt implementiert. Sie als Entwickler können bei Phase 2 helfen. Die bevorstehenden Herausforderungen sind interessant. PCI DSS erfordert wie andere Standards zusätzliche Funktionen des Verschlüsselungssystems. Unser System sollte in der Lage sein, den Hauptschlüssel zu ändern. Zum Beispiel, wenn er kompromittiert wurde oder die Zeit gerade in Übereinstimmung mit der Sicherheitsrichtlinie gekommen ist. Jetzt weiß Ignite nicht wie. In zukünftigen Versionen werden wir TDE jedoch beibringen, den Hauptschlüssel zu ändern.
Das Gleiche gilt für die Möglichkeit, den Cache-Schlüssel zu ändern, ohne den Cluster anzuhalten und mit Daten zu arbeiten. Wenn der Cache langlebig ist und gleichzeitig einige finanzielle und medizinische Daten speichert, sollte Ignite in der Lage sein, den Cache-Verschlüsselungsschlüssel zu ändern und alles im laufenden Betrieb neu zu verschlüsseln. Wir werden dieses Problem in der dritten Phase lösen.
Total: Wie implementiere ich ein großes Feature in einem Open Source-Projekt?
Zusammenfassend. Sie sind für jede Open Source relevant. Ich habe an Kafka und anderen Projekten teilgenommen - überall ist die Geschichte dieselbe.
- Beginnen Sie mit kleinen Aufgaben. Versuchen Sie niemals, ein sehr großes Problem sofort zu lösen. Es ist notwendig zu verstehen, was passiert, wie es passiert, wie es realisiert wird. Wer wird dir helfen? Und im Allgemeinen - von welcher Seite dieses Projekt angegangen werden soll.
- Verstehe das Projekt. Normalerweise kommen alle Entwickler - zumindest ich - und sagen: Alles muss neu geschrieben werden. Vor mir war alles schlecht, und jetzt werde ich es umschreiben - und alles wird gut. Es ist ratsam, solche Aussagen zu verschieben, um herauszufinden, was genau schlecht ist und ob es geändert werden muss.
- Besprechen Sie, ob Verbesserungen erforderlich sind. Ich hatte Fälle, in denen ich mit Erfahrung in die verschiedenen Gemeinden kam, zum Beispiel in Spark. Er sagte es mir, aber die Community war aus irgendeinem Grund nicht interessiert. Auf jeden Fall passiert es. Sie brauchen diese Überarbeitung, aber die Community sagt: Nein, wir sind nicht interessiert, wir werden nicht fusionieren und helfen.
- Mach ein Design. Es gibt Open Source-Projekte, bei denen dies obligatorisch ist. Sie können nicht mit dem Codieren beginnen, ohne ein vom Komitee und erfahrenen Personen vereinbartes Design. In Ignite ist dies formal nicht wahr, aber im Allgemeinen ist es ein wichtiger Teil der Entwicklung. Je nach Projekt ist eine Beschreibung in kompetentem Englisch oder Russisch erforderlich. Damit der Text gelesen werden kann und klar war, was genau Sie tun werden.
- Besprechen Sie die öffentliche API. Das Hauptargument: Wenn es eine schöne und verständliche öffentliche API gibt, die einfach zu verwenden ist, ist das Design korrekt. Diese Dinge sind normalerweise nebeneinander.
Weitere offensichtliche Tipps, die nicht so einfach zu befolgen sind:
- Implementieren Sie die Funktion, ohne etwas zu beschädigen. Mach die Tests.
- Fragen Sie und warten Sie (dies ist am schwierigsten) auf eine Bewertung von den richtigen Leuten, von den richtigen Mitgliedern der Community.
- Machen Sie Benchmarks und finden Sie heraus, ob Sie einen Leistungsabfall haben. Dies ist besonders wichtig, wenn einige kritische Subsysteme fertiggestellt werden.
- Warten Sie auf die Zusammenführung, machen Sie einige Beispiele und Dokumentationen.
Danke fürs Lesen!