Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 34

Securing Local Development with Docker Compose Secrets

Recommended resources

Olá! Vamos continuar nossa jornada pelas melhores práticas de segurança no Docker.

Introdução

Na lição anterior, você aprendeu a proteger o ambiente de execução dos seus contêineres, configurando limites de CPU e memória. Isso garante que seus serviços sejam resilientes e não sofram com o problema do "vizinho barulhento".

Hoje, vamos abordar outro pilar da segurança: o gerenciamento de dados sensíveis. Em qualquer aplicação, lidamos com senhas de banco de dados, chaves de API, tokens e outros segredos. A forma mais comum de gerenciar isso no desenvolvimento local é através de arquivos .env, mas essa abordagem tem suas fragilidades.

O objetivo desta lição é aprender a gerenciar dados sensíveis para desenvolvimento local usando os secrets do Docker Compose. Você entenderá por que esse mecanismo é mais seguro do que usar apenas variáveis de ambiente e como implementá-lo em seus projetos.


1. O Problema: Exposição de Segredos

Como desenvolvedor back-end, você certamente já usou arquivos .env para fornecer configurações a uma aplicação. O Docker Compose se integra perfeitamente com eles, substituindo variáveis no seu docker-compose.yml.

# docker-compose.yml
services:
  db:
    image: postgres:15
    environment:
      # A senha é lida de um arquivo .env
      POSTGRES_PASSWORD: ${DB_PASSWORD}
# Este arquivo deve estar no .gitignore!
DB_PASSWORD=mySuperSecretPassword123

Embora seja melhor do que hardcodar a senha diretamente no docker-compose.yml, essa abordagem ainda expõe o segredo como uma variável de ambiente dentro do contêiner.

Para entender os riscos associados a isso, o artigo a seguir do blog da Phase descreve o problema de forma clara e objetiva.

Managing Secrets in Docker Compose — A Developer's Guide

Leia a primeira parte deste guia, "Managing Secrets in Docker Compose — A Developer's Guide", para entender por que confiar apenas em variáveis de ambiente para segredos pode ser arriscado.

Leia a seção introdutória até "Better Secret Management with Docker Compose". Foque nos pontos que explicam como as variáveis de ambiente podem ser inspecionadas e os exemplos de incidentes de segurança que ocorreram devido a essa exposição.

Como o artigo aponta, qualquer processo dentro do contêiner (ou qualquer pessoa com acesso ao Docker) pode facilmente ler essas variáveis usando docker inspect ou docker exec <container_id> env. Isso viola o Princípio do Menor Privilégio, pois dados sensíveis ficam mais acessíveis do que o necessário.


2. A Solução: Docker Compose secrets

Para mitigar esses riscos, o Docker Compose oferece um mecanismo nativo chamado secrets. Em vez de injetar segredos como variáveis de ambiente, ele os disponibiliza como arquivos dentro de um sistema de arquivos temporário e em memória (tmpfs) no contêiner.

Docker Compose Tutorial Overview
Este diagrama mostra um exemplo de arquivo `docker-compose.yml`, destacando a seção `secrets` na parte inferior esquerda. Isso ilustra como os segredos são definidos ao lado de outros elementos de configuração, como serviços, redes e volumes, para gerenciar uma aplicação completa.

A documentação oficial do Docker explica essa abordagem de forma concisa.

Secrets in Compose

Vamos consultar a documentação oficial para entender os fundamentos dos secrets no Compose.

Leia as seções "Manage secrets securely in Docker Compose" e "Use secrets". Preste atenção em onde os segredos são montados por padrão (/run/secrets/) e no processo de duas etapas: definir o segredo no nível superior e referenciá-lo no serviço.

Principais Vantagens dos secrets:

  • Não são variáveis de ambiente: O segredo não é exposto a todos os processos do contêiner. Apenas a aplicação que sabe ler o arquivo específico terá acesso.
  • Armazenamento em memória: Os arquivos de segredo são escritos em um tmpfs, o que significa que não são persistidos no disco do contêiner, reduzindo o risco de exposição.
  • Controle de acesso por serviço: Você concede explicitamente acesso a um segredo para cada serviço, em vez de expô-lo globalmente.

3. Implementando secrets na Prática

Vamos aplicar o conceito em um cenário comum e muito relevante para você: proteger a senha de um banco de dados PostgreSQL.

O vídeo a seguir, do canal Anton Putra, demonstra o processo completo, desde a criação dos arquivos de segredo até a configuração do docker-compose.yml.

Docker Compose Tutorial for Beginners (Networks - Volumes - Secrets - Postgres - Letsencrypt)

Este trecho do "Docker Compose Tutorial for Beginners" mostra um exemplo prático de como proteger a senha do PostgreSQL usando secrets.

Assista à seção de 44:43 a 47:04. Observe os três passos principais: Criar um arquivo local (ex: postgres_password.txt) contendo a senha. Definir o segredo no nível superior do docker-compose.yml, apontando para esse arquivo. Configurar o serviço do PostgreSQL para usar o segredo, utilizando a variável de ambiente especial POSTGRES_PASSWORD_FILE.

Vamos detalhar o processo visto no vídeo:

Passo 1: Crie o arquivo do segredo
Crie um arquivo de texto simples, por exemplo, db_password.txt, e coloque a senha dentro dele. Apenas a senha, sem espaços extras ou quebras de linha.

db_password.txt:

MySecureDbPassword!

Importante: Adicione este arquivo ao seu .gitignore!

# .gitignore
*.txt

Passo 2: Configure o docker-compose.yml

version: '3.8'

services:
  db:
    image: postgres:15
    environment:
      # Note que não usamos POSTGRES_PASSWORD
      POSTGRES_USER: user
      POSTGRES_DB: mydb
      # Usamos a convenção _FILE para apontar para o caminho do segredo dentro do contêiner
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      # Concede a este serviço acesso ao segredo 'db_password'
      - db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  api:
    image: minha-api-net:latest
    # A API também pode precisar do segredo para se conectar ao banco
    secrets:
      - db_password
    environment:
      # Sua API leria o arquivo em /run/secrets/db_password para obter a string de conexão
      DB_HOST: db
      DB_USER: user
      DB_NAME: mydb

# Definição do segredo no nível superior
secrets:
  db_password:
    # Aponta para o arquivo local que contém o segredo
    file: ./db_password.txt

volumes:
  postgres_data:

A mágica acontece com a variável POSTGRES_PASSWORD_FILE. Muitas imagens oficiais (incluindo PostgreSQL, MySQL, Redis, etc.) seguem essa convenção: se você fornecer uma variável de ambiente com o sufixo _FILE, a imagem lerá o conteúdo do arquivo apontado por essa variável e o usará como o valor do segredo.

Isso permite que você use o mecanismo de secrets do Docker sem precisar modificar o código da sua aplicação (no caso de usar imagens de terceiros) ou implementando uma lógica simples para ler o arquivo, caso seja sua própria aplicação.


4. Controle Fino com a Sintaxe Longa

Para um controle ainda mais granular, especialmente importante em ambientes de produção, mas útil para entender o mecanismo completo, o Docker Compose oferece uma "sintaxe longa" para secrets.

Isso permite especificar:

  • target: O nome ou caminho completo do arquivo dentro do contêiner.
  • uid e gid: O ID do usuário e do grupo que serão donos do arquivo de segredo dentro do contêiner.
  • mode: As permissões do arquivo em notação octal (ex: 0400 para legível apenas pelo dono).

Retornando ao excelente artigo da Phase, vamos explorar como usar essa sintaxe para fortalecer ainda mais a segurança.

Managing Secrets in Docker Compose — A Developer's Guide

Esta seção do artigo "Managing Secrets in Docker Compose" detalha a sintaxe longa e como usá-la para restringir o acesso ao segredo dentro do próprio contêiner.

Leia a seção "Controlling access to secrets supplied to your services". Ela explica cada campo da sintaxe longa e mostra um exemplo prático de como encontrar o uid e gid corretos para a imagem do PostgreSQL e aplicar permissões restritivas (0440).

Exemplo com Sintaxe Longa:
Imagine que a imagem do PostgreSQL é executada com o usuário postgres (UID 999). Podemos garantir que apenas esse usuário possa ler o arquivo de senha:

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_DB: mydb
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      # Usando a sintaxe longa
      - source: db_password
        target: db_password  # Nome do arquivo dentro de /run/secrets/
        uid: '999'           # UID do usuário 'postgres'
        gid: '999'           # GID do grupo 'postgres'
        mode: 0400           # Apenas o dono (UID 999) pode ler

secrets:
  db_password:
    file: ./db_password.txt

Essa configuração impede que qualquer outro processo ou usuário dentro do contêiner, mesmo que comprometido, consiga ler o arquivo de senha.

Teste seu entendimento!

Você está configurando um serviço redis no Docker Compose. A imagem oficial do Redis usa a variável de ambiente REDIS_PASSWORD ou, para usar secrets, pode ser iniciada com um comando que aponta para um arquivo de senha (redis-server --requirepass-file /path/to/password).

Seu arquivo local com a senha é redis_pass.txt. Refatore o seguinte docker-compose.yml para usar secrets de forma segura. Descreva os passos.

docker-compose.yml original:

version: '3.8'
services:
  cache:
    image: redis:alpine
    environment:
      REDIS_PASSWORD: "HardcodedPassword123" # RUIM!
Show answer

Aqui estão os passos e o docker-compose.yml resultante:

  1. Criar o arquivo de segredo: Crie um arquivo redis_pass.txt com o conteúdo MyRedisPassword. Adicione redis_pass.txt ao .gitignore.

  2. Modificar o docker-compose.yml:

    • Remova a variável de ambiente REDIS_PASSWORD.
    • Adicione o bloco secrets no nível superior, apontando para redis_pass.txt.
    • Adicione o bloco secrets ao serviço cache, concedendo-lhe acesso.
    • Use a sintaxe longa para renomear o segredo dentro do contêiner para redis_password e defina permissões seguras (o usuário do Redis na imagem alpine tem UID 999).
    • Adicione um command para instruir o Redis a usar o arquivo de senha.

docker-compose.yml refatorado:

version: '3.8'

services:
  cache:
    image: redis:alpine
    # Instrua o redis-server a ler a senha do arquivo de segredo
    command: redis-server --requirepass-file /run/secrets/redis_password
    secrets:
      - source: redis-secret
        target: redis_password # Nome do arquivo que o comando acima espera
        uid: '999'
        gid: '999'
        mode: 0400

secrets:
  redis-secret:
    file: ./redis_pass.txt

Conclusão

Hoje você deu um passo importante para tornar seus ambientes de desenvolvimento mais seguros, alinhando-os com as melhores práticas de produção. Gerenciar segredos corretamente desde o início evita dores de cabeça e vulnerabilidades no futuro.

Principais Pontos:

  • Usar variáveis de ambiente para segredos é conveniente, mas arriscado, pois elas podem ser facilmente inspecionadas e vazadas.
  • Os secrets do Docker Compose montam dados sensíveis como arquivos em um sistema de arquivos em memória (tmpfs), o que é inerentemente mais seguro.
  • Muitas imagens oficiais suportam a convenção _FILE (ex: POSTGRES_PASSWORD_FILE), facilitando a adoção de secrets.
  • A sintaxe longa oferece controle granular sobre as permissões de acesso ao arquivo de segredo dentro do contêiner, implementando o Princípio do Menor Privilégio.
  • Sempre adicione seus arquivos de segredo locais (ex: .txt) ao seu .gitignore.

Na próxima lição, vamos explorar um cenário relacionado: como fornecer segredos necessários durante o processo de build da imagem (por exemplo, um token para acessar um repositório de pacotes privado), usando o recurso --secret do Docker BuildKit.