DevOps Admin

Como Usar Segurança em Containers Docker: Rootless, Capabilities e Scanning em Produção Já leu

Segurança em Containers Docker: Rootless, Capabilities e Scanning Quando falamos em segurança em containers, é essencial compreender que estamos lidando com um ambiente que compartilha o kernel do host. Isso significa que uma vulnerabilidade em um container pode potencialmente afetar o sistema inteiro. O Docker oferece várias camadas de proteção, e três delas são fundamentais: executar containers sem privilégios root, limitar capabilities do sistema operacional e realizar scanning de vulnerabilidades em imagens. Este artigo aborda cada uma dessas estratégias de forma prática e detalhada. Docker Rootless: Executando Containers sem Privilégios Root Entendendo o Problema do Root em Containers Por padrão, processos dentro de um container executam como root (UID 0). Isso parecia conveniente historicamente, mas representa um risco significativo. Se um atacante conseguir escapar do container, ele terá acesso root no host. O Docker Rootless é uma abordagem que executa tanto o daemon do Docker quanto os containers com um usuário não-root, criando uma camada adicional de segurança através de

Segurança em Containers Docker: Rootless, Capabilities e Scanning

Quando falamos em segurança em containers, é essencial compreender que estamos lidando com um ambiente que compartilha o kernel do host. Isso significa que uma vulnerabilidade em um container pode potencialmente afetar o sistema inteiro. O Docker oferece várias camadas de proteção, e três delas são fundamentais: executar containers sem privilégios root, limitar capabilities do sistema operacional e realizar scanning de vulnerabilidades em imagens. Este artigo aborda cada uma dessas estratégias de forma prática e detalhada.

Docker Rootless: Executando Containers sem Privilégios Root

Entendendo o Problema do Root em Containers

Por padrão, processos dentro de um container executam como root (UID 0). Isso parecia conveniente historicamente, mas representa um risco significativo. Se um atacante conseguir escapar do container, ele terá acesso root no host. O Docker Rootless é uma abordagem que executa tanto o daemon do Docker quanto os containers com um usuário não-root, criando uma camada adicional de segurança através de user namespaces.

A diferença é fundamental: em um Docker padrão, o usuário root dentro do container é mapeado para o usuário root do host. No Docker Rootless, o usuário root do container é mapeado para um usuário normal (como dockremap) no host, eliminando esse privilégio.

Instalando e Configurando Docker Rootless

A instalação do Docker Rootless envolve algumas etapas específicas. Primeiro, desinstalamos a versão padrão (se instalada) e configuramos o ambiente de forma adequada. Veja como fazer isso em um sistema Linux moderno:

# 1. Remover Docker padrão (se necessário)
sudo apt-get remove docker docker-engine docker.io containerd runc

# 2. Instalar dependências necessárias
sudo apt-get install -y uidmap dbus-user-session

# 3. Baixar e instalar Docker Rootless
curl https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz | tar xz -C /tmp/
/tmp/docker/dockerd-rootless-setuptool.sh install

# 4. Ativar o serviço para iniciar automaticamente
systemctl --user enable docker
systemctl --user start docker

# 5. Verificar instalação
dockerd-rootless-setuptool.sh check

Após a instalação, você deve configurar variáveis de ambiente para usar a instância rootless. Adicione ao seu .bashrc ou .zshrc:

export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

Validando o Comportamento Rootless

Uma maneira simples de validar que seu Docker está realmente em modo rootless é executar um container e verificar o UID do processo. Compare o comportamento entre um docker padrão e rootless:

# Em Docker Rootless
docker run --rm ubuntu id

# Saída esperada:
# uid=0(root) gid=0(root) groups=0(root)

# Mas se você verificar no host:
ps aux | grep "docker run"
# O processo estará rodando com UID do usuário não-root

A questão é sutil: dentro do container ele parece ser root (pelo namespace), mas no host real, ele é um usuário normal. Isso oferece proteção significativa contra privilege escalation.

Capabilities: Controlando Privilégios no Nível do Kernel

O que são Capabilities e por que importam

Linux capabilities dividem os privilégios tradicionais do root em unidades específicas e menores. Ao invés de dar permissão total de root, você pode conceder apenas as capacidades específicas que um processo realmente precisa. Docker, por padrão, mantém um conjunto de capabilities "seguras" e remove outras perigosas.

Um container executando um servidor web nginx, por exemplo, não precisa de CAP_NET_ADMIN, que permite configuração avançada de rede. Não precisa de CAP_SYS_MODULE, que permite carregar módulos do kernel. Ao remover essas capabilities, você reduz drasticamente a superfície de ataque.

Capabilities Padrão em Docker

Docker inicia containers com um conjunto padrão de capabilities. Você pode visualizar e modificar esse comportamento. As capabilities padrão incluem permissões básicas para networking e operações de arquivo, mas excluem as mais perigosas:

# Ver capabilities padrão
docker inspect <container_id> | grep -i "cap"

# Exemplo de saída esperada:
# "CapAdd": [],
# "CapDrop": [
#     "NET_RAW",
#     "SYS_CHROOT",
#     "KILL",
#     "SETFCAP",
#     "SETPCAP",
#     "NET_BIND_SERVICE",
#     "SYS_CHROOT",
#     "KILL",
#     "AUDIT_WRITE"
# ]

Removendo e Adicionando Capabilities Seletivamente

A estratégia "least privilege" significa que você deve começar sem capabilities e adicionar apenas as necessárias. Vamos ver um exemplo prático com um Dockerfile que executa um aplicativo específico:

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y curl

# Criamos um usuário não-root para executar a aplicação
RUN useradd -m -u 1000 appuser

COPY app.sh /home/appuser/
RUN chmod +x /home/appuser/app.sh

USER appuser

ENTRYPOINT ["/home/appuser/app.sh"]

Agora, ao executar esse container, você pode especificar exatamente quais capabilities são necessárias:

# Executar com ALL capabilities removidas (máxima segurança)
docker run --cap-drop=ALL minha-app

# Se o app precisa fazer binding em portas < 1024, adicione apenas CAP_NET_BIND_SERVICE
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE minha-app

# Para um servidor web que precisa binding em porta 80
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --cap-add=CHOWN minha-app

Exemplo Prático: Servidor Web Seguro

Vamos criar um exemplo realista de um container nginx com capabilities otimizadas:

# Executar nginx com apenas as capabilities necessárias
docker run -d \
  --name nginx-seguro \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --cap-add=CHOWN \
  --cap-add=SETUID \
  --cap-add=SETGID \
  --read-only \
  --tmpfs /var/run \
  --tmpfs /var/cache/nginx \
  -p 80:80 \
  nginx:latest

Cada capability tem um propósito específico:
- NET_BIND_SERVICE: permite fazer bind em portas < 1024
- CHOWN: permite mudar proprietário de arquivos
- SETUID/SETGID: permite mudar UID/GID de processos

Remover capabilities desnecessárias reduz dramaticamente o potencial de dano se a aplicação for comprometida.

Scanning de Vulnerabilidades em Imagens Docker

Por que Scanning é Essencial

Uma imagem Docker é uma combinação de múltiplas camadas: a imagem base (como Ubuntu ou Alpine), dependências do sistema operacional e dependências de aplicação. Qualquer uma dessas camadas pode conter vulnerabilidades conhecidas (CVEs - Common Vulnerabilities and Exposures). O scanning verifica essas vulnerabilidades antes de colocar a imagem em produção, evitando deploy de código comprometido.

Existem várias ferramentas disponíveis: Trivy (gratuita, open-source e muito usada), Snyk (comercial com plano gratuito), Docker Scout (integrado ao Docker Desktop), e outras. Vamos focar em Trivy por ser a mais acessível e poderosa.

Instalando e Usando Trivy

Trivy é uma ferramenta de scanning leve e rápida. Aqui está como instalá-la e usá-la:

# Instalação no Ubuntu/Debian
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy

# Verificar instalação
trivy version

Executando Scans Básicos em Imagens

Depois de instalado, você pode escanear qualquer imagem Docker. O Trivy verifica vulnerabilidades na imagem, analisando o sistema de arquivos e detectando pacotes vulneráveis:

# Scan simples em uma imagem local
trivy image ubuntu:22.04

# Scan com saída em formato JSON (útil para integração)
trivy image --format json ubuntu:22.04 > scan-results.json

# Scan com saída em tabela resumida
trivy image --severity HIGH,CRITICAL ubuntu:22.04

# Scan de uma imagem que ainda não foi baixada
trivy image --download-db-only
trivy image gcr.io/distroless/python3-debian11

Exemplo Realista: CI/CD com Scanning

Em um pipeline de CI/CD (usando Docker, Kubernetes ou qualquer orquestrador), você quer falhar o build se vulnerabilidades críticas forem encontradas. Aqui está um exemplo de script que você pode integrar:

#!/bin/bash
# scan-image.sh - Script para validar imagem antes de deploy

IMAGE_NAME=$1
SEVERITY_THRESHOLD=${2:-CRITICAL}  # Por padrão, falha apenas em CRITICAL

if [ -z "$IMAGE_NAME" ]; then
    echo "Uso: ./scan-image.sh <nome-da-imagem> [SEVERITY]"
    exit 1
fi

echo "Escaneando imagem: $IMAGE_NAME"
echo "Threshold de falha: $SEVERITY_THRESHOLD"

# Executar trivy e capturar resultado
trivy image --exit-code 0 --severity $SEVERITY_THRESHOLD "$IMAGE_NAME" > scan-output.txt

# Analisar resultado
if grep -q "$SEVERITY_THRESHOLD" scan-output.txt; then
    echo "❌ FALHA: Vulnerabilidades $SEVERITY_THRESHOLD encontradas!"
    cat scan-output.txt
    exit 1
else
    echo "✅ SUCESSO: Nenhuma vulnerabilidade $SEVERITY_THRESHOLD encontrada"
    cat scan-output.txt
    exit 0
fi

Integrando Scanning em Docker Compose

Se você usa Docker Compose, pode adicionar um estágio de scanning antes de colocar imagens em produção:

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: minha-app:${VERSION:-latest}
    security_opt:
      - no-new-privileges:true
    cap-drop:
      - ALL
    cap-add:
      - NET_BIND_SERVICE
    user: "1000"
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

  security-check:
    image: aquasec/trivy:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./scan-results:/results
    command:
      - image
      - --exit-code
      - "1"
      - --severity
      - HIGH,CRITICAL
      - --format
      - json
      - --output
      - /results/scan.json
      - minha-app:${VERSION:-latest}
    depends_on:
      - app

Boas Práticas Integradas

Criando um Container Verdadeiramente Seguro

Combinar todas essas técnicas resulta em um container muito mais seguro. Aqui está um exemplo completo que integra rootless, capabilities limitadas e scanning:

FROM alpine:3.18

# Instalar apenas o necessário
RUN apk add --no-cache python3 py3-pip

# Criar usuário não-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copiar aplicação
COPY app.py .
RUN chown -R appuser:appgroup /app

# Rodar como usuário não-root
USER appuser

EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

ENTRYPOINT ["python3", "app.py"]

Para executar esse container com máxima segurança:

# Build
docker build -t minha-app-segura .

# Escanear antes de usar
trivy image --severity HIGH,CRITICAL minha-app-segura

# Rodar com todas as proteções
docker run \
  --name app-segura \
  --read-only \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --user appuser \
  --tmpfs /tmp \
  --tmpfs /run \
  --security-opt=no-new-privileges:true \
  --memory=256m \
  --cpus=0.5 \
  --pids-limit=100 \
  -p 8000:8000 \
  minha-app-segura

Conclusão

Aprendemos três pilares fundamentais de segurança em Docker que trabalham juntos para proteger seus containers. Primeiro, Docker Rootless elimina o risco de privilege escalation ao nível do host, mapeando o usuário root do container para um usuário normal no sistema operacional. Segundo, capabilities permitem aplicar o princípio de least privilege de forma granular, concedendo apenas as permissões específicas que cada aplicação realmente necessita. Terceiro, scanning com ferramentas como Trivy detecta vulnerabilidades conhecidas em imagens antes da produção, prevenindo deploys de código comprometido. A combinação dessas três estratégias, aplicadas desde o desenvolvimento até a produção, cria uma postura de segurança robusta e defensável.

Referências


Artigos relacionados