paint-brush
Por qué reescribimos Ockam en Rushpor@ockam
8,141 lecturas
8,141 lecturas

Por qué reescribimos Ockam en Rush

por Ockam7m2023/06/25
Read on Terminal Reader

Demasiado Largo; Para Leer

Mrinal Wadhwa, CTO de Ockam, habla sobre por qué reescribieron su código en Rust. También analiza por qué eligieron Rust como su nuevo lenguaje y las lecciones que aprendieron en el camino. Mrinal y Paul Dix, el CTO de InfluxData, también analizan sus reescrituras en Rust en un seminario web.
featured image - Por qué reescribimos Ockam en Rush
Ockam HackerNoon profile picture
0-item

Hola. Soy Mrinal Wadhwa, CTO en Ockam . En los primeros días de Ockam, estábamos desarrollando una biblioteca C. Esta es la historia de por qué, muchos meses después, decidimos abandonar decenas de miles de líneas de C y reescribir en Rust .


Antes de comenzar, estuve en un seminario web grabado esta semana junto con Paul Dix, el CTO de InfluxData, donde ambos discutimos las reescrituras de InfluxDB y Ockam en Rust. Por qué los dos proyectos de código abierto eligieron reescribir, por qué elegimos Rust como nuestro nuevo lenguaje, las lecciones que aprendimos en el camino y más. Echa un vistazo a la grabación . Fue una discusión profunda.

Ockam permite a los desarrolladores crear aplicaciones que pueden confiar en los datos en movimiento. Le brindamos herramientas simples para agregar comunicación cifrada y mutuamente autenticada de extremo a extremo a cualquier aplicación que se ejecute en cualquier entorno. Sus aplicaciones obtienen garantías de extremo a extremo de integridad, autenticidad y confidencialidad de los datos... a través de redes privadas, entre múltiples nubes, a través de flujos de mensajes en Kafka , en cualquier topología de múltiples protocolos y saltos múltiples. Toda la comunicación se vuelve privada y autenticada de extremo a extremo.


También hacemos que las partes difíciles sean muy fáciles de escalar: iniciar relaciones de confianza, administrar claves de forma segura, rotar/revocar credenciales de corta duración, aplicar políticas de autorización basadas en atributos, etc. El resultado final es que puede crear aplicaciones que tienen un control granular sobre cada decisión de confianza y acceso: aplicaciones que son privadas y seguras por diseño.


En 2019, comenzamos a construir todo esto en C. Queríamos que Ockam se ejecutara en todas partes, desde dispositivos periféricos restringidos hasta potentes servidores en la nube. También queríamos que Ockam se pudiera utilizar en cualquier tipo de aplicación, independientemente del idioma en el que esté integrada la aplicación.


Esto convirtió a C en un candidato obvio. Se puede compilar para el 99 % de las computadoras y ejecutar prácticamente en todas partes (una vez que descubras cómo lidiar con todas las cadenas de herramientas específicas del objetivo). Y todos los demás lenguajes populares pueden llamar a las bibliotecas C a través de alguna forma de interfaz de función nativa, por lo que luego podríamos proporcionar contenedores idiomáticos de lenguaje para todos los demás lenguajes: Typescript, Python, Elixir, Java, etc.


La idea era mantener el núcleo de nuestros protocolos centrados en la comunicación desvinculado de cualquier comportamiento específico del hardware y tener adaptadores enchufables para el hardware que queremos admitir. Por ejemplo, habría adaptadores para almacenar claves secretas en varios HSM, adaptadores para varios protocolos de transporte, etc.


Nuestro plan era implementar nuestro núcleo como una biblioteca C. Luego envolveríamos esta biblioteca C con contenedores para otros idiomas y la ejecutaríamos en todas partes con la ayuda de adaptadores de hardware conectables.


Interfaces simples y seguras

Pero también nos preocupamos profundamente por la simplicidad: está en nuestro nombre. Queremos que Ockam sea simple de usar, simple de construir y simple de mantener.


El núcleo de Ockam es una pila en capas de protocolos criptográficos y basados en mensajes, como Ockam Secure Channels y Ockam Routing . Estos son protocolos de comunicación asincrónicos, de varios pasos y con estado, y queríamos abstraer todos los detalles de estos protocolos de los desarrolladores de aplicaciones. Imaginamos la experiencia del usuario como una única llamada de función de una línea para crear un canal seguro autenticado y encriptado de extremo a extremo.


El código relacionado con la criptografía también tiende a tener muchas armas de fuego, un pequeño paso en falso y su sistema se vuelve inseguro. Por lo tanto, la simplicidad no es solo un ideal estético para nosotros, creemos que es un requisito crucial para garantizar que podamos capacitar a todos para construir sistemas seguros. No debería ser necesario conocer los detalles de cada protocolo involucrado. Queríamos ocultar estas armas de fuego y proporcionar interfaces de desarrollador que fueran fáciles de usar correctamente e imposibles/difíciles de usar de una manera que dispararía su aplicación en el pie.


Ahí es donde C estaba severamente deficiente.


Nuestros intentos de exponer interfaces seguras y simples, en C, no tuvieron éxito. En cada iteración, descubrimos que los desarrolladores de aplicaciones necesitarían conocer demasiados detalles sobre el estado del protocolo y las transiciones de estado.


El prototipo de elixir

Por esa época, escribí un prototipo para crear un canal seguro de Ockam sobre el enrutamiento de Ockam en Elixir.


Los programas de Elixir se ejecutan en BEAM, la máquina virtual de Erlang. BEAM proporciona procesos Erlang. Los procesos de Erlang son actores concurrentes ligeros y con estado. Dado que los actores pueden ejecutarse simultáneamente mientras mantienen el estado interno, resultó fácil ejecutar una pila simultánea de protocolos con estado Ockam Transports + Ockam Routing + Ockam Secure Channels.


Pude ocultar todas las capas con estado y crear una función simple de una línea a la que alguien puede llamar para crear un canal seguro cifrado de extremo a extremo a través de una variedad de rutas de múltiples protocolos y saltos múltiples.


 {:ok, channel} = Ockam.SecureChannel.create(route, vault, keypair)


Un desarrollador de aplicaciones invocaría esta función simple y múltiples actores concurrentes ejecutarían las capas subyacentes de protocolos con estado. La función regresaría cuando se establezca el canal o si hay un error. Esto es exactamente lo que queríamos en nuestra interfaz.


Pero Elixir no es como C. No funciona tan bien en computadoras pequeñas/restringidas y no es una buena opción para envolverse en envoltorios idiomáticos específicos del idioma.


Explorando el óxido

En este punto, sabíamos que queríamos implementar actores ligeros, pero también sabíamos que C no lo facilitaría. Fue entonces cuando comencé a indagar en Rust y rápidamente encontré algunas cosas que hicieron que Rust fuera muy atractivo:

Compatibilidad con la convención de llamadas C

Las bibliotecas de Rust pueden exportar una interfaz que sea compatible con la convención de llamadas de C. Esto significa que cualquier lenguaje o tiempo de ejecución que pueda vincular y llamar funciones estática o dinámicamente en una biblioteca C también puede vincular y llamar funciones en una biblioteca Rust, exactamente de la misma manera. Dado que la mayoría de los lenguajes admiten funciones nativas en C, también admiten funciones nativas en Rust. Esto hizo que Rust fuera igual a C desde la perspectiva de nuestro requisito de tener envoltorios específicos del idioma alrededor de nuestra biblioteca central.

Soporte para muchos objetivos

Rust compila usando LLVM, lo que significa que puede apuntar a una gran cantidad de computadoras. Es probable que este conjunto no sea tan grande como todo lo que C puede abordar con GCC y varias bifurcaciones GCC patentadas, pero aún es un subconjunto muy grande y hay trabajo en curso para hacer que Rust compile con GCC. Con el soporte creciente de nuevos objetivos LLVM y el soporte potencial de GCC en Rust, parecía una buena apuesta desde la perspectiva de nuestro requisito de poder ejecutarse en todas partes.

Escritura fuerte y un sistema de tipo potente

El sistema de tipos de Rust nos permite convertir invariantes en errores de tiempo de compilación. Esto reduce el conjunto de posibles errores que pueden enviarse a producción al facilitar su detección en el momento del desarrollo. Nuestro equipo y el usuario de nuestra biblioteca Rust tienen menos probabilidades de enviar errores de comportamiento o vulnerabilidades de seguridad a producción.

Seguridad de la memoria y el verificador de préstamo

Las características de seguridad de la memoria de Rust eliminan la posibilidad de uso después de liberaciones, liberaciones dobles, desbordamientos, acceso fuera de los límites, carreras de datos y muchos otros errores comunes que se sabe que causan el 60-70% de las vulnerabilidades de alta gravedad en C grandes o bases de código C++. Rust brinda esta seguridad en tiempo de compilación sin incurrir en los costos de rendimiento de administrar la memoria de manera segura en tiempo de ejecución mediante un recolector de elementos no utilizados. Esto le da a Rust una gran ventaja para escribir código que debe tener un alto rendimiento, ejecutarse en entornos restringidos y ser muy seguro.

Async/await y tiempos de ejecución asíncronos conectables

La pieza final que me convenció de que Rust encaja perfectamente con Ockam fue async/await . Ya habíamos identificado que necesitamos actores livianos para crear una interfaz simple y segura con la pila de protocolos de Ockam. async/await significó que gran parte del trabajo duro para crear actores ya se había realizado en proyectos como tokio y async-std. Podríamos construir la implementación del actor de Ockam sobre esta base.


Otro aspecto importante que se destacó fue que async/await en rust tiene una diferencia significativa con async/await en otros lenguajes como Javascript. En Javascript, un motor de navegador o nodejs elige la forma en que ejecutará las funciones asíncronas. Pero en Rust puedes conectar un mecanismo de tu elección. Estos se denominan tiempos de ejecución asíncronos: tokio es un ejemplo popular de un tiempo de ejecución de este tipo que está optimizado para sistemas altamente escalables. Pero no siempre tenemos que usar tokio, en su lugar podemos elegir un tiempo de ejecución asincrónico optimizado para pequeños dispositivos integrados o microcontroladores.


Esto significó que la implementación del actor de Ockam, que luego llamamos Ockam Workers , si la basamos en async/await de Rust puede presentar exactamente la misma interfaz a nuestros usuarios, independientemente de dónde se esté ejecutando: computadoras grandes o pequeñas. Todas nuestras interfaces de protocolo que se ubicarían encima de Ockam Workers también pueden presentar exactamente la misma interfaz simple, independientemente de dónde se estén ejecutando.


En este punto estábamos convencidos de que deberíamos volver a escribir Ockam en Rust. En la conversación del seminario web , que mencioné anteriormente, Paul Dix y yo discutimos cómo sería la transición para nuestros equipos en Ockam e InfluxDB después de que cada proyecto decidiera cambiar a Rust. Discutimos cómo InfluxDB pasó de Go a Rust y cómo Ockam pasó de C a Rust. Por si te interesa, en esa parte de nuestro viaje ve a escuchar la grabación .


Muchas iteraciones más tarde, ahora cualquiera puede usar la caja Ockam en Rust para crear un canal seguro encriptado y mutuamente autenticado de extremo a extremo con una simple llamada de función.


Aquí está esa única línea que habíamos imaginado cuando empezamos:


 let channel = node.create_secure_channel(&identity, route, options).await?;


Crea un canal autenticado y encriptado a través de rutas arbitrarias de múltiples protocolos y múltiples saltos que pueden abarcar redes privadas y nubes. Somos capaces de ocultar toda la complejidad subyacente y las armas de fuego detrás de esta llamada de función simple y segura. El código sigue siendo el mismo independientemente de cómo lo use: en servidores escalables o pequeños microcontroladores.


Para obtener más información, consulte kout Ockam en GitHub o pruebe los tutoriales paso a paso de la biblioteca Ockam Rust y Ockam Command .


También publicado aquí.


Si ha sido parte de un proyecto que fue reescrito en Rust, venga a compartir la historia de su equipo con nosotros en nuestro servidor de discordia . También estamos contratando para los roles de Rust y Elixir, únase a nuestro equipo de desarrolladores : estamos en una búsqueda para hacer que el software sea más seguro y privado por diseño.