Supply Chain Security: SBOM, Cosign e Verificação de Imagens: Do Básico ao Avançado Já leu

Supply Chain Security: Protegendo sua Pipeline de Software A segurança da cadeia de suprimentos de software tornou-se uma das maiores preocupações da indústria. Ataques sofisticados como o SolarWinds (2020) e Codecov (2021) demonstraram que comprometer dependências ou componentes intermediários é tão efetivo quanto atacar o alvo final. Neste artigo, exploraremos três pilares essenciais: SBOM (Software Bill of Materials), Cosign para assinatura e verificação, e as práticas de verificação de imagens Docker. Você aprenderá não apenas o quê fazer, mas por quê cada camada importa na defesa em profundidade da sua aplicação. SBOM: Inventariando sua Stack de Software O que é SBOM e por que você precisa Um SBOM (Software Bill of Materials) é um documento estruturado que lista todos os componentes, dependências, versões e licenças utilizadas em um projeto. Pense nele como um "inventário completo" da sua aplicação — semelhante a uma lista de ingredientes em um produto alimentício. Sem essa visibilidade, você não consegue responder perguntas críticas: "Minha aplicação

Supply Chain Security: Protegendo sua Pipeline de Software

A segurança da cadeia de suprimentos de software tornou-se uma das maiores preocupações da indústria. Ataques sofisticados como o SolarWinds (2020) e Codecov (2021) demonstraram que comprometer dependências ou componentes intermediários é tão efetivo quanto atacar o alvo final. Neste artigo, exploraremos três pilares essenciais: SBOM (Software Bill of Materials), Cosign para assinatura e verificação, e as práticas de verificação de imagens Docker. Você aprenderá não apenas o quê fazer, mas por quê cada camada importa na defesa em profundidade da sua aplicação.

SBOM: Inventariando sua Stack de Software

O que é SBOM e por que você precisa

Um SBOM (Software Bill of Materials) é um documento estruturado que lista todos os componentes, dependências, versões e licenças utilizadas em um projeto. Pense nele como um "inventário completo" da sua aplicação — semelhante a uma lista de ingredientes em um produto alimentício. Sem essa visibilidade, você não consegue responder perguntas críticas: "Minha aplicação usa a versão vulnerável da Log4j?" ou "Qual é a cadeia de dependências que leva ao OpenSSL 1.0.2?"

Governos, reguladores (como a NIST nos EUA) e grandes empresas agora exigem SBOMs em contratos de software. Mais importante: você não consegue defender o que não consegue ver. Um SBOM bem feito permite detecção automática de vulnerabilidades conhecidas (CVEs) em toda sua stack, não apenas no código principal.

Formatos: SPDX e CycloneDX

Existem dois padrões principais: SPDX (ISO/IEC 5230) focado em conformidade de licenças, e CycloneDX otimizado para segurança de dependências. Para fins práticos de supply chain security, recomendo CycloneDX. Ambos são estruturados em XML ou JSON.

Vamos gerar um SBOM usando Syft, a ferramenta mais prática atualmente:

# Instalar Syft (para macOS/Linux)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

# Gerar SBOM em formato CycloneDX para uma imagem Docker
syft nginx:latest -o cyclonedx-json > nginx-sbom.json

# Gerar SBOM para um repositório local (Go, Python, Node.js, etc)
syft /caminho/para/seu/projeto -o spdx > project-sbom.spdx

Um exemplo simplificado de SBOM em CycloneDX JSON:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "version": 1,
  "components": [
    {
      "type": "library",
      "name": "requests",
      "version": "2.28.1",
      "purl": "pkg:pypi/requests@2.28.1",
      "licenses": [
        {
          "license": {
            "name": "Apache-2.0"
          }
        }
      ]
    },
    {
      "type": "library",
      "name": "urllib3",
      "version": "1.26.12",
      "purl": "pkg:pypi/urllib3@1.26.12",
      "licenses": [
        {
          "license": {
            "name": "MIT"
          }
        }
      ]
    }
  ]
}

Integrando SBOM na Pipeline CI/CD

A geração de SBOM deve ser automática e obrigatória em cada build. Aqui está um exemplo com GitHub Actions:

name: Build e SBOM

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Build imagem Docker
        run: docker build -t myapp:${{ github.sha }} .

      - name: Instalar Syft
        run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

      - name: Gerar SBOM
        run: |
          syft myapp:${{ github.sha }} \
            -o cyclonedx-json \
            > sbom-${{ github.sha }}.json

      - name: Fazer upload do SBOM
        uses: actions/upload-artifact@v3
        with:
          name: sbom
          path: sbom-*.json

      - name: Verificar vulnerabilidades conhecidas
        run: |
          syft myapp:${{ github.sha }} | grype

Note que usamos Grype (também da Anchore) para verificar vulnerabilidades no SBOM — isso é segurança automatizada.

Cosign: Assinando e Verificando Imagens Docker

Criptografia de Confiança: Por que assinar imagens

Toda imagem Docker publicada em um registro (Docker Hub, ECR, Harbor) pode ser baixada por qualquer pessoa e executada. Sem assinatura criptográfica, não há garantia de autenticidade. Um ataque Man-in-the-Middle (MITM) poderia interceptar sua imagem e substituir por uma maliciosa. Cosign resolve isso usando criptografia assimétrica: você assina a imagem com sua chave privada, e qualquer pessoa verifica com sua chave pública.

Cosign é a ferramenta recomendada pela Linux Foundation e funciona perfeitamente com registros OCI (Docker, ECR, GCR, etc).

Instalação e Configuração de Chaves

# Instalar Cosign (requer Go 1.16+)
wget https://github.com/sigstore/cosign/releases/download/v2.0.0/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign

# Gerar par de chaves (será solicitada uma senha)
cosign generate-key-pair
# Cria: cosign.key (privada) e cosign.pub (pública)

# Exportar a chave pública em formato PEM para compartilhar
cat cosign.pub

Assinando uma Imagem Docker

# Fazer login no registro (Docker Hub, ECR, etc)
docker login -u seu_usuario

# Build e push da imagem
docker build -t seu_usuario/myapp:v1.0.0 .
docker push seu_usuario/myapp:v1.0.0

# Assinar a imagem (será solicitada a senha da chave privada)
cosign sign --key cosign.key seu_usuario/myapp:v1.0.0

A assinatura é armazenada como uma imagem separada no registro (exemplo: seu_usuario/myapp:v1.0.0.sig). Você pode verificar os metadados assinados:

# Verificar a assinatura (usa a chave pública automaticamente)
cosign verify --key cosign.pub seu_usuario/myapp:v1.0.0

Saída esperada:

[{"critical":{"identity":{"docker-reference":"seu_usuario/myapp"},...}}]

Assinando com Keyless (Sigstore)

A forma mais moderna é usar Keyless Signing via Sigstore, eliminando a necessidade de gerenciar chaves privadas localmente:

# Login com sua conta do GitHub/Google
cosign login ghcr.io

# Assinar sem chave local (usa OIDC do GitHub)
COSIGN_EXPERIMENTAL=1 cosign sign ghcr.io/seu_usuario/myapp:v1.0.0

# Verificar sem chave pública explícita
COSIGN_EXPERIMENTAL=1 cosign verify ghcr.io/seu_usuario/myapp:v1.0.0

Internamente, Cosign usa certificados OIDC do provedor (GitHub) e Rekor (um registro de transparência) para auditoria imutável.

Verificação de Imagens: Policy-as-Code com Kyverno

Por que apenas assinar não é suficiente

Você pode assinar todas as imagens, mas se seu cluster Kubernetes aceitasse uma imagem não assinada, o atacante poderia simplesmente fazer bypass da assinatura. Você precisa de policy enforcement automático — um sistema que rejeite qualquer pod que use uma imagem não assinada ou não confiável.

Kyverno é um admission controller Kubernetes que funciona como um firewall de imagens. Ele verifica políticas antes de qualquer container ser executado.

Instalando Kyverno

# Instalar Kyverno no cluster
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace

# Aguardar o deployment
kubectl rollout status deployment/kyverno-admission-controller -n kyverno

Policy: Requerer Imagens Assinadas

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
  namespace: kyverno
spec:
  validationFailureAction: enforce  # audit = logging apenas, enforce = bloqueia
  rules:
  - name: verify-image-signature
    match:
      resources:
        kinds:
        - Pod
    verifyImages:
    - imageReferences:
      - "seu_usuario/*"
      - "gcr.io/seu-projeto/*"
      attestors:
      - count: 1
        entries:
        - keys:
            publicKeys: |
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
              -----END PUBLIC KEY-----
            signatureAlgorithm: sha256

Testando a Policy

# Criar um pod com imagem assinada (vai passar)
kubectl run test-signed --image=seu_usuario/myapp:v1.0.0 \
  --dry-run=server

# Tentar criar pod com imagem não assinada (vai falhar)
kubectl run test-unsigned --image=nginx:latest \
  --dry-run=server
# Error: admission webhook "mutate.kyverno.io" denied the request

Policy Avançada: Verificar SBOM Anexado

Cosign pode anexar SBOMs à imagem e Kyverno pode verificá-los:

# Anexar SBOM à imagem
cosign attach sbom --sbom sbom-v1.0.0.json seu_usuario/myapp:v1.0.0

# Verificar o SBOM anexado
cosign download sbom seu_usuario/myapp:v1.0.0

Uma policy que verifica presença de SBOM:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-sbom
spec:
  validationFailureAction: enforce
  rules:
  - name: verify-sbom-exists
    match:
      resources:
        kinds:
        - Pod
    verifyImages:
    - imageReferences:
      - "*"
        attestations:
        - name: sbom
          predicateType: https://cyclonedx.org/schema/ext/vulnerability/4.0/vulnerability.json
          conditions:
          - all:
            - key: "{{ components[0].name }}"
              operator: Pattern
              value: "?*"  # verifica se existe ao menos 1 componente

Integrando Tudo: Pipeline End-to-End

Uma pipeline real deve:
1. Gerar SBOM automaticamente no build
2. Verificar vulnerabilidades do SBOM contra bancos de CVE
3. Assinar a imagem com Cosign
4. Anexar SBOM e atestações à imagem
5. Verificar assinatura no cluster com Kyverno

Aqui está um exemplo completo com GitHub Actions + Kyverno:

name: Secure Build Pipeline

on: 
  push:
    tags:
      - 'v*'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  secure-build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
    - uses: actions/checkout@v3

    - name: Setup Cosign
      uses: sigstore/cosign-installer@v3
      with:
        cosign-release: 'v2.0.0'

    - name: Setup Syft
      run: |
        curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | \
          sh -s -- -b /usr/local/bin

    - name: Build imagem Docker
      run: |
        docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} .

    - name: Gerar SBOM
      run: |
        syft ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} \
          -o cyclonedx-json \
          > sbom.json

    - name: Verificar vulnerabilidades
      run: |
        curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | \
          sh -s -- -b /usr/local/bin
        grype ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} \
          --fail-on high

    - name: Fazer login no GHCR
      uses: docker/login-action@v2
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Push imagem
      run: |
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}

    - name: Assinar imagem com Cosign (Keyless)
      run: |
        cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
      env:
        COSIGN_EXPERIMENTAL: 1

    - name: Anexar SBOM à imagem
      run: |
        cosign attach sbom --sbom sbom.json \
          ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
      env:
        COSIGN_EXPERIMENTAL: 1

    - name: Gerar atestação de build (provenance)
      uses: actions/attest-build-provenance@v1
      with:
        subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        subject-digest: ${{ steps.push.outputs.digest }}
        push-to-registry: true

No cluster Kubernetes, a policy Kyverno automaticamente bloqueará qualquer imagem não assinada ou vulnerável.

Conclusão

Os três pilares da segurança supply chain que aprendemos — SBOM para visibilidade, Cosign para autenticidade e Kyverno para enforcement — formam uma defesa em profundidade que detém ataques em múltiplas camadas. Primeiro, você conhece exatamente o que está rodando (SBOM). Segundo, você garante que ninguém alterou sua imagem no caminho (Cosign). Terceiro, você rejeita qualquer coisa suspeita automaticamente no cluster (Kyverno). Essa abordagem transforma segurança de um problema reativo para proativo — você não espera ser hacked, você previne que seja hacked. A automatização completa dessa pipeline via CI/CD é o que separa equipes defensoras de equipes vulneráveis no mundo atual.

Referências


Artigos relacionados