Introdução ao Hardening de Containers Docker
A adoção massiva do Docker e da orquestração de containers revolucionou a forma como desenvolvemos e implantamos software. No entanto, essa flexibilidade introduz riscos de segurança significativos se não for gerenciada com rigor. O conceito de hardening (endurecimento) de containers refere-se ao conjunto de práticas destinadas a reduzir a superfície de ataque, limitando o acesso do container aos recursos do sistema hospedeiro e isolando processos sensíveis.
Muitos administradores iniciantes executam containers com privilégios root por padrão ou utilizam imagens base não otimizadas, que vêm repletas de ferramentas desnecessárias. Em um ambiente de VPS (Virtual Private Server) compartilhado ou mesmo dedicado, a falha em aplicar restrições adequadas pode permitir que um atacante escape do container e comprometa o host Linux subjacente.
Neste tutorial técnico, abordaremos as melhores práticas para restringir permissões em containers Docker. Vamos cobrir desde a execução básica via linha de comando até a configuração robusta utilizando Docker Compose, garantindo que seus serviços rodem com o mínimo privilégio necessário (princípio do menor privilégio).
1. Entendendo o Contexto de Segurança no Linux
Antes de aplicar comandos, é crucial entender como o Docker interage com o kernel do Linux. Containers não são máquinas virtuais completas; eles compartilham o kernel do host. Se um container for executado como usuário root e explorar uma vulnerabilidade no kernel, o atacante pode obter controle total sobre a máquina virtual ou física que hospeda o Docker.
Portanto, o objetivo principal deste guia é garantir que:
- O container não rode como root.
- Capsulas de Linux (Linux Capabilities) desnecessárias sejam removidas.
- O sistema de arquivos seja imutável onde possível.
- Recursos do sistema (CPU, Memória) sejam limitados para evitar ataques de negação de serviço (DoS).
2. Execução Básica: Criando Usuário e Removendo Privilégios
A primeira camada de defesa é garantir que o processo principal do container não execute como root. Vamos começar com um exemplo prático usando uma imagem simples, como a oficial do nginx, mas modificada para rodar com privilégios reduzidos.
Passo 1: Criar um usuário não-root no Dockerfile
Se você está construindo sua própria imagem, nunca execute comandos de instalação ou execução como root. Crie um usuário dedicado:
FROM nginx:alpine
# Cria um grupo e um usuário sem login
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Define o proprietário dos arquivos da web para o novo usuário
RUN chown -R appuser:appgroup /usr/share/nginx/html
# Muda para o usuário não-root
USER appuser
Passo 2: Execução manual com restrições
Ao rodar o container diretamente, evite a flag --privileged. Ela concede acesso irrestrito a todos os dispositivos e é extremamente perigosa em produção. Em vez disso, especifique o usuário explicitamente se não estiver definido no Dockerfile:
docker run -d \
--name secure-nginx \
--user 1000:1000 \
-p 8080:80 \
nginx:alpine
Neste comando, substituímos 1000:1000 pelo UID e GID do seu usuário criado. Isso garante que, mesmo que haja uma falha na configuração da imagem, o processo rodará com permissões limitadas.
3. Gerenciamento de Capabilities do Linux
O Docker permite a manipulação das Linux Capabilities, que são divisões dos privilégios root tradicionais. Por padrão, o Docker adiciona um conjunto de capabilities ao container. Para hardening, devemos remover aquelas que não são essenciais para o funcionamento do serviço.
Removendo todas as capabilities e adicionando apenas o necessário:
docker run -d \
--name hardened-app \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
-p 8080:80 \
nginx:alpine
A flag --cap-drop=ALL remove todos os privilégios especiais. A flag --cap-add=NET_BIND_SERVICE permite que o container se ligue a portas abaixo de 1024 (como a porta 80), sem precisar de root para outras operações. Essa abordagem é muito mais segura do que manter o conjunto padrão.
4. Restringindo Acesso ao Sistema de Arquivos
Muitas aplicações não precisam escrever no sistema de arquivos raiz do container, mas muitas imagens padrão permitem isso. Para impedir que malware ou scripts maliciosos modifiquem binários ou configurações críticas, monte o sistema de arquivos como somente leitura.
docker run -d \
--name readonly-container \
--read-only \
-v /tmp/data:/tmp:rw \
nginx:alpine
A flag --read-only torna o sistema de arquivos do container imutável. No entanto, algumas aplicações precisam de diretórios temporários para funcionar (como /tmp ou logs). Nesse caso, use volumes anônimos ou mapeamentos específicos (-v /tmp:/tmp:rw) para fornecer espaço de escrita apenas onde é estritamente necessário.
5. Limitação de Recursos com Cgroups
Um container comprometido pode tentar consumir toda a CPU ou memória da VPS, causando uma negação de serviço contra outros serviços no mesmo host. O Docker utiliza Cgroups (Control Groups) para impor limites.
Definindo limites de memória e CPU:
docker run -d \
--name limited-resources \
--memory=512m \
--cpus="1.5" \
nginx:alpine
Este comando limita o container a usar no máximo 512MB de RAM e 1.5 núcleos de CPU. Se o processo tentar exceder esses limites, o kernel do Linux limitará sua execução ou o matará (OOM Killer), protegendo a estabilidade da VPS.
6. Hardening Avançado com Docker Compose
Em ambientes reais, gerenciar dezenas de containers manualmente é inviável. O Docker Compose permite definir infraestrutura como código, facilitando a aplicação consistente de políticas de segurança.
Crie um arquivo docker-compose.yml que incorpore as práticas de hardening discutidas acima:
version: '3.8'
services:
web-app:
image: nginx:alpine
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
deploy:
resources:
limits:
cpus: '0.50'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
volumes:
- ./data:/var/www/html:ro
- tmpfs-data:/tmp
networks:
- internal_net
volumes:
tmpfs-data:
driver: local
driver_opts:
type: tmpfs
o: nodev,noexec,nosuid
networks:
internal_net:
driver: bridge
Analisemos os elementos de segurança neste arquivo:
- user: Força a execução como um UID específico, impedindo root.
- read_only: true: Torna o sistema de arquivos imutável.
- security_opt: A opção
no-new-privileges:trueimpede que processos dentro do container obtenham novos privilégios via setuid/setgid (como o comandosu). Isso é crucial para evitar escalada de privilégios. - cap_drop/cap_add: Remove todas as capabilities e adiciona apenas a necessária para portas baixas.
- deploy.resources: Limita CPU e memória via especificação do Docker Compose (que mapeia para flags
--cpuse--memory). - volumes: O diretório de dados é montado como somente leitura (
:ro). O/tmpusa um volume local com opçõesnoexec,nosuid,nodev, impedindo a execução de binários maliciosos nessa área. - networks: Isola o container em uma rede privada, impedindo acesso direto da internet externa se não houver portas expostas via
ports.
7. Varredura de Imagens e Atualizações
O hardening não termina após a configuração do container. As imagens Docker podem conter vulnerabilidades conhecidas (CVEs) em suas bibliotecas ou pacotes base.
Passo 1: Utilizar imagens oficiais e minimalistas
Prefira sempre as variantes :alpine ou :slim das imagens oficiais. Elas contêm menos pacotes, reduzindo a superfície de ataque.
docker pull nginx:alpine
Passo 2: Verificar vulnerabilidades
Utilize ferramentas de análise estática para verificar suas imagens antes de implantá-las. O Docker Engine possui integração nativa com scanners como o Snyk ou Aqua Security, mas você também pode usar o docker scan (se configurado) ou ferramentas de linha de comando como trivy:
trivy image nginx:alpine
Isso gerará um relatório detalhado sobre bibliotecas desatualizadas com falhas de segurança conhecidas.
8. Boas Práticas Adicionais para Sysadmins
- Nunca use a tag
latestem produção: Ela é volátil e pode trazer atualizações não testadas que quebram a segurança ou a funcionalidade. Use versões específicas (ex:nginx:1.25-alpine). - Habilite o AppArmor ou SELinux: No host Linux, configure perfis de AppArmor para restringir ainda mais as chamadas de sistema que os containers podem fazer. Isso adiciona uma camada de defesa além das namespaces do Docker.
- Rotacione segredos: Nunca passe senhas ou chaves de API como variáveis de ambiente no
docker-compose.yml. Use volumes especiais (/run/secrets) ou ferramentas externas como HashiCorp Vault. - Mantenha o Docker atualizado: Aplique patches regularmente no daemon do Docker e no kernel do Linux (host).
Conclusão
O hardening de containers é um processo contínuo, não um estado final. Ao aplicar as práticas descritas neste tutorial — restringir usuários, drop capabilities, limitar recursos e usar volumes seguros — você transforma o Docker de uma ferramenta potencialmente perigosa em uma infraestrutura robusta e segura.
Lembre-se: a segurança é uma responsabilidade compartilhada entre o provedor da imagem, o desenvolvedor que a constrói e o administrador de sistemas que a implanta. Comece aplicando essas mudanças no seu ambiente de staging e monitore os logs para garantir que nenhuma funcionalidade crítica foi quebrada pela restrição de permissões.
Ao adotar uma postura de "defesa em profundidade", você protege não apenas seus dados, mas a integridade da sua VPS e de toda a rede corporativa.