Sigstore e Cosign: Assinar Contentores e Artefactos na PME

1. Introdução — O Problema da Cadeia de Abastecimento

Os ataques à cadeia de abastecimento de software tornaram-se uma das principais ameaças para as organizações modernas. Incidentes como o comprometimento da SolarWinds ou os problemas no ecossistema npm mostraram que uma imagem de contentor não assinada é um vector de ataque directo: se um atacante conseguir substituir uma imagem num registo, todos os serviços que a usam ficam comprometidos sem que ninguém dê conta.

Para as PMEs portuguesas, este risco é particularmente relevante. A directiva NIS2 e o Cyber Resilience Act exigem que as organizações demonstrem proveniência e integridade dos componentes de software que utilizam e comercializam. Assinar imagens de contentores e artefactos não é apenas uma boa prática técnica — é um requisito de conformidade regulatória.

O problema concreto é o seguinte: quando se faz o docker pull de uma imagem, não há qualquer garantia de que essa imagem foi produzida por quem se pensa, que o conteúdo não foi alterado em trânsito, ou que não foi substituída no registo. O Docker Content Trust (DCT) parcial resolve isto, mas é proprietário da Docker e não cobre artefactos fora do ecossistema Docker. O Sigstore surge como resposta de código aberto, transparente e auditável.

ℹ Porquê assinar agora? Sem assinatura verificável, qualquer PME que faça implantação a partir de um registo partilhado (Docker Hub, GitHub Container Registry, Harbor) está a confiar cegamente na infraestrutura de terceiros. Um único comprometimento do registo propaga-se a todos os serviços da organização.

2. O que é o Sigstore

O Sigstore é uma estrutura de código aberto, apoiada pela Open Source Security Foundation (OpenSSF), que fornece assinatura digital transparente para artefactos de software. Foi criada para resolver um problema fundamental: tornar a assinatura de software tão simples e ubíqua como a assinatura de commits com GPG, mas sem a complexidade operacional que torna o GPG difícil de adoptar.

A estrutura é composta por três componentes principais que trabalham em conjunto:

Componente Função Analogia
Cosign Ferramenta CLI para assinar e verificar artefactos A caneta que assina o documento
Rekor Registo de transparência (transparency log) imutável e auditável O cartório que regista a assinatura
Fulcio Autoridade de certificados de curta duração (OIDC-based) O cartão de cidadão digital temporário

O fluxo é o seguinte: quando se assina um artefacto com o Cosign, o Fulcio emite um certificado de curta duração (10 minutos) baseado na identidade OIDC do utilizador (por exemplo, a conta GitHub ou Google). A assinatura é armazenada no registo de contentores junto à imagem, e um registo público da assinatura é gravado no Rekor. Isto significa que qualquer pessoa pode, a qualquer momento, verificar quem assinou o quê, quando, e que a imagem não foi alterada desde então.

A grande inovação do Sigstore é a assinatura keyless. Em vez de gerir chaves privadas de longa duração (que se perdem, são comprometidas ou exigem infraestrutura PKI complexa), o Sigstore usa identidades OIDC existentes. Isto reduz drasticamente a barreira de entrada para PMEs que não têm uma PKI própria.

ℹ Sigstore vs GPG O GPG exige gestão manual de chaves, troca de chaves públicas entre partes, e não tem registo de transparência. O Sigstore elimina a gestão de chaves de longa duração e fornece um registo público auditável onde todas as assinaturas são gravadas e pesquisáveis.

3. Cosign: Assinar e Verificar Imagens

O Cosign é a ferramenta CLI principal do ecossistema Sigstore. Permite assinar imagens de contentores, blobs, ficheiros arbitrários e gerar atestações (attestations) — declarações assinadas sobre o conteúdo ou processo de construção de um artefacto.

3.1 Instalação

O Cosign pode ser instalado a partir de binários directamente do GitHub ou via gestor de pacotes:

# Instalar a partir do binário (Linux x86_64)
COSIGN_VERSION="2.4.1"
wget -O cosign https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64
chmod +x cosign
sudo mv cosign /usr/local/bin/

# Verificar a instalação
cosign version
# Output esperado:
#   GitVersion: 2.4.1
#   GitCommit: ...
#   BuildDate: ...

3.2 Assinar com chave própria (key-pair)

Para começar, o método mais simples é gerar um par de chaves e assinar a imagem localmente. Isto é útil quando se quer controlo total das chaves, sem depender de identidades OIDC:

# Gerar par de chaves (pede uma password para proteger a chave privada)
cosign generate-key-pair
# Cria: cosign.key (privada) e cosign.pub (pública)

# Assinar uma imagem no registo
export COSIGN_PASSWORD="minha-password-forte"
cosign sign --key cosign.key registry.exemplo.pt/app:v1.0.0

# A assinatura é armazenada no registo como:
#   registry.exemplo.pt/app:sha256-.sig

Para verificar a assinatura noutro ambiente, usa-se a chave pública:

# Verificar a assinatura com a chave pública
cosign verify --key cosign.pub registry.exemplo.pt/app:v1.0.0

# Output esperado:
#   Verification for registry.exemplo.pt/app:v1.0.0 --
#   The following checks were performed on each of these signatures:
#     - The cosign claims were validated
#     - The signatures were verified against the specified public key

3.3 Assinatura keyless (sem gestão de chaves)

A assinatura keyless é o método recomendado pelo Sigstore. Em vez de gerar e gerir chaves, o Cosign autentica-se via OIDC (GitHub, Google, GitLab) e o Fulcio emite um certificado de curta duração. Não há chave privada para perder ou comprometer:

# Assinatura keyless — abre navegador para autenticação OIDC
cosign sign registry.exemplo.pt/app:v1.0.0

# Num pipeline CI/CD (sem navegador), usar variáveis de ambiente:
export COSIGN_EXPERIMENTAL=1
cosign sign --identity-token="$OIDC_TOKEN" registry.exemplo.pt/app:v1.0.0

# Verificar keyless — especificar a identidade esperada
cosign verify registry.exemplo.pt/app:v1.0.0 \
  --certificate-identity="[email protected]" \
  --certificate-oidc-issuer="https://accounts.google.com"

ℹ Identidade vs Chave Na verificação keyless, em vez de fornecer uma chave pública, especifica-se a identidade esperada (e-mail) e o emissor OIDC. O Cosign valida que o certificado foi emitido para essa identidade por aquele emissor, e que a assinatura corresponde. Se alguém tentar assinar com uma identidade diferente, a verificação falha.

3.4 Atestações (Attestations)

As atestações são declarações assinadas sobre um artefacto — não apenas a sua existência, mas factos sobre ele. Por exemplo, pode-se atestar que uma imagem foi construída a partir de um commit específico, que passou nos testes, ou que tem um SBOM associado:

# Atestar que a imagem passou nos testes CI
echo '{"schemaType":"ci-result","passed":true}' | \
  cosign attest --predicate - \
  --type ci-result \
  registry.exemplo.pt/app:v1.0.0

# Atestar um SBOM (Software Bill of Materials)
cosign attest --predicate sbom.spdx.json \
  --type spdxjson \
  registry.exemplo.pt/app:v1.0.0

# Verificar uma atestação
cosign verify-attestation registry.exemplo.pt/app:v1.0.0 \
  --key cosign.pub \
  --type spdxjson

4. Integração com CI/CD

A assinatura deve ser automatizada na pipeline CI/CD — nunca feita manualmente. O Cosign integra-se naturalmente com GitHub Actions, GitLab CI, Jenkins e outras pipelines. O exemplo abaixo usa GitHub Actions com assinatura keyless, a aproveitar o OIDC token nativo do GitHub:

# .github/workflows/build-and-sign.yml
name: Build, Sign and Push

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write   # Necessário para OIDC / keyless signing
  packages: write

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

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

      - name: Construir e carregar imagem
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Instalar Cosign
        uses: sigstore/cosign-installer@v3

      - name: Assinar imagem (keyless via OIDC do GitHub)
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

      - name: Atestar SBOM
        run: |
          # Gerar SBOM com syft
          syft ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }} \
            -o spdx-json > sbom.spdx.json
          cosign attest --yes \
            --predicate sbom.spdx.json \
            --type spdxjson \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

Para GitLab CI, o princípio é idêntico. A diferença principal é a forma de obter o token OIDC:

# .gitlab-ci.yml
stages:
  - build
  - sign

build_image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

sign_image:
  stage: sign
  image: alpine:latest
  needs: [build_image]
  variables:
    COSIGN_EXPERIMENTAL: "1"
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  before_script:
    - wget -qO /usr/local/bin/cosign \
        https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
    - chmod +x /usr/local/bin/cosign
  script:
    - cosign sign --yes --identity-token $SIGSTORE_ID_TOKEN \
        $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

⚠ Permissão id-token:write Sem a permissão id-token: write no GitHub Actions, o OIDC não funciona e a assinatura keyless falha silenciosamente. Isto é o erro mais comum ao configurar a integração pela primeira vez.

5. Verificação em Runtime no Kubernetes

Assinar imagens é apenas metade do trabalho. Para que as assinaturas sejam úteis, é preciso verificar antes de as imagens serem admitidas no cluster Kubernetes. As duas ferramentas principais para isto são o Kyverno e o OPA (Open Policy Agent) com Gatekeeper.

5.1 Kyverno — verifyImages

O Kyverno é um motor de políticas nativo do Kubernetes que pode verificar assinaturas de imagens antes de as admitir. A política abaixo bloqueia qualquer imagem que não esteja assinada com a chave pública especificada:

# verify-signatures.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-cosign-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "ghcr.io/minha-empresa/*"
      attestors:
      - count: 1
        entries:
        - keys:
            publicKeys: |
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET...
              -----END PUBLIC KEY-----

Para aplicar e testar a política:

# Instalar Kyverno via Helm
helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno -n kyverno --create-namespace

# Aplicar a política de verificação
kubectl apply -f verify-signatures.yaml

# Testar: imagem assinada deve ser admitida
kubectl run app-valid --image=ghcr.io/minha-empresa/app:v1.0.0
# Pod/app-valid created

# Testar: imagem não assinada deve ser bloqueada
kubectl run app-invalid --image=ghcr.io/minha-empresa/app:unsigned
# Error from server: admission webhook denied the request:
#   failed to verify image: no matching signatures

5.2 OPA Gatekeeper

Para organizações que já usam OPA Gatekeeper, a verificação de imagens assinadas é feita através do controller sigstore-policy-controller, que estende o Gatekeeper com verificação de assinaturas Cosign:

# Constraint de exemplo com Gatekeeper + sigstore-policy-controller
apiVersion: policy.sigstore.dev/v1beta1
kind: Policy
metadata:
  name: require-signature
spec:
  images:
  - glob: "ghcr.io/minha-empresa/**"
  authorities:
  - key:
      data: |
        -----BEGIN PUBLIC KEY-----
        MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET...
        -----END PUBLIC KEY-----

A escolha entre Kyverno e OPA depende do contexto: o Kyverno é mais simples de configurar e usa YAML nativo do Kubernetes (mais legível para equipas sem experiência em OPA/Rego). O OPA Gatekeeper oferece maior flexibilidade e integra-se com políticas existentes escritas em Rego, sendo preferido por organizações com maturidade em policy-as-code.

Critério Kyverno OPA Gatekeeper
Linguagem de políticas YAML nativo Kubernetes Rego (curva de aprendizagem)
verifyImages nativo Sim, built-in Via policy-controller externo
Complexidade inicial Baixa Média-alta
Ideal para PMEs a começar Organizações com OPA existente

6. Boas Práticas para PME

Para uma PME portuguesa a adoptar Sigstore, recomenda-se uma abordagem faseada. Não se deve tentar assinar e verificar tudo ao mesmo tempo — isso cria atrito com as equipas de desenvolvimento e pode bloquear implantações legítimas.

Fase 1 — Assinar em CI/CD. Configurar a assinatura keyless na pipeline CI/CD para todas as imagens produzidas internamente. Nesta fase, a verificação ainda não bloqueia implantações — apenas regista as assinaturas. Isto permite ganhar confiança e construir o histórico no Rekor sem disrupção.

Fase 2 — Verificar em staging. Configurar o Kyverno ou OPA no cluster de staging com validationFailureAction: Audit (modo auditoria). Neste modo, as violações são registadas mas não bloqueiam. Permite identificar imagens não assinadas sem interromper serviços.

Fase 3 — Enforce em produção. Após validar que todas as imagens em staging estão assinadas, mudar para Enforce no cluster de produção. A partir deste momento, apenas imagens assinadas são admitidas.

ℹ SBOM obrigatório Combinar a assinatura de imagens com um SBOM assinado através de atestações Cosign cria uma cadeia de confiança completa: sabe-se quem assinou, que a imagem não foi alterada, e exactamente que componentes ela contém. Isto é fundamental para auditoria NIS2.

Outras práticas essenciais:

  • Usar registos privados. Assinar imagens não substitui a necessidade de um registo privado (Harbor, GitHub Container Registry). As assinaturas Cosign são armazenadas no mesmo registo que as imagens.
  • Guardar chaves privadas em segredo. Se se usar chaves próprias (não keyless), proteger a chave privada com um gestor de segredos (Vault, AWS Secrets Manager, ou GitHub Actions secrets). Nunca carregar cosign.key para um repositório Git.
  • Pin por digest, não por tag. Referenciar imagens por @sha256:... em produção garante que a tag não pode ser movida para uma imagem não assinada.
  • Automatizar a verificação. A verificação manual é insuficiente — a verificação tem de acontecer automaticamente no admission controller do Kubernetes antes de cada implantação.
  • Documentar o processo de recuperação. Se a chave de assinatura for perdida (no modo key-pair), ter um processo documentado para gerar nova chave e re-assinar as imagens. No modo keyless, isto não é um problema porque não há chaves de longa duração.

⚠ Não usar Enforce sem staging primeiro Activar Enforce directamente em produção, sem período de auditoria em staging, pode bloquear implantações de emergência. Se uma imagem não assinada precisar de ser aplicada urgentemente, não haverá excepção automática — o cluster recusará o pod. Planear sempre um período de transição.

7. Conclusão

O Sigstore representa uma mudança de paradigma na segurança da cadeia de abastecimento de software. Ao eliminar a complexidade da gestão de chaves de longa duração e fornecer um registo de transparência público e auditável, torna a assinatura de artefactos acessível a organizações de qualquer dimensão — incluindo PMEs que não têm equipas de segurança dedicadas.

A adopção faseada (assinar em CI/CD, verificar em staging, enforce em produção) permite introduzir a verificação de assinaturas sem disrupção operacional. Combinado com SBOM assinados e verificação no admission controller do Kubernetes, cria-se uma cadeia de confiança que vai desde o commit do programador até ao pod em execução — algo que os regulamentos NIS2 e Cyber Resilience Act exigem cada vez mais explicitamente.

Para as PMEs portuguesas, o investimento é modesto: o Sigstore é código aberto, sem custos de licenciamento, e a integração com GitHub Actions ou GitLab CI exige apenas algumas dezenas de linhas de configuração. O retorno é directo: conformidade regulatória, protecção contra ataques à cadeia de abastecimento, e capacidade de demonstrar proveniência e integridade a clientes e auditores.