Por que os processos nos contêineres do Docker não devem ser executados como root

Publicados: 2022-08-19

Gráfico mostrando o logotipo do Docker

Os processos em um contêiner do Docker não devem ser executados como root. É mais seguro executar seus aplicativos como um usuário não root que você especifica como parte de seu Dockerfile ou ao usar docker run . Isso minimiza o risco apresentando uma superfície de ataque reduzida para quaisquer ameaças em seu contêiner.

Neste artigo, você aprenderá sobre os perigos de executar aplicativos em contêiner como root. Você também verá como criar um usuário não root e configurar o namespace em situações em que isso não for possível.

Por que a execução como root é perigosa?

Os contêineres são executados como root por padrão. O daemon do Docker é executado como root em seu host e os contêineres em execução também serão root.

Embora possa parecer que o root dentro do contêiner é um usuário independente, na verdade é o mesmo que a conta root em seu host. A separação é fornecida apenas pelos mecanismos de isolamento de contêiner do Docker. Não há limites físicos fortes; outro processo do seu contêiner executado pelo usuário root no kernel do seu host. Isso significa que uma vulnerabilidade em seu aplicativo, no tempo de execução do Docker ou no kernel do Linux pode permitir que invasores saiam do contêiner e executem operações com privilégios de root em sua máquina.

Existem algumas proteções internas que diminuem o risco de isso acontecer. A raiz dentro do contêiner não tem privilégios e tem recursos restritos. Isso impede que o contêiner use comandos de administração do sistema, a menos que você adicione recursos manualmente ou use o modo privilegiado ao iniciar seus contêineres.

Apesar dessa mitigação, permitir que os aplicativos sejam executados como root continua sendo um perigo. Assim como você restringiria o uso de root em um ambiente tradicional, não é aconselhável usá-lo desnecessariamente em seus contêineres. Você está fornecendo um ambiente com privilégios excessivos que oferece aos invasores mais espaço no caso de ocorrer uma violação.

Executando aplicativos em contêiner como um usuário não raiz

É uma prática recomendada que os aplicativos em contêiner sejam executados como um usuário comum. A maioria dos softwares não precisa de acesso root, portanto, alterar o usuário fornece uma camada imediata de defesa contra a quebra de contêineres.

Você deve criar uma nova conta de usuário como um dos estágios finais em seu Dockerfile. Você pode conseguir isso com a instrução USER :

 DA imagem base: mais recente
RUN apt install demo-package
USER demo-usuário:demo-group
ENTRYPOINT ["demo-binário"]

Os contêineres iniciados a partir desta imagem serão executados como demo-user . O usuário será um membro do demo-group group. Você pode omitir o nome do grupo se não precisar que o usuário esteja em um grupo:

 USER usuário de demonstração

Você pode especificar um ID de usuário (UID) e um ID de grupo (GID) em vez de nomes:

 USUÁRIO 950:950

Alocar um UID e GID conhecido geralmente é a maneira mais segura de proceder. Ele impede que o usuário no contêiner seja mapeado para uma conta de host com privilégios excessivos.

USER geralmente é especificado como o penúltimo estágio em um Dockerfile. Isso significa que você ainda pode executar operações que exigem root anteriormente na compilação da imagem. A instrução de apt install no exemplo acima tem uma necessidade legítima de root. Se a instrução USER fosse colocada acima dela, o apt seria executado como demo-user que não teria as permissões necessárias. Como as instruções do Dockerfile se aplicam apenas a compilações de imagem, não executando contêineres, é seguro deixar a alteração do usuário para mais tarde em seu Dockerfile.

Alterar o usuário que seu contêiner executa pode exigir que você atualize as permissões nos arquivos e pastas que ele acessa. Defina a propriedade em qualquer caminho que será usado pelo seu aplicativo:

 COPIAR initial-config.yaml /data/config.yaml

USER demo-usuário:demo-group
RUN chown demo-user:demo-group /data

Neste exemplo, o diretório /data precisa ser de propriedade demo-user para que o aplicativo possa fazer alterações em seu arquivo de configuração. A instrução COPY anterior terá copiado o arquivo como root. Uma abreviação está disponível usando o sinalizador --chown com copy :

 COPY --chown=demo-user:demo-group initial-config.yaml /data/config.yaml

Alterando o usuário ao iniciar um contêiner

Embora você possa alterar facilmente o usuário em seus próprios Dockerfiles, muitos aplicativos de terceiros continuam sendo executados como root. Você pode reduzir o risco associado ao uso deles definindo o sinalizador --user sempre que chamar o docker run . Isso substitui o usuário definido no Dockerfile da imagem.

 $ docker run -d --user demo-user:demo-group demo-image:latest
$ docker run -d --user demo-user demo-image:latest
$ docker run -d --user 950:950 demo-image:latest

O sinalizador --user executa o processo do contêiner como o usuário especificado. É menos seguro do que a instrução Dockerfile USER porque você precisa aplicá-la individualmente a cada comando de docker run . Uma opção melhor para imagens usadas regularmente é criar sua própria imagem derivada que pode definir uma nova conta de usuário:

 FROM image-that-run-as-root:latest
USER usuário de demonstração
 $ docker build . -t image-that-now-run-as-non-root:latest

Alterar o usuário de uma imagem de terceiros pode causar problemas: se o contêiner espera ser executado como root ou precisa acessar os caminhos do sistema de arquivos pertencentes ao root, você verá erros ao usar o aplicativo. Você pode tentar alterar manualmente as permissões nos caminhos que causam problemas. Como alternativa, verifique se o fornecedor tem um método compatível para executar o aplicativo com uma conta de usuário sem privilégios.

Manipulando aplicativos que precisam ser executados como root

O namespace do usuário é uma técnica para lidar com aplicativos que precisam de alguns privilégios de root. Ele permite mapear o root dentro de um container para um usuário não root em seu host. A raiz simulada dentro do contêiner tem os privilégios necessários, mas uma quebra não fornecerá acesso root ao host.

O remapeamento de namespace é ativado adicionando um campo userns-remap ao seu arquivo /etc/docker/daemon.json :

 {
    "userns-remap": "padrão"
}

Usar default como o valor para userns-remap instrui o Docker a criar automaticamente um novo usuário em seu host chamado dockremap . A raiz dentro dos contêineres será mapeada de volta para o dockremap em seu host. Opcionalmente, você pode especificar um usuário e grupo existente, usando uma combinação de UID/GID ou nome de usuário/nome de grupo:

 {
    "userns-remap": "demo-user"
}

Reinicie o daemon do Docker após aplicar sua alteração:

 $ sudo service docker restart

Se você estiver usando nsuser-remap: default , o usuário dockremap agora deve existir em seu host:

 $ id dockremap

uid=140(dockremap) gid=119(dockremap) groups=119(dockremap)

O usuário também deve aparecer nos arquivos de ID subordinados /etc/subuid e /etc/subgid :

 $ dockremap:231500:65535

O usuário recebeu um intervalo de 65.535 IDs subordinados a partir de 231500. No namespace do usuário, o ID 231500 é mapeado para 0 , tornando-o o usuário root em seus contêineres. Sendo um UID de número alto, 231500 não tem privilégios no host, portanto, os ataques de fuga de contêiner não poderão causar tantos danos.

Todos os contêineres que você iniciar serão executados usando o namespace de usuário remapeado, a menos que você desative com docker run --userns=host . O mecanismo funciona criando diretórios com namespace dentro /var/lib/docker que são de propriedade do UID e GID subordinados do usuário com namespace:

 $ sudo ls -l /var/lib/docker/231500.231500

total de 14
drwx------ 5 231500 231500 13 jul 22 19:00 aufs
drwx------ 3 231500 231500 13 jul 22 19:00 contêineres
...

O namespace do usuário é uma maneira eficaz de aumentar o isolamento do contêiner, evitar interrupções e preservar a compatibilidade com aplicativos que precisam de privilégios de root. No entanto, existem algumas desvantagens: o recurso funciona melhor em uma nova instância do Docker, os volumes montados a partir do host devem ter suas permissões ajustadas e alguns drivers de armazenamento externos não oferecem suporte ao mapeamento de usuários. Você deve revisar a documentação antes de adotar esta opção.

Resumo

A execução de aplicativos em contêiner como root é um risco de segurança. Embora fácil de ignorar, o isolamento fornecido pelos contêineres não é forte o suficiente para separar totalmente os usuários do kernel dos usuários do contêiner. Root no contêiner é o mesmo que root em seu host, portanto, um comprometimento bem-sucedido pode fornecer controle de sua máquina.

Como autor de imagem, você deve incluir a instrução USER em seu Dockerfile para que seu aplicativo seja executado sem root. Os usuários de imagem podem substituir isso com docker run --user para atribuir um UID e GID específicos. Isso ajuda a atenuar os casos em que a imagem normalmente usa root.

Você pode aumentar ainda mais a segurança descartando todos os recursos do contêiner usando --cap-drop=ALL e, em seguida, colocando na lista de permissões aqueles que são necessários com os sinalizadores --cap-add . A combinação dessas técnicas executará seu aplicativo como um usuário não root com o conjunto mínimo de privilégios necessários, melhorando sua postura de segurança.