Supply Chain Security: Protegendo Imagens Docker com Notary e Sigstore (Cosign)

Introdução à Segurança da Cadeia de Suprimentos de Software

No ecossistema de desenvolvimento moderno, centrado em contêineres e microsserviços, a cadeia de suprimentos de software (Software Supply Chain) tornou-se um vetor de ataque cada vez mais visado. desde a imagem base do sistema operacional até as bibliotecas de terceiros, cada componente introduzido em uma imagem docker representa uma potencial superfície de ataque. A manipulação de uma única imagem em um registro público ou privado pode ter consequências devastadoras, comprometendo aplicações inteiras em produção. Diante deste cenário, garantir a autenticidade e a integridade dos artefatos de software, como as imagens de contêiner, não é mais uma opção, mas uma necessidade fundamental.

A assinatura de imagens é a principal contramedida para mitigar esses riscos. Ao aplicar uma assinatura digital a uma imagem, criamos uma prova criptográfica de sua origem (autenticidade) e garantimos que ela não foi alterada desde sua criação (integridade). Este artigo explora duas das mais proeminentes tecnologias para esta finalidade: Notary, a solução estabelecida da CNCF, e Sigstore (com sua ferramenta Cosign), a abordagem moderna que está revolucionando a segurança de artefatos de código aberto.

O Problema Central: Integridade e Autenticidade em Registros de Contêineres

Uma imagem Docker é composta por uma série de camadas, cada uma representando uma alteração no sistema de arquivos. O processo de construção de uma imagem, seu armazenamento em um registro (como o Docker Hub, ACR, GCR ou ECR) e sua subsequente implantação em um orquestrador como o Kubernetes formam uma cadeia de suprimentos. Os pontos de vulnerabilidade nesta cadeia são múltiplos:

  • Imagem Base Comprometida: Utilização de uma imagem base que já contém malware ou vulnerabilidades.
  • Build Server Comprometido: Um invasor pode obter acesso ao ambiente de CI/CD e injetar código malicioso durante o processo de build da imagem.
  • Ataques Man-in-the-Middle (MITM): Interceptação da comunicação entre o cliente Docker e o registro para substituir a imagem legítima por uma maliciosa.
  • Registro Comprometido: Acesso não autorizado a um registro pode permitir a substituição de tags de imagens, fazendo com que um `latest` aponte para uma versão adulterada.

A assinatura digital aborda diretamente esses problemas. O processo utiliza criptografia de chave assimétrica (pública/privada). O desenvolvedor ou o sistema de CI/CD assina o digest (hash SHA256) da imagem com uma chave privada. Qualquer pessoa que possua a chave pública correspondente pode então verificar se a assinatura é válida para aquele digest de imagem. Se um único bit da imagem for alterado, o digest mudará e a verificação da assinatura falhará, alertando para a adulteração.

Notary: A Abordagem Clássica Baseada em TUF

Notary é um projeto graduado da Cloud Native Computing Foundation (CNCF) projetado para garantir a confiança sobre coleções de conteúdo digital. Para imagens Docker, ele é mais conhecido através do Docker Content Trust (`DOCKER_CONTENT_TRUST=1`). A arquitetura do Notary é robusta e baseia-se no The Update Framework (TUF), um framework projetado para proteger mecanismos de atualização de software.

Arquitetura e Componentes do Notary

O Notary opera com um servidor próprio e um cliente. Sua segurança é garantida por um sistema de papéis (roles) e chaves, cada um com responsabilidades distintas:

  • Root Role: A raiz da confiança. A chave raiz é a mais importante e deve ser mantida offline. Ela é usada para assinar as chaves dos outros papéis.
  • Targets Role: Assina os artefatos individuais (as imagens Docker). Esta é a chave mais utilizada no dia a dia.
  • Snapshot Role: Assina um arquivo de “snapshot” que lista as versões atuais de todos os metadados, protegendo contra ataques de replay.
  • Timestamp Role: Fornece frescor, indicando que os metadados do repositório são recentes e não uma versão antiga e potencialmente vulnerável.

Fluxo de Trabalho com Notary

O processo de uso do Notary, embora seguro, é complexo:

  1. Inicialização do Repositório: O comando `notary init` é usado para criar os metadados iniciais e gerar a chave raiz.
  2. Geração e Delegação de Chaves: A gestão manual das chaves para cada papel é necessária. A rotação dessas chaves é um processo manual e crítico.
  3. Assinatura e Push: Ao fazer `docker push` com o Docker Content Trust ativado, o cliente Docker interage com o Notary para assinar a imagem com a chave de `targets`.
  4. Verificação: Ao fazer `docker pull`, o cliente verifica todas as assinaturas e a cadeia de confiança, desde o timestamp até a raiz, antes de baixar a imagem.

A principal barreira para a adoção em massa do Notary v1 tem sido sua complexidade operacional. A necessidade de gerenciar um servidor Notary separado e a complexa hierarquia de chaves representam um fardo significativo para as equipes de DevOps.

Sigstore e Cosign: A Revolução na Assinatura de Software

Sigstore é um projeto da Linux Foundation que visa ser o “Let’s Encrypt para a assinatura de software”. Seu objetivo é tornar a assinatura de artefatos de software fácil, transparente e acessível a todos. Cosign é a principal ferramenta dentro do ecossistema Sigstore, focada especificamente na assinatura e verificação de contêineres e outros artefatos OCI (Open Container Initiative).

Os Pilares do Ecossistema Sigstore

Sigstore simplifica o processo através de três componentes principais que funcionam como uma infraestrutura de bem público (public good infrastructure):

  • Cosign: A ferramenta de linha de comando para assinar, verificar e armazenar imagens em registros OCI.
  • Fulcio: Uma autoridade certificadora (CA) de raiz que emite certificados de assinatura de código de curta duração. Em vez de gerenciar chaves de longo prazo, os desenvolvedores se autenticam usando provedores de identidade OpenID Connect (OIDC) existentes (como contas do Google, GitHub ou Microsoft) para obter um certificado válido por apenas alguns minutos.
  • Rekor: Um log de transparência público e imutável. Cada assinatura gerada é registrada no Rekor, criando uma trilha de auditoria inviolável. Isso permite que qualquer pessoa verifique quando um artefato foi assinado e por quem.

Implementando a Assinatura com Cosign: Abordagem Baseada em Chaves

Mesmo que o modo “keyless” seja o grande diferencial, Cosign suporta o fluxo de trabalho tradicional baseado em pares de chaves, que é mais simples que o do Notary.

Passo 1: Instalação do Cosign

A instalação do Cosign pode ser feita através de gerenciadores de pacotes como Homebrew ou Go:

# Com Go
go install github.com/sigstore/cosign/cmd/cosign@latest

# Com Homebrew (macOS/Linux)
brew install cosign

Passo 2: Geração do Par de Chaves

Gere um par de chaves pública/privada. A chave privada será protegida por uma senha.

cosign generate-key-pair

Este comando criará dois arquivos: `cosign.key` (a chave privada) e `cosign.pub` (a chave pública).

Passo 3: Assinando uma Imagem

Assine sua imagem Docker e envie a assinatura para o mesmo registro OCI onde a imagem está armazenada. Cosign armazena a assinatura como um artefato separado, vinculado ao digest da imagem original.

# Exemplo de assinatura
cosign sign --key cosign.key seu-registro/seu-usuario/sua-imagem:tag

Você será solicitado a fornecer a senha da sua chave privada. Após a execução, a assinatura é enviada ao registro.

Passo 4: Verificando a Assinatura

Qualquer pessoa com acesso à imagem e à chave pública pode verificar sua integridade.

# Exemplo de verificação
cosign verify --key cosign.pub seu-registro/seu-usuario/sua-imagem:tag

Se a verificação for bem-sucedida, Cosign retornará um código de saída 0 e exibirá os dados da assinatura. Caso contrário, indicará uma falha na verificação.

O Poder do “Keyless Signing” com Cosign e OIDC

A verdadeira inovação do Sigstore é a assinatura sem chave (keyless). Este método elimina a necessidade de os desenvolvedores gerenciarem e protegerem chaves privadas de longa duração. O fluxo é o seguinte:

  1. O desenvolvedor executa o comando `cosign sign`.
  2. Cosign abre um navegador, solicitando que o desenvolvedor se autentique em um provedor OIDC (ex: login com GitHub).
  3. Após a autenticação bem-sucedida, o provedor OIDC retorna um token de identidade para o Cosign.
  4. Cosign envia este token para o Fulcio, a CA do Sigstore.
  5. Fulcio valida o token e emite um certificado de assinatura de código de curta duração (geralmente 10 minutos) que vincula a identidade do desenvolvedor (ex: `[email protected]`) a uma chave efêmera gerada no momento.
  6. Cosign usa essa chave e certificado efêmeros para assinar o digest da imagem.
  7. A assinatura, o certificado e um comprovante de inclusão no log de transparência Rekor são enviados para o registro OCI.

Para usar este modo, o comando é ainda mais simples:

# Assinatura Keyless
cosign sign seu-registro/seu-usuario/sua-imagem:tag

# Verificação Keyless
cosign verify seu-registro/seu-usuario/sua-imagem:tag

Durante a verificação, Cosign recupera a assinatura e o certificado do registro, valida a cadeia de certificados até a raiz do Fulcio e verifica no Rekor se a assinatura foi registrada durante o período de validade do certificado. Este processo oferece uma segurança robusta sem o fardo do gerenciamento de chaves.

Análise Comparativa: Notary vs. Sigstore (Cosign)

Ambas as ferramentas visam resolver o mesmo problema, mas suas abordagens e implicações são muito diferentes.

Facilidade de Uso e Experiência do Desenvolvedor

  • Notary: Curva de aprendizado íngreme. Requer configuração complexa, gerenciamento manual de chaves e papéis, e uma compreensão profunda do framework TUF.
  • Cosign: Projetado para simplicidade. O modo keyless remove quase toda a sobrecarga de gerenciamento de chaves, tornando a assinatura de software acessível a qualquer desenvolvedor.

Infraestrutura Necessária

  • Notary: Requer a implantação e manutenção de um servidor Notary, além de um banco de dados para armazenar os metadados de confiança.
  • Cosign: Utiliza o próprio registro OCI para armazenar assinaturas, eliminando a necessidade de infraestrutura adicional. Para o modo keyless, ele depende da infraestrutura pública e gratuita do Sigstore (Fulcio e Rekor).

Gerenciamento de Chaves

  • Notary: O gerenciamento e a rotação de chaves são totalmente manuais e representam um risco operacional significativo se não forem feitos corretamente.
  • Cosign: O modo keyless elimina o gerenciamento de chaves de longo prazo. O modo baseado em chaves é mais simples que o Notary, sem a complexa hierarquia de papéis.

Integração com CI/CD e Políticas de Admissão no Kubernetes

A verdadeira força da assinatura de imagens é realizada quando integrada a um pipeline de CI/CD e reforçada por políticas em tempo de execução.

Automação no CI/CD

Em plataformas como GitHub Actions, GitLab CI ou Jenkins, o processo de assinatura pode ser automatizado. Com o modo keyless do Cosign, o pipeline pode usar a identidade OIDC do próprio serviço de CI (por exemplo, a identidade de um job do GitHub Actions) para assinar a imagem. Isso garante que apenas imagens construídas e validadas dentro do pipeline de CI/CD sejam assinadas.

Aplicação de Políticas com Admission Controllers

No Kubernetes, a segurança da cadeia de suprimentos é aplicada através de Admission Controllers. Ferramentas como Kyverno, OPA Gatekeeper ou a própria integração nativa do Sigstore permitem a criação de políticas que impedem a execução de contêineres que não atendam a critérios de segurança específicos. Por exemplo, uma política Kyverno pode ser escrita para:

  • Exigir que todas as imagens provenientes de um registro específico sejam assinadas.
  • Verificar se a assinatura foi feita por uma identidade específica (ex: o e-mail do time de engenharia ou a identidade do pipeline de CI/CD).
  • Rejeitar qualquer imagem cuja verificação de assinatura falhe.

Essa combinação de assinatura na origem (CI/CD) e verificação na implantação (Kubernetes) cria um ciclo de confiança fechado e auditável, fortalecendo drasticamente a postura de segurança de qualquer aplicação nativa da nuvem.