TDE en Apache Ignite: una historia importante en un gran proyecto de código abierto

Muchas organizaciones, especialmente las financieras, tienen que lidiar con varios estándares de seguridad, por ejemplo, PCI DSS. Dichas certificaciones requieren cifrado de datos. Cifrado de datos transparente en el disco El cifrado de datos transparente se implementa en muchos DBMS industriales.

Apache Ignite se usa en bancos, por lo tanto, se decidió implementar TDE en él.

Describiré cómo desarrollamos TDE a través de la comunidad, públicamente, a través de los procesos de Apachev.


A continuación se muestra una versión de texto del informe:

Trataré de hablar sobre arquitectura, sobre la complejidad del desarrollo, cómo se ve realmente en código abierto.

¿Qué se ha hecho y qué queda por hacer?


Actualmente implementado Apache Ignite TDE. Fase 1

Incluye las características básicas de trabajar con cachés cifradas:

  • Gestión de claves
  • Crear cachés cifrados
  • Guardar todos los datos de caché en el disco en forma cifrada

En la Fase 2, se planea habilitar la posibilidad de rotación (cambio) de la clave maestra.
En la Fase 3, la capacidad de rotar claves de caché.

Terminología


  • Cifrado de datos transparente: cifrado de datos transparente (para el usuario) cuando se guarda en el disco. En el caso de Ignite, cifrado de caché, porque Ignite se trata de cachés.
  • Encender caché: caché de valor clave en Apache Ignite. Los datos de caché se pueden guardar en el disco
  • Páginas: páginas de datos. En Ignite, todos los datos están paginados. Las páginas se escriben en el disco y deben estar encriptadas.
  • WAL: escribe el registro con antelación. Todos los cambios de datos en Ignite se guardan allí, todas las acciones que realizamos para todos los cachés.
  • Keystore: almacén de claves estándar de Java, generado por Keytool Javascript. Funciona y está certificado en todas partes, lo usamos.
  • Master key - llave maestra. Al usarlo, las claves de las tablas se cifran, las claves de cifrado de caché. Almacenado en el almacén de claves de Java.
  • Claves de caché: claves con las que los datos se cifran realmente. Junto con la clave maestra, se obtiene una estructura de dos niveles. La clave maestra se almacena por separado del caché de claves y los datos maestros, por motivos de seguridad, separación de derechos de acceso, etc.

Arquitectura


Todo se implementa de acuerdo con el siguiente esquema:

  • Todos los datos de caché se cifran con la nueva SPI de cifrado.
  • Por defecto, se utiliza AES, un algoritmo de cifrado industrial.
  • La clave maestra se almacena en un archivo JKS, un archivo estándar de Java para claves.

Los bancos y otras organizaciones usan sus propios algoritmos de cifrado: GOST y otros. Está claro que hemos brindado la oportunidad de deslizar nuestro SPI de cifrado, la implementación de cifrado que un usuario específico necesita.

Esquema de trabajo


imagen

Entonces, tenemos RAM - memoria de acceso aleatorio con páginas que contienen datos puros. El uso de RAM implica que no estamos protegidos de un pirata informático que obtuvo acceso de root y descargó toda la memoria. Nos protegemos del administrador que toma el disco duro y lo vende en el mercado de Tushino (o donde actualmente se venden datos similares).

Además de las páginas con un caché, los datos también se almacenan en el registro de escritura anticipada, que escribe en el disco el delta de los registros modificados en la transacción. El metastore almacena claves de cifrado de caché. Y en un archivo separado: una clave maestra.

Cada vez que se crea una clave para el caché, antes de escribir o transferir a la red, encriptamos esta clave utilizando una clave maestra. Para que nadie pueda obtener la clave de caché después de recibir los datos de Ignite. Solo robando la clave maestra y los datos puede acceder a ellos. Esto es poco probable, ya que el acceso a estos archivos requiere varios derechos.

El algoritmo de acciones es el siguiente:

  • Al comienzo del nodo, reste la clave maestra de jks.
  • Al comienzo de los nodos, lea la meta-tienda y descifre las claves de caché.
  • Cuando une nodos en un clúster:
    - Verifique los hashes de la clave maestra.
    - Revise las claves para cachés compartidos.
    - Guardar claves para nuevas cachés.
  • Al crear una memoria caché de forma dinámica, generamos una clave y la guardamos en la meta tienda.
  • Al leer / escribir una página, la desciframos / ciframos.
  • Cada entrada WAL para el caché encriptado también está encriptada.

Ahora con más detalle:

Al comienzo del nodo, tenemos una devolución de llamada que inicia nuestro EncryptionSPI. De acuerdo con los parámetros, restamos la clave maestra del archivo jks.

Luego, cuando metastore está listo, obtenemos las claves de cifrado almacenadas. En este caso, ya tenemos una clave maestra, para que podamos descifrar las claves y obtener acceso a los datos de la caché.

Por separado, hay un proceso muy interesante: cómo unimos un nuevo nodo en un clúster. Ya tenemos un sistema distribuido que consta de varios nodos. ¿Cómo asegurarse de que el nuevo nodo esté configurado correctamente, que no sea un atacante?

Realizamos estas acciones:

  • Cuando llega un nuevo nodo, envía un hash desde la clave maestra. Parece que coincide con el existente.
  • Luego verificamos las claves para cachés compartidas. Del nodo proviene el identificador de caché y la clave de caché cifrada. Los verificamos para asegurarnos de que todos los datos en todos los nodos estén encriptados con la misma clave. Si esto no es así, simplemente no tenemos derecho a dejar que el nodo entre en el clúster; de lo contrario, viajará por claves y datos.
  • Si hay nuevas claves y cachés en el nuevo nodo, guárdelas para usarlas en el futuro.
  • Al crear una memoria caché dinámicamente, se proporciona una función de generación de claves. Lo generamos, lo guardamos en la meta tienda y podemos continuar realizando las operaciones descritas.

La segunda parte es una superestructura sobre las operaciones de E / S. Las páginas se escriben en el archivo de partición. Nuestro complemento analiza qué caché de página, los encripta en consecuencia y los guarda.

Lo mismo vale para WAL. Hay un serializador que serializa objetos de registro WAL. Y si el registro es para cachés cifradas, entonces debemos cifrarlo y solo luego guardarlo en el disco.

Dificultades de desarrollo


Dificultades comunes a todos los proyectos de código abierto más o menos complejos:

  1. Primero debe comprender el dispositivo Ignite por completo. Por qué, qué y cómo se hizo allí, cómo y en qué lugares colocar sus controladores.
  2. Es necesario proporcionar compatibilidad con versiones anteriores. Esto puede ser bastante difícil, no obvio. Al desarrollar un producto que otros usan, debe tener en cuenta que los usuarios desean actualizarse sin problemas. La compatibilidad con versiones anteriores es correcta y buena. Cuando realiza una mejora tan grande como TDE, cambia las reglas para guardar en el disco, cifra algo. Y la compatibilidad con versiones anteriores debe ser trabajada.
  3. Otro punto no obvio está relacionado con la distribución de nuestro sistema. Cuando diferentes clientes intentan crear el mismo caché, debe aceptar la clave de cifrado, ya que de forma predeterminada se generarán dos diferentes. Hemos resuelto este problema. No me detendré en más detalles: la solución merece una publicación separada. Ahora estamos garantizados para usar una clave.
  4. La siguiente cosa importante condujo a grandes mejoras, cuando parecía que todo estaba listo (¿una historia familiar?) :). El cifrado tiene sobrecarga. Tenemos un vector de inicio: cero datos aleatorios que se utilizan en el algoritmo AES. Se almacenan en forma abierta, y con su ayuda aumentamos la entropía: los mismos datos se cifrarán de manera diferente en diferentes sesiones de cifrado. En términos generales, incluso si tenemos dos Ivan Petrovs con el mismo apellido, cada vez que ciframos, recibiremos diferentes datos cifrados. Esto reduce la posibilidad de piratería.

    El cifrado se realiza en bloques de 16 bytes, y si los datos no están alineados por 16 bytes, entonces agregamos información de relleno: la cantidad de datos que realmente hemos cifrado. En un disco, debe escribir una página que sea múltiplo de 2 Kb. Estos son los requisitos de rendimiento: debemos usar el búfer de disco. Si escribimos no 2 Kb (no 4 o no 8, dependiendo del búfer de disco), inmediatamente obtenemos un gran rendimiento de caída.

    ¿Cómo resolvimos el problema? Tuve que arrastrarme a PageIO, en RAM y cortar 16 bytes de cada página, que se cifrarían cuando se escribieran en el disco. En estos 16 bytes escribimos el vector init.
  5. Otra dificultad es no romper nada. Esto es algo común cuando vienes y haces algunos cambios. En realidad, no es tan simple como parece.
  6. En MVP resultó 6 mil líneas. Es difícil de revisar, y pocas personas quieren hacer esto, especialmente de expertos que ya no tienen tiempo. Tenemos varias partes: API pública, parte central, administradores SPI, almacén persistente de páginas, administradores WAL. Los cambios en varios subsistemas requieren que sean revisados ​​por diferentes personas. Y esto también impone dificultades adicionales. Especialmente cuando trabajas en una comunidad donde todas las personas están ocupadas con sus tareas. Sin embargo, todo funcionó para nosotros.

Lo que sucederá en TDE. Fase 2 y 3


Ahora se implementa la Fase 1. Usted, como desarrollador, puede ayudar con la Fase 2. Los desafíos futuros son interesantes. PCI DSS, como otros estándares, requiere características adicionales del sistema de encriptación. Nuestro sistema debería poder cambiar la clave maestra. Por ejemplo, si se vio comprometido o ha llegado el momento de acuerdo con la política de seguridad. Ahora Ignite no sabe cómo. Pero en futuras versiones, enseñaremos a TDE a cambiar la clave maestra.

Lo mismo con la capacidad de cambiar la clave de caché sin detener el clúster y trabajar con datos. Si el caché es de larga duración y al mismo tiempo almacena algunos datos (financieros, médicos), Ignite debería poder cambiar la clave de cifrado del caché y volver a cifrar todo sobre la marcha. Resolveremos este problema en la tercera fase.

Total: ¿Cómo implementar una gran característica en un proyecto de código abierto?


Para resumir. Serán relevantes para cualquier fuente abierta. Participé en Kafka y en otros proyectos, en todas partes la historia es la misma.

  1. Comience con pequeñas tareas. Nunca intente resolver un problema súper grande de inmediato. Es necesario comprender lo que está sucediendo, cómo está sucediendo, cómo se está realizando. ¿Quién te ayudará? Y en general, de qué lado abordar este proyecto.
  2. Comprende el proyecto. Por lo general, todos los desarrolladores, al menos yo, vienen y dicen: todo debe reescribirse. Todo estaba mal antes que yo, y ahora lo reescribiré, y todo estará bien. Es aconsejable posponer tales declaraciones, para determinar qué es exactamente malo y si es necesario cambiarlo.
  3. Discuta si se necesitan mejoras. He tenido casos en los que vine a varias comunidades con experiencia, por ejemplo, en Spark. Me lo dijo, pero la comunidad no estaba interesada por alguna razón. En cualquier caso sucede. Necesita esta revisión, pero la comunidad dice: no, no estamos interesados, no nos fusionaremos ni ayudaremos.
  4. Haz un diseño. Hay proyectos de código abierto en los que esto es obligatorio. No puede comenzar a codificar sin un diseño acordado por el comité y las personas con experiencia. En Ignite, esto no es formalmente cierto, pero en general es una parte importante del desarrollo. Es necesario hacer una descripción en inglés o ruso competente, según el proyecto. Para que el texto se pueda leer y quede claro qué es exactamente lo que va a hacer.
  5. Discuta la API pública. El argumento principal: si hay una API pública hermosa y comprensible que sea fácil de usar, entonces el diseño es correcto. Estas cosas suelen ser adyacentes entre sí.

Más consejos más obvios que no son tan fáciles de seguir:

  • Implemente la función sin romper nada. Haz las pruebas.
  • Pida y espere (esto es lo más difícil) una revisión de los tipos correctos, de los miembros correctos de la comunidad.
  • Haga puntos de referencia, descubra si tiene una caída en el rendimiento. Esto es especialmente importante al finalizar algunos subsistemas críticos.
  • Espere la fusión, haga algunos ejemplos y documentación.

Gracias por leer!

Source: https://habr.com/ru/post/440808/


All Articles