Sigstore e cosign: assinatura de imagens Docker para supply chain segura

1. Fundamentos da Segurança na Supply Chain de Containers

A adoção de containers transformou a entrega de software, mas também introduziu novos vetores de ataque na supply chain. Uma imagem Docker pode ser alterada silenciosamente entre o momento da construção e o deploy em produção. Ataques como dependency confusion (onde pacotes maliciosos são inseridos em repositórios públicos), image poisoning (imagens legítimas contaminadas com malware) e man-in-the-middle (interceptação de tráfego durante o pull) são ameaças reais.

Hashes SHA256 e conexões TLS oferecem proteção parcial. Hashes garantem integridade do conteúdo, mas não autenticam a origem. TLS protege o canal de comunicação, mas não impede que um atacante com acesso ao registro substitua uma imagem por outra maliciosa. Para garantir que uma imagem foi gerada por uma identidade confiável, é necessário um mecanismo de assinatura criptográfica que vincule a imagem a um signatário verificável.

2. Introdução ao Sigstore: ecossistema de assinatura criptográfica

Sigstore é um ecossistema open source que simplifica a assinatura e verificação de artefatos de software. Seus componentes principais são:

  • Fulcio: autoridade certificadora que emite certificados X.509 de curta duração baseados em identidade OIDC (OpenID Connect).
  • Rekor: ledger transparente e imutável que registra todas as assinaturas, permitindo auditoria pública.
  • Cosign: ferramenta de linha de comando para assinar e verificar containers e outros artefatos.

Diferente da assinatura tradicional com PGP (que exige gerenciamento complexo de chaves), o Sigstore adota o modelo keyless: a identidade do desenvolvedor (ex: email vinculado ao GitHub, Google ou Microsoft) é usada para obter um certificado temporário do Fulcio. Esse certificado expira em minutos, eliminando a necessidade de armazenar chaves privadas de longo prazo. A assinatura é registrada no Rekor, criando um histórico público de quem assinou o quê e quando.

3. Instalação e configuração do Cosign

A instalação do cosign pode ser feita de várias formas. No Linux e macOS, o método mais simples é via script oficial:

# Linux/macOS
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign

No macOS com Homebrew:

brew install cosign

No Windows, via Chocolatey:

choco install cosign

Para verificar a instalação:

cosign version

A configuração de ambiente para autenticação OIDC depende do provedor. Para GitHub Actions, não é necessário configurar nada adicional — o token OIDC é injetado automaticamente no ambiente. Para uso local, o cosign abrirá o navegador para autenticação via provedor suportado (GitHub, Google, Microsoft).

4. Assinatura de imagens Docker com Cosign

Assinatura keyless (recomendada para CI/CD)

O comando básico para assinar uma imagem com identidade OIDC é:

cosign sign ghcr.io/seu-usuario/sua-imagem:tag

O cosign solicitará autenticação via navegador. Após autenticação, ele obtém um certificado do Fulcio, assina a imagem e registra a transação no Rekor. A assinatura é armazenada como uma tag ou anotação no registro de containers.

Assinatura com par de chaves (para ambientes offline)

Para cenários sem acesso à internet ou com políticas restritivas, é possível gerar um par de chaves:

cosign generate-key-pair

Isso cria cosign.key (privada) e cosign.pub (pública). A assinatura é feita com:

cosign sign --key cosign.key ghcr.io/seu-usuario/sua-imagem:tag

A chave privada deve ser armazenada com segurança (ex: cofre de senhas, secrets do CI/CD).

5. Verificação de assinaturas e políticas de segurança

Para verificar manualmente uma imagem assinada:

cosign verify ghcr.io/seu-usuario/sua-imagem:tag

A saída exibe o certificado do signatário, incluindo o email e o emissor OIDC:

Verification for ghcr.io/seu-usuario/sua-imagem:tag --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified identity
Certificate subject:  usuario@example.com
Certificate issuer URL:  https://github.com/login/oauth

Para verificar atestações (metadados adicionais como SBOM, resultados de scan):

cosign verify-attestation --type slsaprovenance ghcr.io/seu-usuario/sua-imagem:tag

Em clusters Kubernetes, a verificação pode ser automatizada com admission controllers. O Kyverno, por exemplo, pode ser configurado para bloquear pods que usem imagens não assinadas:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image
spec:
  validationFailureAction: enforce
  rules:
    - name: verify-cosign-signature
      match:
        resources:
          kinds:
            - Pod
      verifyImages:
        - image: "ghcr.io/seu-usuario/*"
          key: |-
            -----BEGIN PUBLIC KEY-----
            ...
            -----END PUBLIC KEY-----

6. Automatizando assinatura em pipelines CI/CD

Um exemplo completo de GitHub Actions que assina automaticamente imagens após o build:

name: Build, Sign and Push

on:
  push:
    branches: [main]

jobs:
  build-and-sign:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Install cosign
        uses: sigstore/cosign-installer@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
          docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Sign the image with Cosign (keyless)
        run: |
          cosign sign ghcr.io/${{ github.repository }}:${{ github.sha }}

O token OIDC é obtido automaticamente pelo GitHub Actions. A assinatura é registrada no Rekor público, garantindo transparência.

Boas práticas incluem:
- Usar secrets para tokens de registro.
- Rotacionar credenciais de registro regularmente.
- Segregar ambientes (dev/staging/prod) com diferentes políticas de assinatura.

7. Cenários avançados e troubleshooting

Assinatura de imagens multi-arquitetura

Imagens que suportam múltiplas arquiteturas (amd64, arm64) usam manifest lists. Para assiná-las:

cosign sign --key cosign.key ghcr.io/seu-usuario/sua-imagem:tag

O cosign detecta automaticamente o tipo de manifest e assina a lista inteira. Para verificar, use o mesmo comando cosign verify.

Erros comuns

Token OIDC expirado: ocorre quando o fluxo keyless demora demais. Solução: refazer a autenticação ou usar --yes para pular confirmações.

Conflitos de registro: alguns registros (como Docker Hub) exigem configuração extra de OIDC. Verifique se o registro suporta anotações OCI.

Ambientes corporativos: para empresas que não podem usar o Fulcio e Rekor públicos, é possível hospedar a própria stack Sigstore:

# Exemplo com docker-compose (simplificado)
services:
  fulcio:
    image: gcr.io/projectsigstore/fulcio:latest
    ports:
      - "5555:5555"
  rekor:
    image: gcr.io/projectsigstore/rekor-server:latest
    ports:
      - "3000:3000"

Configure o cosign para apontar para sua instância:

export SIGSTORE_FULCIO_URL=http://localhost:5555
export SIGSTORE_REKOR_URL=http://localhost:3000

Referências