Eu tenho trabalhado em um projeto pessoal em C # cujo objetivo é mais ou menos permitir ao usuário executar scripts escritos por outros usuários e restringir as permissões desse script. Meu programa compila os scripts usando uma biblioteca de terceiros, coloca-os na caixa de areia usando os mecanismos do .NET Code Access Security e garante que eles tenham apenas as permissões que o usuário deseja conceder.
Em termos gerais, meus requisitos de segurança são:
- o usuário deve poder restringir o acesso do script não confiável a apenas certas partes do sistema de arquivos, incluindo a proibição de todo acesso ao sistema de arquivos
- o usuário deve poder restringir as conexões de rede do script não confiável a apenas determinados endereços IP ou nomes de host, incluindo a proibição de todas as conexões de rede
- tudo bem se o script do usuário conseguir travar ou encerrar o aplicativo host, mas o script do usuário não puder contornar as restrições de permissão (por exemplo, negação de serviço é aceitável, violação não é)
Estou pensando em tentar fazer algo semelhante em C ++, como uma espécie de exercício pessoal. Obviamente, as coisas são mais complicadas ao executar o código nativo diretamente, mesmo que os scripts do usuário sejam escritos em uma linguagem de script como Lua.
A primeira abordagem em que consigo pensar é inserir meus próprios ganchos nas funções da biblioteca padrão dos ambientes de script. Por exemplo, se a linguagem de script for Lua, em vez de expor o io.open normalmente, eu teria que expor um wrapper que verificasse os argumentos nas permissões do script antes de passá-los para a implementação original.
Minha preocupação com essa abordagem é que ela aumenta drasticamente a quantidade de meu próprio código responsável pela segurança e, portanto, uma potencial vulnerabilidade de segurança que eu mesmo escrevi. Em outras palavras, ao trabalhar com o .NET CAS, posso confiar que a Microsoft fez seu trabalho bem no código de área restrita, em vez de precisar confiar no meu próprio código de área restrita.
Existem alternativas que não conheço?
Respostas:
Como outros já declararam, a execução de código estrangeiro é o maior problema nesse tipo de implementação. Como o comentário de Kain0_0 sugeriu, uma VM seria a maneira mais apropriada de manter a liberdade de código externo sem comprometer a máquina host (demais). Isso é basicamente o que os serviços de Integração Contínua como o CircleCI fazem.
Isso também facilita muito a implementação da interface, pois você pode obter imagens com todos os recursos de configuração e segurança desejados. Você também não precisa se preocupar se o código deles conseguirá ser executado em seu host.
Então, para isso, eu:
Faça instantâneos dos ambientes de script do usuário que quero cobrir com o Docker (um ambiente para C #, outro para Python, etc, com as configurações de segurança apropriadas)
Mediante solicitação do usuário, gire as instâncias relevantes do Docker pelo gatilho de código, injetando o script externo no ponto de entrada da instância do Docker.
O código é executado na instância do Docker com permissões de usuário, os arquivos são gravados, talvez uma conexão seja feita aqui e ali, obtenha a saída e o ambiente seja destruído
Como os contêineres do Docker são executados como processos, eles podem ser finalizados com bastante facilidade, ou seja, se excederem um determinado limite de tempo. Se houver um erro, eles podem terminar imediatamente.
Basicamente, faça com que seu código principal gerencie a lógica de acionamento do usuário, injeção de ponto de entrada e destruição automática do Docker, que é responsável por fazer o sandbox para esse tipo de operação.
fonte