Segurança de Containers: Executando como Usuário Não-Root

11 min de leitura Segurança Linux
Segurança de Containers: Executando como Usuário Não-Root

A execução de containers como usuário root é uma das vulnerabilidades mais comuns e perigosas no ambiente de containerização moderna. Por padrão, muitos desenvolvedores e ferramentas orquestradoras iniciam processos com privilégios totais dentro do namespace do container. Embora o isolamento do kernel ofereça proteção, a quebra desse isolamento (container escape) pode resultar em acesso root ao host, comprometendo toda a infraestrutura.

Neste tutorial técnico, vamos abordar as melhores práticas de segurança containers, focando especificamente na implementação do princípio dos privilégios mínimos através da execução como usuário não-root. Este guia é essencial para profissionais de sysadmin e devops que buscam aplicar hardening linux em seus ambientes Docker, garantindo que aplicações críticas operem com o menor nível de acesso necessário.

Por que Executar como Root é Perigoso?

O modelo de segurança do Linux baseia-se na separação de privilégios. Quando um processo roda como root dentro de um container, ele possui capacidades (capabilities) elevadas. Se um atacante explorar uma vulnerabilidade no código da aplicação (como buffer overflow ou RCE - Remote Code Execution), ele terá controle total sobre o namespace do usuário e, potencialmente, poderá manipular namespaces de rede, PID ou mount para tentar escapar para o host.

Além disso, imagens oficiais ou de terceiros frequentemente incluem ferramentas de depuração ou pacotes desnecessários que rodam com privilégios elevados. Ao forçar a execução como um usuário comum:

  • Redução da Superfície de Ataque: Mesmo que o container seja comprometido, o dano é confinado aos arquivos e processos acessíveis por aquele usuário específico.
  • IDefense in Depth: Adiciona uma camada extra de segurança mesmo se as proteções do kernel falharem.
  • Conformidade: Muitas normas de segurança (PCI-DSS, ISO 27001) exigem a separação de contas e privilégios mínimos.

Etapa 1: Preparando o Usuário no Dockerfile

A base para uma imagem segura começa na construção. Não basta apenas definir um usuário; é crucial criar um grupo dedicado, um usuário dedicado e garantir que os direitos de propriedade (ownership) dos arquivos da aplicação sejam ajustados corretamente.

Crie ou edite seu Dockerfile. Vamos seguir a prática recomendada de usar UID/GID não padrão para evitar conflitos com usuários do sistema no host, embora o Docker gerencie isso internamente, é uma boa prática de documentação.

# Define um nome base para a imagem
FROM node:18-alpine

# Define variáveis de ambiente para facilitar manutenção
ARG APP_USER=appuser
ARG APP_UID=1001
ARG APP_GID=1001

# Cria o grupo e o usuário não-root
RUN addgroup -g ${APP_GID} -S ${APP_USER} && \
    adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_USER}

# Define o diretório de trabalho
WORKDIR /app

# Copia os arquivos da aplicação
COPY package*.json ./
RUN npm install --production

# Copia o restante dos arquivos
COPY . .

# IMPORTANTE: Altera a propriedade dos arquivos para o novo usuário
# Isso garante que o app possa escrever logs ou uploads se necessário
RUN chown -R ${APP_USER}:${APP_USER} /app

# Define o comando de execução (opcional, mas recomendado definir user depois)
CMD ["node", "server.js"]

Nesta configuração, utilizamos comandos nativos do Alpine Linux (addgroup e adduser). Se estiver usando imagens base Debian/Ubuntu, substitua por groupadd e useradd.

Etapa 2: Configurando o Runtime no Docker

Apenas definir o usuário no Dockerfile não garante que ele será usado se você sobrescrever o comando de entrada ou se a imagem original tiver um entrypoint que force root. A maneira mais robusta de garantir docker security é especificar o usuário na execução do container ou nas definições de serviço.

Execução via Linha de Comando

Para testes ou implantações rápidas, use a flag --user. Isso sobrescreve qualquer configuração interna do Dockerfile e força o processo principal a rodar com o UID/GID especificado.

# Executa o container usando o UID 1001 e GID 1001
docker run -d \
  --name minha-app-segura \
  --user 1001:1001 \
  -p 3000:3000 \
  minha-imagem-node

Se você usar --user=root, o container ignorará a configuração do Dockerfile. Portanto, audite seus scripts de deploy para garantir que essa flag não esteja presente ou esteja configurada incorretamente.

Execução via Docker Compose

No ambiente de desenvolvimento e orquestração básica, o docker-compose.yml deve refletir as mesmas restrições de produção.

version: '3.8'
services:
  app:
    image: minha-imagem-node
    user: "1001:1001" # Força execução como não-root
    ports:
      - "3000:3000"
    volumes:
      - ./logs:/app/logs # Se precisar de escrita, o volume deve ter permissões adequadas

Etapa 3: Gerenciamento de Permissões de Arquivos e Volumes

Um erro comum ao migrar para usuário não-root é a falha na inicialização da aplicação devido à falta de permissão de escrita em diretórios necessários, como /tmp, logs ou diretórios de upload.

O Problema dos Volumes Host

Quando você monta um volume do host para dentro do container (ex: -v /host/data:/container/data), o Docker mantém os atributos originais do arquivo no host. Se o diretório no host for de propriedade do root, e seu container tentar escrever como UID 1001, a operação falhará.

Solução: No host, altere a permissão do diretório montado para o UID que será usado no container.

# No host Linux
sudo chown -R 1001:1001 /caminho/para/seus/dados

O Problema de Logs em Tempo Real

Muitas aplicações tentam escrever logs em um diretório específico durante o startup. Se o container for iniciado como root, ele cria os arquivos com permissão root. Quando você reinicia o container configurado para rodar como appuser, a aplicação não consegue sobrescrever ou anexar aos arquivos existentes.

Solução: Limpe os diretórios de logs antes de iniciar, ou configure sua aplicação para usar um diretório temporário com permissões globais de escrita (como /tmp), ou inicialize o arquivo com as permissões corretas via script de entrada.

Etapa 4: Hardening Adicional no Dockerfile

A execução como não-root é a camada principal, mas deve ser complementada por outras práticas de hardening linux.

Uso de Imagens Slim e Multi-stage Builds

Imagens menores têm menos bibliotecas instaladas, reduzindo a chance de vulnerabilidades conhecidas (CVEs). Utilize multi-stage builds para separar o ambiente de compilação do runtime.

# Estágio 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm run build

# Estágio 2: Runtime Seguro
FROM alpine:3.18
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /usr/src/app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

Note que definimos USER appuser no Dockerfile. Isso é uma camada de segurança extra: mesmo se o usuário for omitido na linha de comando do Docker, a imagem já carrega o contexto de segurança correto.

Desabilitar Capabilities desnecessárias

O Linux permite que processos tenham capacidades específicas (como NET_BIND_SERVICE para portas abaixo de 1024). Containers herdam um subconjunto padrão. Para máxima segurança, remova capacidades não usadas.

docker run -d \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \ # Se precisar de porta 80/443
  --user 1001:1001 \
  minha-imagem

A flag --cap-drop ALL remove todas as capacidades por padrão. Você deve adicionar apenas aquelas estritamente necessárias para o funcionamento da aplicação. Isso mitiga ataques que dependem de privilégios específicos do kernel.

Etapa 5: Validação e Verificação

Após configurar sua imagem e container, é vital validar se as restrições estão sendo aplicadas corretamente. Não assuma que a configuração funcionou; verifique.

Verificando o Usuário em Execução

Use o comando docker exec para inspecionar o processo ativo dentro do container.

# Verifica quem está rodando o processo principal
docker exec -it meu-container ps aux | grep -v grep | grep node

O resultado deve mostrar o usuário appuser (ou UID 1001), e não root.

Analisando a Imagem

Ferramentas de análise estática podem detectar configurações inseguras antes mesmo da implantação. Utilize scanners como Trivy, Grype ou o próprio docker scan.

# Instalação do Trivy (exemplo para Linux)
wget -qO - https://raw.githubusercontent.com/aquasecurity/trivy/main/packaging/linux/deb/gpg | sudo apt-key add -
sudo apt-get install trivy
# Escaneia a imagem em busca de vulnerabilidades e más configurações
trivy image minha-imagem-node --security-checks config

O sinalizador --security-checks config verifica especificamente problemas de configuração, como execução como root.

Etapa 6: Boas Práticas para Desenvolvedores e DevOps

A segurança de containers não é apenas uma tarefa do time de infraestrutura; ela deve ser integrada ao ciclo de vida do desenvolvimento (DevSecOps).

  • Documentação Clara: Inclua no README do repositório a exigência de que o container deve rodar como não-root. Explique as variáveis de ambiente e portas necessárias.
  • Linter de Dockerfile: Utilize ferramentas como hadolint em seus pipelines CI/CD para bloquear builds que não sigam convenções de segurança, como a ausência da instrução USER.
# Exemplo de regra no .hadolint.yaml para forçar usuário não-root
trustedUsers: ["root", "appuser"]
  • Gestão de Segredos: Nunca armazene senhas ou chaves SSH dentro da imagem. Use variáveis de ambiente passadas via orquestrador (Kubernetes Secrets, Docker Configs) ou montagens de volume seguras. Usuários não-root não devem ter acesso a arquivos de configuração sensíveis se eles estiverem no sistema de arquivos do container.
  • Atualização Constante: Mantenha as imagens base atualizadas. Vulnerabilidades em bibliotecas do sistema (glibc, openssl) podem ser exploradas mesmo por usuários não-root para ganhar privilégios elevados (privilege escalation).
  • Conclusão: A Cultura de Segurança na Containerização

    A migração para a execução como usuário não-root é um passo fundamental, mas não único, no caminho do hardening linux. Ela exige uma mudança de mentalidade: sair da conveniência do "root" para a disciplina dos "privilégios mínimos".

    Ao implementar as etapas descritas neste tutorial — desde a construção correta do Dockerfile com usuários dedicados, passando pela configuração explícita no runtime e gerenciamento de volumes, até a validação contínua via CI/CD — você fortalece significativamente a postura de segurança da sua infraestrutura.

    Lembre-se: um container comprometido rodando como usuário limitado é apenas um processo isolado com acesso restrito. Um container comprometido rodando como root é uma porta aberta para o seu servidor host. Priorize a segurança desde a primeira linha de código e na primeira configuração do Dockerfile.

    Para aprofundar seus conhecimentos em containerização, explore tópicos avançados como seccomp profiles, AppArmor/SELinux, e a implementação de namespaces isolados no Kubernetes. A segurança é um processo contínuo de melhoria e adaptação às novas ameaças.

    Compartilhar: Link copiado!
    Esse tutorial foi útil?

    Comentários (0)

    Seja o primeiro a comentar.

    Deixe seu comentário

    Seu comentário será analisado antes de ser publicado.

    0/2000