Skip to main content

Dicas e práticas recomendadas para criar imagens de contêiner seguras

Escrito por
blog feature build secure containers

6 de julho de 2021

0 minutos de leitura

Ao começar a analisar suas imagens de contêiner, pode ser preocupante descobrir que elas têm um grande número de vulnerabilidades. Abaixo está uma análise que fiz na semana passada de uma imagem vulnerável do Node que criei. Embora seja um exemplo bastante extremo, você pode ver que, logo de início, essa imagem apresenta mais de 800 vulnerabilidades.

Varredura de vulnerabilidades em imagem de contêiner mostrando 899 vulnerabilidades em pacotes como bzip2, curl, file, git e glib2.0

Diante disso, muitos de nós ficam paralisados, como um cervo diante dos faróis de um carro, ao se deparar com uma longa lista de CVEs, principalmente quando o foco é o desenvolvimento de aplicações, e não a administração de sistemas. O que devo fazer com essas informações? Por onde começo? Eu só queria uma imagem para executar minha aplicação Node e, de repente, já tenho essa tarefa gigantesca de torná-la segura.

O mais importante é lembrar que corrigir esses problemas em contêineres não é como corrigi-los em um sistema operacional. Não vamos abordar a atualização de pacotes individuais nem o gerenciamento do sistema inteiro. Com contêineres, precisamos entender como as vulnerabilidades chegaram às nossas imagens antes de pensar nas estratégias para corrigi-las, como a nossa lista de 10 práticas recomendadas de segurança para Docker.

O que há na minha imagem de contêiner?

A primeira coisa que vale a pena entender nesse contexto é como as imagens que usamos podem ser criadas. A menos que criemos imagens do zero, provavelmente começamos com uma imagem-base no nosso Dockerfile. Embora a chamemos de imagem-base, é provável que ela também tenha sido criada a partir de uma imagem-pai, na qual softwares foram instalados durante o processo de build. A própria imagem-pai foi criada de alguma forma, talvez a partir de outra imagem-pai ou com alguma ferramenta de criação de sistema de arquivos raiz. Entender como o software que estamos analisando chegou às nossas imagens é essencial para definir uma estratégia de redução de vulnerabilidades.

Para ver um exemplo, vamos analisar a imagem oficial do Nginx no Docker Hub. Ao examinar o Dockerfile dessa imagem, vemos que ela se baseia na imagem Debian Buster slim, à qual são adicionados softwares e configurações durante a criação da imagem do Nginx.

FROM debian:buster-slim

Por sua vez, a imagem Debian Buster é criada a partir de outro Dockerfile, que usa uma imagem scratch e adiciona um arquivo tar.

FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]

Se pesquisarmos como esse arquivo tar é criado, veremos que ele é gerado pela ferramenta debuerreotype, um conjunto de scripts usados pelo projeto Debian para criar sistemas de arquivos raiz. É assim que o Debian faz isso, mas existem diferentes métodos para criar esses componentes em outros sistemas operacionais normalmente usados como imagens-base.

O ponto é que, mesmo quando analisamos apenas nossas imagens-base, o caminho que o software percorre até chegar a elas pode ser longo e potencialmente complexo. Pode ser difícil acompanhá-lo sem entender todos esses diferentes paradigmas.

Scratch

Algumas pessoas diriam que basta usar scratch e criar suas próprias imagens do zero, começando com um sistema de arquivos vazio. Essa é uma abordagem válida em algumas situações e pode funcionar bem para binários compilados de linguagens sem dependências, como Go ou C. Mas, para a maioria dos outros casos, você ficará responsável pela manutenção de tudo o que entra na imagem, o que pode gerar uma sobrecarga contínua considerável. Se criarmos muitas imagens de contêiner diferentes, essa sobrecarga pode se tornar insustentável rapidamente — e talvez percamos, com o trabalho de manutenção, o benefício de uma superfície de ataque menor.

Confiar ou não confiar

Ao pensar no gerenciamento de vulnerabilidades em imagens, confiar na imagem-base é uma consideração fundamental. É preciso encontrar um equilíbrio entre confiar na imagem upstream e decidir assumir todo o processo de criação das imagens, responsabilizando-se por todos os softwares instalados nelas.

Como vimos, também precisamos confiar em toda a cadeia de processos de build que resultou na imagem que estamos usando, e pode ser difícil acompanhá-la com clareza. Muitas imagens em registros públicos podem ser mal criadas ou não receber manutenção. Em geral, esses registros não garantem a qualidade da maioria das imagens hospedadas neles. É claro que isso não é diferente da forma como consumimos a maior parte dos softwares de código aberto; muitos dos mesmos fatores de qualidade que influenciam nossas escolhas também se aplicam. O software recebe manutenção e atualizações regulares? Há uma comunidade ampla de usuários? Existem empresas que oferecem suporte? Todas essas informações estão disponíveis online. Portanto, pesquise com calma e descubra o que você está usando. A ferramenta Snyk Advisor é ótima para ajudar nessa investigação.

Se decidirmos confiar na imagem-base upstream, quando surgirem problemas nela, devemos buscar uma correção upstream, em vez de manter nossa própria versão derivada da imagem-base com pacotes atualizados. Por natureza, os contêineres são projetados para serem imutáveis. Se começarmos a atualizar pacotes durante o processo de build do contêiner, acabaremos comprometendo o conceito de usar uma imagem-base. Essa estratégia se tornará rapidamente inviável, pois passaremos a ser os mantenedores de fato da nossa imagem.

Mas escolher uma imagem-base nem sempre é tão simples quanto parece. Por exemplo, a imagem-base “oficial” do Python no Docker Hub tem muitas vulnerabilidades e é muito grande. Isso é bastante comum em imagens oficiais de ambientes de execução, pois, por definição, elas precisam atender a todos os casos de uso. Podemos optar pela versão slim, que é menor e tem menos vulnerabilidades, ou procurar outra imagem — mas o repositório tem muitas e muitas tags. Como escolher?

Para começar, a tag genérica latest de uma imagem de framework de linguagem provavelmente não é a melhor opção para produção. É difícil saber qual versão do framework está sendo usada, e isso pode mudar no futuro. Mas slim também não é automaticamente a melhor escolha: você pode ter menos vulnerabilidades, mas talvez precise começar a gerenciar as dependências do build.

A melhor opção é... builds em vários estágios

A prática recomendada é usar builds em vários estágios: você usa uma imagem maior e mais genérica para compilar o software e, em seguida, copia os artefatos do build para a versão slim, que será implantada em produção. Assim, não é necessário gerenciar as dependências de build, e ainda aproveitamos o tamanho reduzido e o menor número de vulnerabilidades da versão slim. Também devemos usar versões específicas do ambiente de execução para saber exatamente qual ambiente estamos usando e garantir que ele não mude inesperadamente.

Práticas recomendadas para escolher imagens-base

Veja algumas recomendações gerais para escolher imagens-base.

  • Confie em um provedor upstream para fazer o trabalho pesado e corrigir vulnerabilidades por você. Essas equipes são maiores e, por isso, têm muito mais chances de corrigir os problemas rapidamente.

  • Fixe seus aplicativos em imagens com versões específicas — no mínimo, a versão principal, mas de preferência também a secundária. Assim, você evita mudanças inesperadas no futuro.

  • Adote os builds em vários estágios. Eles permitem usar imagens slim na implantação e, ao mesmo tempo, aproveitar combinações comprovadas durante o build.

  • Reconstrua com frequência. Muitas vezes, o processo de build já incorpora correções de segurança.

  • Considere atualizar de tempos em tempos. As novas versões também trazem mais correções de segurança.

Sempre analise suas imagens em busca de vulnerabilidades

Ao analisar suas imagens e seus Dockerfiles com o Snyk, você descobre quais imagens-base alternativas pode usar para reduzir o número total de vulnerabilidades. O Snyk também pode criar pull requests automaticamente nos seus Dockerfiles para trocar a imagem-base. E o melhor: você pode usar de graça.

Painel de segurança de contêineres que mostra detalhes de uma imagem Docker e recomendações para atualizar a imagem base, com a quantidade de vulnerabilidades por nível de gravidade.

Na parte 2 desta série do blog, vamos analisar os softwares que adicionamos às imagens-base e como começar a pensar em corrigir as vulnerabilidades encontradas neles. Volte em breve ou siga @snyksec no Twitter para saber quando o artigo for publicado.

Segurança de contêineres que prioriza os desenvolvedores

O Snyk encontra e corrige automaticamente vulnerabilidades em imagens de contêiner e workloads do Kubernetes.