Skip to main content

10 práticas recomendadas de segurança do Docker

Docker security best practices blog small

8 de janeiro de 2025

0 minutos de leitura

Principais aspectos da segurança do Docker

A segurança do Docker abrange aspectos de criação, execução e orquestração de contêineres Docker. Isso inclui a segurança do Dockerfile e das imagens base do Docker, além dos aspectos de segurança dos contêineres em tempo de execução, como privilégios de usuário, daemon do Docker, controles adequados de CPU para um contêiner e outras preocupações relacionadas à orquestração de contêineres Docker em escala.

O cenário de segurança dos contêineres Docker se divide em quatro problemas principais:

  • Segurança e práticas recomendadas para Dockerfiles

  • Segurança de contêineres Docker em tempo de execução

  • Riscos à segurança da cadeia de suprimentos no Docker Hub e seu impacto nas imagens de contêineres Docker

  • Aspectos de segurança da orquestração de contêineres nativos da nuvem relacionados ao Kubernetes e ao Helm

Folha de dicas da Snyk intitulada “10 práticas recomendadas para a segurança de imagens Docker”, com 10 recomendações para proteger imagens Docker.

Segurança de contêineres para DevSecOps

Encontre e corrija vulnerabilidades em contêineres gratuitamente com a Snyk.

10 práticas recomendadas de segurança do Docker

1. Prefira imagens base mínimas

Um problema comum de segurança em contêineres Docker é acabar com imagens grandes. Em geral, quanto mais software houver no contêiner, maior será a superfície de ataque potencial. Por isso, o princípio deve ser incluir apenas o mínimo necessário para executar sua aplicação. O caminho mais simples costuma ser usar uma imagem genérica de contêiner Docker para começar rapidamente. Pode ser uma imagem de sistema operacional, como Debian, ou uma imagem oficial de ambiente de execução, como Node. Se o seu projeto não precisa de bibliotecas ou utilitários gerais do sistema, é melhor evitar usar um sistema operacional completo como imagem base.

Por natureza, as imagens genéricas de ambientes de execução contêm tudo o que é necessário para executar aplicações na maioria das situações e provavelmente incluem muito software que sua aplicação não usa. A prática recomendada é aproveitar as compilações em vários estágios: você pode usar uma imagem genérica durante a compilação, com as ferramentas necessárias, e depois transferir a aplicação compilada para uma imagem enxuta, com um ambiente mínimo adequado para produção. 

Você também pode considerar distribuições especializadas para contêineres, como as imagens distroless do Google ou o Alpine Linux, que é bem menor por padrão. Para linguagens compiladas, como Go ou C, você pode até criar contêineres completamente vazios usando scratch no Dockerfile, já que esse tipo de binário pode não ter dependências externas.

2. Use um usuário com o mínimo de privilégios

Quando um Dockerfile não especifica um USER, o padrão é executar o contêiner como usuário root. Na prática, há pouquíssimos motivos para um contêiner precisar de privilégios de root, e isso pode se tornar um problema de segurança do Docker. Por padrão, o Docker executa contêineres como usuário root. Quando esse namespace é mapeado para o usuário root no contêiner em execução, o contêiner pode ter acesso de root ao host Docker. Executar uma aplicação no contêiner como root facilita a elevação de privilégios caso a própria aplicação seja vulnerável à exploração.

Para reduzir a exposição, crie um usuário e um grupo dedicados para a aplicação na imagem Docker. Use a diretiva USER no Dockerfile para garantir que o contêiner execute a aplicação com o menor nível de privilégio possível.

Lembre-se de que novos usuários ou grupos talvez não existam na imagem; crie-os usando as instruções no Dockerfile.

Veja a seguir um exemplo completo de como fazer isso com uma imagem genérica do Ubuntu:

FROM ubuntu
RUN mkdir /app
RUN groupadd -r lirantal && useradd -r -s /bin/false -g lirantal lirantal
WORKDIR /app
COPY . /app
RUN chown -R lirantal:lirantal /app
USER lirantal
CMD node index.js

O exemplo acima:

  • cria um usuário do sistema (-r) sem senha, sem diretório pessoal definido e sem shell

  • adiciona o usuário criado a um grupo existente, que criamos previamente usando groupadd

  • adiciona um argumento final com o nome do usuário que queremos criar associado ao grupo criado

Se você gosta de Node.js e imagens Alpine, elas já incluem um usuário genérico chamado node. Veja um exemplo com Node.js que usa esse usuário:

FROM node:10-alpine 
RUN mkdir /app
COPY . /app
RUN chown -R node:node /app
USER node
CMD [“node”, “index.js”]

Se você desenvolve aplicações Node.js, consulte as práticas recomendadas oficiais para Docker e Node.js.

Segurança de contêineres para DevSecOps

Encontre e corrija vulnerabilidades em contêineres gratuitamente com a Snyk.

3. Assine e verifique as imagens para reduzir ataques MITM

A autenticidade das imagens Docker é um desafio. Confiamos muito nessas imagens, pois literalmente as usamos como contêineres para executar nosso código em produção. Por isso, é fundamental garantir que a imagem que baixamos seja a mesma publicada pelo fornecedor e que ninguém a tenha alterado. A adulteração pode ocorrer durante a transmissão, entre o cliente Docker e o registro, ou por meio do comprometimento da conta do proprietário do registro para publicar uma imagem maliciosa.

A assinatura de imagens e outros artefatos relacionados é um tema fundamental para proteger sua cadeia de suprimentos de software, e as tecnologias dessa área estão amadurecendo rapidamente. Atualmente, as duas ferramentas mais usadas para isso são:

  • Docker Notary v1, que serve de base para a funcionalidade Docker Content Trust (DCT) integrada à CLI do Docker

  • O projeto SigStore, incluindo a ferramenta cosign, permite assinar, armazenar e verificar artefatos com facilidade.

Docker Content Trust

O Docker Content Trust (DCT) existe há vários anos e permite que autores de imagens assinem as tags que publicam em servidores de registro de imagens compatíveis com a API Docker Notary, como o DockerHub. Quando ativado no host Docker, o DCT impede que uma imagem seja baixada, publicada ou executada se não for possível verificar sua assinatura. 

Assinar sua imagem de contêiner com o DCT é simples: basta definir a variável de ambiente `DOCKER_CONTENT_TRUST=1`. Ao publicar sua imagem no DockerHub ou em outro registro de imagens compatível com Notary, o Docker aplica a assinatura e a armazena com a tag da imagem.

❯ export DOCKER_CONTENT_TRUST=1                                                                                                                   
❯ docker push myaccount/myimage:1                                                                                                          
The push refers to repository [docker.io/myaccount/myimage]
64ab2aa20cf9: Layer already exists 
b99937123ca0: Layer already exists 
…
orig: digest: sha256:c221d4dc80b8a0d3866602020b09722d942157c720273d325a0496a529b5fcab size: 4093
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new root key with ID 85871b5: 
Repeat passphrase for new root key with ID 85871b5: 
Enter passphrase for new repository key with ID e675351: 
Repeat passphrase for new repository key with ID e675351: 
Finished initializing "docker.io/ericsmalling/javagoof"
Successfully signed docker.io/myaccount/myimage:1

Na inicialização do contêiner, basta definir a mesma variável de ambiente para exigir a assinatura. A CLI do Docker se recusará a baixar ou iniciar uma imagem de contêiner cuja tag não tenha assinatura ou tenha uma assinatura inválida.

❯ docker run --rm -it myaccount/someapp                                                                                                docker: Error: remote trust data does not exist for docker.io/myaccount/someapp: notary.docker.io does not have trust data for docker.io/myaccount/someapp.
See 'docker run --help'.

Assuntos mais complexos, como a revogação de chaves, são tratados pela ferramenta binária notary. Para saber mais sobre o Notary v1 e o DCT, consulte a documentação oficial.

Sigstore

As ferramentas do projeto Sigstore buscam viabilizar não apenas assinaturas de imagens de contêineres, mas também de quaisquer artefatos, como uma lista de materiais de software (SBOM), atestações sobre um artefato (ou seja, metadados de compilação) ou até mesmo um arquivo binário que não seja uma imagem, ou “blob”.

No caso mais simples, assinar uma imagem é tão fácil quanto executar o binário `cosign sign` e informar o registro e a imagem:tag que você quer assinar. O comando correspondente `cosign verify` valida a assinatura.

❯ `Generate keypair (do this once):
❯ cosign generate-key-pair                                                                                                                        
Enter password for private key: 
Enter password for private key again: 
Private key written to cosign.key
Public key written to cosign.pub

❯ `Sign the already-pushed image
❯ cosign sign --key cosign.key myaccount/myimage:1                                                                                       Enter password for private key: 
tlog entry created with index: 1234567
Pushing signature to: index.docker.io/myaccount/myimage

A partir da versão 1.9, o cosign exige que você gere e gerencie seus próprios pares de chaves. Embora também ofereça o comando `generate-key-pair`, muito fácil de usar (mostrado acima), a próxima versão GA incluirá suporte a assinaturas sem chave. Esse recurso usa um mecanismo de autorização OpenID Connect (OIDC) que gera automaticamente pares de chaves efêmeras com seu endereço de e-mail como anotação. O recurso já está disponível: basta definir `COSIGN_EXPERIMENTAL=1` antes de executar os comandos `sign` ou `verify`.

❯ export COSIGN_EXPERIMENTAL=1
❯ cosign sign myaccount/myimage:1                                                                                                          
Generating ephemeral keys...
Retrieving signed certificate...

        Note that there may be personally identifiable information associated with this signed artifact.
        This may include the email address associated with the account with which you authenticate.
        This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later.
        By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs.

Are you sure you want to continue? (y/[N]): y
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=********************
Successfully verified SCT...
tlog entry created with index: 1234567
Pushing signature to: index.docker.io/myaccount/myimage

Outro ponto a considerar em relação às assinaturas de imagens é como você executará os contêineres. Para a maioria de nós, o Kubernetes é a plataforma preferida, mas ele não oferece suporte nativo ao DCT. Portanto, a menos que você use uma distribuição específica que o implemente, será necessário fornecer algum tipo de aplicação de políticas em tempo de execução. Felizmente, é possível aproveitar a API do controlador de admissão do Kubernetes para isso. Projetos de código aberto como o Connaisseur podem cuidar do processo para assinaturas DCT / Notary v1 e Cosign.

4. ENCONTRE, CORRIJA E MONITORE VULNERABILIDADES DE CÓDIGO ABERTO

Ao escolher uma imagem base para seu contêiner Docker, você assume indiretamente os riscos de todas as questões de segurança de contêineres associadas a ela. Isso inclui configurações padrão inadequadas, que não contribuem para a segurança do sistema operacional, e bibliotecas do sistema incluídas na imagem base escolhida.

Um bom primeiro passo é usar a imagem base mais enxuta possível, desde que sua aplicação continue funcionando sem problemas. Isso ajuda a reduzir a superfície de ataque ao limitar a exposição a vulnerabilidades. Por outro lado, essa medida não executa auditorias por conta própria nem protege você contra vulnerabilidades futuras que possam ser divulgadas para a versão da imagem base em uso.

Por isso, uma forma de se proteger contra vulnerabilidades em softwares de segurança de código aberto é usar ferramentas como o Snyk para adicionar análises de segurança contínuas do Docker e monitorar vulnerabilidades em todas as camadas das imagens Docker em uso.

Janela do terminal mostrando um ambiente de linha de comando em ~/projects/forks/goof, na branch master

Analise uma imagem Docker em busca de vulnerabilidades conhecidas com estes comandos:

# fetch the image to be tested so it exists locally
$ docker pull node:10
# scan the image with snyk
$ snyk test --docker node:10 --file=path/to/Dockerfile

Monitore uma imagem Docker em busca de vulnerabilidades conhecidas. Quando novas vulnerabilidades forem encontradas, o Snyk poderá notificar você e oferecer orientações para corrigi-las:

$ snyk monitor --docker node:10

Com base nas análises realizadas por usuários do Snyk, descobrimos que 44% das imagens Docker analisadas tinham vulnerabilidades conhecidas e que havia imagens base mais novas e seguras disponíveis. Essa orientação para correção é exclusiva do Snyk e ajuda desenvolvedores a agir e atualizar suas imagens Docker.

O Snyk também descobriu que, em 20% de todas as análises de imagens Docker, bastava recriar a imagem Docker para reduzir o número de vulnerabilidades.

Como auditar a segurança de contêineres?

É essencial analisar seu projeto de contêiner baseado em Linux em busca de vulnerabilidades conhecidas para garantir a segurança do seu ambiente. Para isso, o Snyk analisa a imagem base, os pacotes do sistema operacional (SO) instalados e gerenciados pelo gerenciador de pacotes, além dos binários importantes nas camadas que não foram instalados por meio do gerenciador de pacotes.

Snyk Container oferece orientações para corrigir e proteger imagens públicas do Docker Hub, indicando recomendações de imagens base, a camada do Dockerfile em que uma vulnerabilidade foi encontrada e muito mais.

5. NÃO EXPONHA INFORMAÇÕES CONFIDENCIAIS EM IMAGENS DO DOCKER

Às vezes, ao criar uma aplicação dentro de uma imagem Docker, você precisa de segredos, como uma chave privada SSH para baixar código de um repositório privado ou tokens para instalar pacotes privados. Se você copiá-los para um contêiner intermediário do Docker, eles ficarão armazenados em cache na camada em que foram adicionados, mesmo que você os exclua depois. Mantenha esses tokens e chaves fora do Dockerfile.

Use compilações em vários estágios

Outra forma de melhorar a segurança dos contêineres Docker é usar compilações em vários estágios. Aproveite o suporte do Docker a esse tipo de compilação para buscar e gerenciar segredos em uma camada intermediária da imagem, que será descartada depois, garantindo que nenhum dado confidencial chegue à imagem final. Use código para adicionar segredos a essa camada intermediária, como no exemplo a seguir:

FROM ubuntu as intermediate

WORKDIR /app
COPY secret/key /tmp/
RUN scp -i /tmp/key build@acme/files .

FROM ubuntu
WORKDIR /app
COPY --from=intermediate /app .

Use os segredos do Docker BuildKit

Use um recurso do BuildKit no Docker para gerenciar segredos e montar arquivos confidenciais sem armazená-los em cache, como no exemplo a seguir:

# syntax = docker/dockerfile:1.0-experimental
FROM alpine

# shows secret from default secret location
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecre

# shows secret from custom secret location
RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar

Saiba mais sobre os segredos de compilação do Docker no site oficial.

Cuidado com a cópia recursiva

Tenha cuidado também ao copiar arquivos para a imagem em criação. Por exemplo, o comando a seguir copia recursivamente toda a pasta de contexto da compilação para a imagem Docker, o que pode incluir arquivos confidenciais:

COPY . .

Se houver arquivos confidenciais na pasta, remova-os ou use .dockerignore para ignorá-los:

private.key
appsettings.json

Como proteger um contêiner Docker?

Use compilações em vários estágios para garantir que a imagem de contêiner criada para produção não inclua recursos de desenvolvimento nem segredos ou tokens.

Além disso, use uma ferramenta de segurança para contêineres para verificar suas imagens Docker pela CLI, diretamente no Docker Hub ou nas imagens implantadas em produção usando Amazon ECR, Google GCR ou outros serviços.

6. Use tags fixas para garantir a imutabilidade

Cada imagem Docker pode ter várias tags, que são variantes da mesma imagem. A tag mais comum é latest, que representa a versão mais recente da imagem. As tags de imagem não são imutáveis, e quem publica as imagens pode publicar a mesma tag várias vezes.

Isso significa que a imagem base do seu Dockerfile pode mudar entre compilações. Essas mudanças podem causar comportamentos inconsistentes. Há várias maneiras de reduzir esse risco e melhorar a segurança do Docker:

  • Prefira a tag mais específica disponível. Se a imagem tiver várias tags, como :8 e :8.0.1 ou até mesmo :8.0.1-alpine, prefira a última, pois ela identifica a imagem com mais precisão. Evite tags genéricas, como latest. Lembre-se de que uma tag específica pode ser excluída com o tempo.

  • Para evitar que uma tag específica fique indisponível e impeça o trabalho das equipes que dependem dela, considere manter uma cópia local dessa imagem em um registro ou conta sob seu controle. Leve em conta o esforço de manutenção necessário para essa abordagem, pois será preciso manter um registro. Replicar a imagem que você quer usar em um registro próprio é uma boa prática para garantir que ela não mude.

  • Seja muito específico! Em vez de baixar uma tag, baixe a imagem usando sua referência SHA256 específica. Assim, você garante que receberá a mesma imagem em cada download. No entanto, usar uma referência SHA256 pode ser arriscado: se a imagem mudar, esse hash talvez deixe de existir.

As imagens Docker são seguras?

As imagens Docker podem ser baseadas em distribuições Linux de código aberto e incluir softwares e bibliotecas de código aberto. Uma pesquisa recente da Snyk sobre segurança de código aberto constatou que as imagens Docker mais populares contêm pelo menos 30 vulnerabilidades.

7. Use COPY em vez de ADD

O Docker oferece dois comandos para copiar arquivos do host para a imagem Docker durante a compilação: COPY e ADD. As instruções são semelhantes, mas têm funções diferentes e podem gerar problemas de segurança no contêiner Docker da imagem:

  • COPY — copia arquivos locais recursivamente, com arquivos ou diretórios de origem e destino especificados. Com COPY, é necessário declarar os locais.

  • ADD — copia arquivos locais recursivamente, cria o diretório de destino automaticamente se ele não existir e aceita arquivos compactados como origem local ou URLs remotas, que são extraídos ou baixados, respectivamente, para o diretório de destino.

Embora sutis, as diferenças entre ADD e COPY são importantes. Conheça essas diferenças para evitar possíveis problemas de segurança:

  • Quando URLs remotas são usadas para baixar dados diretamente para um local de origem, podem ocorrer ataques man-in-the-middle que modificam o conteúdo do arquivo baixado. Além disso, é preciso validar melhor a origem e a autenticidade das URLs remotas. Ao usar COPY, a origem dos arquivos baixados de URLs remotas deve ser declarada por meio de uma conexão TLS segura, e as origens também precisam ser validadas.

  • Considerações sobre espaço e camadas da imagem: usar COPY permite separar a adição de um arquivo compactado de um local remoto da sua descompactação, criando camadas diferentes e otimizando o cache da imagem. Se precisar de arquivos remotos, reúna o download, a extração e a limpeza em um único comando RUN. Isso otimiza a operação em uma só camada, em vez de usar várias camadas, como seria necessário com ADD.

  • Quando arquivos compactados locais são usados, ADD os extrai automaticamente para o diretório de destino. Embora isso possa ser aceitável, também cria o risco de zip bombs e de vulnerabilidades Zip Slip, que podem ser acionadas automaticamente.

8. Use rótulos de metadados

Os rótulos da imagem fornecem metadados sobre a imagem que você está criando. Isso ajuda as pessoas a entenderem facilmente como usá-la. O rótulo mais comum é “maintainer”, que informa o nome e o endereço de e-mail da pessoa responsável pela manutenção da imagem. Adicione metadados com o seguinte comando LABEL:

LABEL maintainer="me@acme.com"

Além do contato da pessoa responsável pela manutenção, adicione todos os metadados importantes para você. Eles podem incluir: hash de commit, link para a compilação correspondente, status de qualidade (todos os testes passaram?), código-fonte, referência ao local do arquivo SECURITY.TXT e muito mais.

É uma boa prática adotar um arquivo SECURITY.TXT (RFC5785) que indique sua política de divulgação responsável no esquema de rótulos do Docker, por exemplo:

LABEL securitytxt="https://www.example.com/.well-known/security.txt"

Veja mais informações sobre rótulos para imagens Docker.

9. Use compilações em vários estágios para criar imagens Docker menores e mais seguras

Ao compilar sua aplicação com um Dockerfile, muitos artefatos são criados, mas só são necessários durante a compilação. Eles podem ser pacotes, como ferramentas e bibliotecas de desenvolvimento necessárias para compilar, dependências para executar testes unitários, arquivos temporários, segredos e muito mais.

Manter esses artefatos na imagem base, que talvez seja usada em produção, aumenta o tamanho da imagem Docker. Isso pode aumentar bastante o tempo de download e ampliar a superfície de ataque, pois há mais pacotes instalados. O mesmo vale para a imagem Docker que você usa: talvez seja necessária uma imagem específica para a compilação, mas não para executar o código da aplicação.

Go é um ótimo exemplo. Para compilar uma aplicação em Go, você precisa do compilador Go. Ele gera um executável que roda em qualquer sistema operacional, sem dependências, inclusive em imagens scratch.

Esse é um bom motivo para usar o recurso de compilação em vários estágios do Docker. Ele permite usar várias imagens temporárias durante o processo de compilação e manter apenas a imagem final, junto com as informações copiadas para ela. Dessa forma, você tem duas imagens:

  • Primeira imagem — muito grande e com várias dependências usadas para compilar a aplicação e executar os testes.

  • Segunda imagem — muito menor, tanto em tamanho quanto no número de bibliotecas, contendo apenas uma cópia dos artefatos necessários para executar a aplicação em produção.

10. Use um linter

Use um linter para evitar erros comuns e estabelecer práticas recomendadas que os engenheiros possam seguir de forma automatizada. Essa é uma etapa útil da análise de segurança do Docker para detectar estaticamente problemas de segurança no Dockerfile.

Um exemplo de linter é o hadolint. Ele analisa um Dockerfile e mostra avisos para erros que não seguem suas regras de boas práticas.

Saída do terminal do Hadolint analisando um Dockerfile, com avisos sobre fixar versões de pacotes, listas do apt e usar COPY em vez de ADD.

O Hadolint é ainda mais eficiente quando usado em um ambiente de desenvolvimento integrado (IDE). Por exemplo, ao usar o hadolint como extensão do VSCode, os erros de lint aparecem enquanto você digita. Assim, fica mais fácil escrever Dockerfiles melhores e com mais rapidez.

Segurança de contêineres para DevSecOps

Encontre e corrija vulnerabilidades em contêineres gratuitamente com a Snyk.

Como reforçar a segurança de uma imagem de contêiner Docker?

Você pode usar linters, como hadolint ou dockle, para garantir que o Dockerfile tenha uma configuração segura. Não se esqueça de verificar também suas imagens de contêiner para evitar vulnerabilidades com alto impacto na segurança dos contêineres em produção. 

O Docker representa um risco à segurança?

O Docker é uma tecnologia de virtualização de software que ganhou popularidade e ampla adoção. Ao compilar e implantar com Docker, é preciso seguir as boas práticas de segurança para reduzir riscos como vulnerabilidades incluídas nas imagens base do Docker ou violações de dados causadas por configurações incorretas dos contêineres.

Como proteger um contêiner Docker?

Siga as boas práticas de segurança do Docker para usar imagens base do Docker com poucas ou nenhuma vulnerabilidade conhecida, configurar o Dockerfile com segurança e monitorar os contêineres implantados para detectar diferenças entre as imagens de desenvolvimento e produção. Além disso, siga as boas práticas de Infrastructure as Code para soluções de orquestração de contêineres.

Segurança do Docker: recursos adicionais

Para concluir, se você quer acompanhar as boas práticas de segurança para criar imagens Docker otimizadas para aplicações Node.js e Java:

  1. Você é desenvolvedor Java? Este recurso pode ser útil: Docker para desenvolvedores Java: 5 coisas que você precisa saber para não comprometer sua segurança

  2. 10 boas práticas para criar um contêiner Java com Docker — Um excelente guia detalhado sobre como criar contêineres prontos para produção para aplicações Java.

  3. 10 boas práticas para conteinerizar aplicações web Node.js com Docker — Se você desenvolve em Node.js, vai gostar deste passo a passo que mostra como criar imagens base Docker eficientes e seguras para suas aplicações Node.js.

Conheça o cenário atual da segurança de código aberto

Entenda as tendências e abordagens atuais para proteger softwares de código aberto e a cadeia de suprimentos.