Ei. Sou Mrinal Wadhwa, CTO da Ockam . Nos primeiros dias de Ockam, estávamos desenvolvendo uma biblioteca C. Esta é a história de por que, muitos meses depois, decidimos abandonar dezenas de milhares de linhas de C e reescrever em Rust .
Antes de começarmos, participei de um webinar gravado esta semana junto com Paul Dix, o CTO da InfluxData, onde ambos discutimos as reescritas do InfluxDB e do Ockam em Rust. Por que os dois projetos de código aberto optaram por reescrever, por que escolhemos Rust como nossa nova linguagem, lições que aprendemos ao longo do caminho e muito mais. Confira a gravação . Foi uma discussão esclarecedora.
Ockam permite que os desenvolvedores criem aplicativos que podem confiar em dados em movimento. Oferecemos a você ferramentas simples para adicionar comunicação criptografada ponta a ponta e mutuamente autenticada - qualquer aplicativo em execução em qualquer ambiente. Seus aplicativos obtêm garantias de ponta a ponta de integridade, autenticidade e confidencialidade dos dados… em redes privadas, entre várias nuvens, por meio de fluxos de mensagens em Kafka – em qualquer topologia multi-hop e multiprotocolo. Toda a comunicação se torna autenticada de ponta a ponta e privada.
Também tornamos as partes difíceis superfáceis de dimensionar - relacionamentos de confiança de inicialização, gerenciamento seguro de chaves, rotação/revogação de credenciais de curta duração, aplicação de políticas de autorização baseadas em atributos, etc. O resultado final é - você pode criar aplicativos que tenham controle granular sobre cada decisão de confiança e acesso - aplicativos que são privados e seguros por design.
Em 2019, começamos a construir tudo isso em C. Queríamos que o Ockam fosse executado em qualquer lugar - de dispositivos de ponta limitados a poderosos servidores em nuvem. Também queríamos que o Ockam pudesse ser usado em qualquer tipo de aplicativo - independentemente do idioma em que o aplicativo foi criado.
Isso fez de C um candidato óbvio. Ele pode ser compilado para 99% dos computadores e praticamente executado em qualquer lugar (uma vez que você descubra como lidar com todas as cadeias de ferramentas específicas do alvo). E todas as outras linguagens populares podem chamar bibliotecas C por meio de alguma forma de interface de função nativa - para que possamos fornecer wrappers idiomáticos de linguagem para todas as outras linguagens: Typescript, Python, Elixir, Java, etc.
A ideia era manter o núcleo de nossos protocolos centrados na comunicação dissociados de qualquer comportamento específico de hardware e ter adaptadores conectáveis para o hardware que queremos oferecer suporte. Por exemplo, haveria adaptadores para armazenar chaves secretas em vários HSMs, adaptadores para vários protocolos de transporte, etc.
Nosso plano era implementar nosso núcleo como uma biblioteca C. Em seguida, agruparíamos essa biblioteca C com wrappers para outras linguagens e a executaríamos em qualquer lugar com a ajuda de adaptadores de hardware conectáveis.
Mas também nos preocupamos profundamente com a simplicidade - está em nosso nome. Queremos que o Ockam seja simples de usar, simples de construir e simples de manter.
No núcleo do Ockam está uma pilha em camadas de protocolos criptográficos e baseados em mensagens, como Ockam Secure Channels e Ockam Routing . Esses são protocolos de comunicação assíncronos, com várias etapas e com estado, e queríamos abstrair todos os detalhes desses protocolos dos desenvolvedores de aplicativos. Imaginamos que a experiência do usuário seria uma única chamada de função de uma linha para criar um canal seguro autenticado e criptografado de ponta a ponta.
O código relacionado à criptografia também tende a ter muitos footguns, um pequeno passo em falso e seu sistema se torna inseguro. Portanto, a simplicidade não é apenas um ideal estético para nós, mas também um requisito crucial para garantir que possamos capacitar todos a criar sistemas seguros. Conhecer o âmago da questão de cada protocolo envolvido não deve ser necessário. Queríamos esconder esses footguns e fornecer interfaces de desenvolvedor que fossem fáceis de usar corretamente e impossíveis/difíceis de usar de uma maneira que disparasse seu aplicativo no pé.
É aí que C estava faltando severamente.
Nossas tentativas de expor interfaces seguras e simples, em C, não tiveram sucesso. Em cada iteração, descobrimos que os desenvolvedores de aplicativos precisariam saber muitos detalhes sobre o estado do protocolo e as transições de estado.
Naquela época, escrevi um protótipo de criação de um Ockam Secure Channel sobre o Ockam Routing no Elixir.
Os programas Elixir são executados no BEAM, a Erlang Virtual Machine. BEAM fornece processos Erlang. Processos Erlang são atores simultâneos com estado leve. Como os atores podem ser executados simultaneamente, mantendo o estado interno, tornou-se fácil executar uma pilha simultânea de protocolos com estado Ockam Transports + Ockam Routing + Ockam Secure Channels.
Consegui ocultar todas as camadas com estado e criar uma função simples de uma linha que alguém pode chamar para criar um canal seguro criptografado de ponta a ponta em uma variedade de rotas multi-hop e multi-protocolo.
{:ok, channel} = Ockam.SecureChannel.create(route, vault, keypair)
Um desenvolvedor de aplicativo invocaria essa função simples e vários atores simultâneos executariam as camadas subjacentes de protocolos com estado. A função retornaria quando o canal fosse estabelecido ou se houvesse um erro. Isso é exatamente o que queríamos em nossa interface.
Mas o Elixir não é como o C. Ele não roda tão bem em computadores pequenos/restringidos e não é uma boa escolha por ser empacotado em wrappers idiomáticos específicos da linguagem.
Neste ponto, sabíamos que queríamos implementar atores leves, mas também sabíamos que C não facilitaria isso. Foi quando comecei a pesquisar Rust e rapidamente encontrei algumas coisas que o tornaram muito atraente:
As bibliotecas Rust podem exportar uma interface compatível com a convenção de chamada do C. Isso significa que qualquer linguagem ou tempo de execução que possa vincular e chamar funções estática ou dinamicamente em uma biblioteca C também pode vincular e chamar funções em uma biblioteca Rust - exatamente da mesma maneira. Como a maioria das linguagens oferece suporte a funções nativas em C, elas também já oferecem suporte a funções nativas em Rust. Isso tornou Rust igual a C do ponto de vista de nossa exigência de ter wrappers específicos de linguagem em nossa biblioteca principal.
Rust compila usando LLVM, o que significa que pode atingir um número muito grande de computadores. Este conjunto provavelmente não é tão grande quanto tudo o que C pode atingir com o GCC e vários forks proprietários do GCC, mas ainda é um subconjunto muito grande e há trabalho em andamento para fazer o Rust compilar com o GCC. Com o crescente suporte de novos alvos LLVM e potencial suporte GCC em Rust, parecia uma boa aposta do ponto de vista de nossa exigência de poder rodar em qualquer lugar.
O sistema de tipos do Rust nos permite transformar invariantes em erros de tempo de compilação. Isso reduz o conjunto de possíveis erros que podem ser enviados para produção, tornando-os mais fáceis de detectar no momento do desenvolvimento. Nossa equipe e o usuário de nossa biblioteca Rust tornam-se menos propensos a enviar bugs comportamentais ou vulnerabilidades de segurança para produção.
Os recursos de segurança de memória do Rust eliminam a possibilidade de uso após liberações, liberações duplas, estouros, acesso fora dos limites, corridas de dados e muitos outros erros comuns que são conhecidos por causar 60-70% das vulnerabilidades de alta gravidade em grandes C ou bases de código C++. Rust fornece essa segurança em tempo de compilação sem incorrer nos custos de desempenho de gerenciamento seguro de memória em tempo de execução usando um coletor de lixo. Isso dá ao Rust uma grande vantagem para escrever código que precisa ser de alto desempenho, executado em ambientes restritos e altamente seguro.
A peça final que me convenceu de que Rust é uma ótima opção para Ockam foi async/await
. Já havíamos identificado que precisamos de atores leves para criar uma interface simples e segura pilha de protocolos de Ockam. async/await
significava que muito do trabalho duro para criar atores já havia sido feito em projetos como tokio e async-std. Poderíamos construir a implementação do ator de Ockam sobre essa base.
Outro aspecto importante que se destacou foi que async/await
em ferrugem tem uma diferença significativa de async/await
em outras linguagens como Javascript. Em Javascript, um mecanismo de navegador ou nodejs escolhe a maneira como executará as funções assíncronas. Mas no Rust você pode conectar um mecanismo de sua própria escolha. Eles são chamados de tempos de execução assíncronos - tokio é um exemplo popular de tempo de execução otimizado para sistemas altamente escaláveis. Mas nem sempre temos que usar o tokio, podemos escolher um tempo de execução assíncrono otimizado para pequenos dispositivos incorporados ou microcontroladores.
Isso significava que a implementação do ator de Ockam, que mais tarde chamamos de Ockam Workers , se a basearmos no async/await
de Rust, pode apresentar exatamente a mesma interface para nossos usuários, independentemente de onde esteja sendo executado - computadores grandes ou minúsculos. Todas as nossas interfaces de protocolo que ficariam em cima do Ockam Workers também podem apresentar exatamente a mesma interface simples - independentemente de onde estão sendo executadas.
Nesse ponto, estávamos convencidos de que deveríamos reescrever Ockam in Rust. Na conversa do webinar , que mencionei anteriormente, Paul Dix e eu discutimos como seria a transição para nossas equipes em Ockam e InfluxDB depois que cada projeto decidiu mudar para Rust. Discutimos como o InfluxDB mudou de Go para Rust e como Ockam mudou de C para Rust. Caso você esteja interessado, nessa parte de nossa jornada, ouça a gravação .
Muitas iterações depois, qualquer um agora pode usar a caixa Ockam em ferrugem para criar um canal seguro criptografado de ponta a ponta e mutuamente autenticado com uma simples chamada de função.
Aqui está aquela única linha que havíamos imaginado quando começamos:
let channel = node.create_secure_channel(&identity, route, options).await?;
Ele cria um canal autenticado e criptografado em rotas arbitrárias de vários saltos e vários protocolos que podem se estender por redes privadas e nuvens. Somos capazes de esconder toda a complexidade subjacente e armas de pé por trás dessa chamada de função simples e segura. O código permanece o mesmo independentemente de como você o usa - em servidores escaláveis ou minúsculos microcontroladores.
Para saber mais, confira kout Ockam no GitHub ou experimente as instruções passo a passo da biblioteca Ockam Rust e do Ockam Command .
Também publicado aqui.
Se você fez parte de um projeto que foi reescrito em Rust, venha compartilhar a história da sua equipe conosco em nosso servidor do Discord . Também estamos contratando para as funções Rust e Elixir, junte -se à nossa equipe de construtores - estamos em uma missão para tornar o software mais seguro e privado por design.