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