Firma y Verificación de Imágenes Container con Sigstore y Cosign

Introducción: Por qué la Firma de Artefactos es Importante en CI/CD

Los pipelines modernos de entrega de software son extraordinariamente buenos para compilar y desplegar código rápidamente. Pero la velocidad sin confianza es un riesgo. Entre el momento en que el código fuente se hace commit y el momento en que una imagen container se ejecuta en producción, existe una brecha — una brecha donde la manipulación, la sustitución o la corrupción silenciosa pueden ocurrir sin que nadie lo note.

Esto no es una preocupación teórica. El ataque a SolarWinds en 2020 demostró cómo los adversarios podían inyectar código malicioso en un proceso de compilación, produciendo artefactos firmados pero comprometidos que se propagaron a miles de organizaciones. La brecha de Codecov en 2021 mostró cómo un script de CI manipulado podía exfiltrar secretos de cada repositorio que lo utilizaba. En ambos casos, la cadena de suministro — la infraestructura entre el código y el despliegue — fue la superficie de ataque.

La firma de artefactos aborda una pieza crítica de este rompecabezas: autenticidad e integridad. Al firmar criptográficamente una imagen container, se crea un vínculo verificable entre la imagen y la identidad (persona, equipo o sistema CI) que la produjo. Los consumidores de esa imagen pueden entonces verificar la firma antes de ejecutarla, asegurándose de que no ha sido modificada desde que fue compilada.

Durante años, la firma fue impracticable en la mayoría de las organizaciones. La gestión de claves GPG era engorrosa, la distribución de claves era frágil y las herramientas requerían una profunda experiencia criptográfica. Sigstore cambió eso. Introdujo un conjunto de herramientas de código abierto que hacen que la firma y la verificación sean accesibles, automatizadas y — de manera crítica — sin claves (keyless).

Esta guía recorre el ecosistema Sigstore, muestra cómo firmar y verificar imágenes container con Cosign, integrar la firma en pipelines CI/CD y adjuntar attestations y SBOMs. Al final, tendrá una comprensión práctica de cómo hacer de la firma de artefactos una parte estándar de su proceso de entrega.

¿Qué es Sigstore?

Sigstore es un proyecto de código abierto bajo la Linux Foundation que proporciona herramientas gratuitas, transparentes y fáciles de usar para firmar, verificar y proteger artefactos de software. Fue creado para resolver un problema específico: hacer que la firma criptográfica sea práctica para el ecosistema de código abierto y más allá.

El ecosistema Sigstore consta de tres componentes principales:

Cosign

Cosign es la herramienta del lado del cliente para firmar y verificar imágenes container y otros artefactos OCI. Es con lo que los desarrolladores y los pipelines CI/CD interactúan directamente. Cosign soporta tanto la firma tradicional con par de claves como el flujo más reciente de firma keyless.

Fulcio

Fulcio es una autoridad certificadora (CA) gratuita que emite certificados de corta duración basados en una identidad OpenID Connect (OIDC). Cuando se utiliza la firma keyless, Fulcio verifica su identidad a través de un proveedor OIDC (como Google, GitHub o Microsoft) y emite un certificado que vincula su identidad a una clave de firma. El certificado es válido solo por unos minutos — el tiempo suficiente para firmar un artefacto, pero lo suficientemente corto para que el compromiso de la clave no sea un riesgo persistente.

Rekor

Rekor es un log de transparencia — un registro inmutable, de solo adición, que registra eventos de firma. Cada vez que se firma un artefacto, se añade un registro a Rekor. Esto proporciona un rastro público y auditable de quién firmó qué y cuándo. Es conceptualmente similar a los logs de Certificate Transparency utilizados en el ecosistema TLS.

Cómo la Firma Keyless Difiere de GPG

La firma tradicional basada en GPG requiere generar un par de claves de larga duración, proteger la clave privada, distribuir la clave pública y gestionar la rotación y revocación de claves. Esto es operativamente pesado y propenso a errores, razón por la cual la mayoría de los proyectos nunca adoptaron la firma.

La firma keyless de Sigstore elimina esta carga:

  • Sin claves de larga duración — Las claves de firma son efímeras. Existen solo durante la operación de firma.
  • Confianza basada en identidad — En lugar de confiar en una clave, se confía en una identidad (por ejemplo, un workflow de GitHub Actions, una dirección de correo electrónico específica). Fulcio vincula la identidad a la clave efímera mediante un certificado de corta duración.
  • Transparencia por defecto — Cada evento de firma se registra en Rekor, creando un registro auditable sin necesidad de ejecutar su propia infraestructura.
  • Sin problema de distribución de claves — Los verificadores no necesitan obtener una clave pública fuera de banda. Verifican contra la identidad y el log de transparencia.

Firma de Imágenes Container con Cosign

Instalación de Cosign

Cosign se distribuye como un binario único. Puede instalarlo en la mayoría de las plataformas:

# macOS (Homebrew)
brew install cosign

# Linux (Go install)
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Linux (binary release)
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign

# Verify installation
cosign version

Firma con un Par de Claves

El enfoque más simple es generar un par de claves y usarlo para firmar imágenes. Esto es útil cuando se desea un control total sobre la gestión de claves o cuando la firma keyless no está disponible en su entorno.

Paso 1: Generar un par de claves

cosign generate-key-pair

Esto crea dos archivos: cosign.key (clave privada, protegida por contraseña) y cosign.pub (clave pública). Almacene la clave privada de forma segura — en un gestor de secretos, HSM o bóveda cifrada.

Paso 2: Firmar una imagen container

# Sign an image by its digest (always prefer digest over tag)
cosign sign --key cosign.key ghcr.io/myorg/myapp@sha256:abc123...

Cosign solicitará la contraseña de la clave privada, generará una firma y la enviará al mismo registro OCI junto a la imagen. La firma se almacena como un artefacto OCI separado, etiquetado con una convención que lo vincula al digest de la imagen.

Importante: Siempre firme por digest, no por tag. Los tags son mutables — alguien podría mover un tag para apuntar a una imagen diferente después de la firma. Los digests están direccionados por contenido y son inmutables.

Firma Keyless con Identidad OIDC

La firma keyless es el enfoque recomendado para pipelines CI/CD. Elimina la necesidad de gestionar claves de firma por completo.

# Keyless signing (interactive — opens browser for OIDC login)
cosign sign ghcr.io/myorg/myapp@sha256:abc123...

# Keyless signing (non-interactive, for CI/CD)
# The --yes flag skips the confirmation prompt
cosign sign --yes ghcr.io/myorg/myapp@sha256:abc123...

En un entorno CI/CD como GitHub Actions, Cosign detecta automáticamente el token OIDC ambiental proporcionado por la plataforma. No se necesita interacción con el navegador.

Qué Ocurre Detrás de Escena

Cuando ejecuta cosign sign --yes en modo keyless, ocurre la siguiente secuencia:

  1. Generación de clave efímera — Cosign genera un par de claves temporal en memoria.
  2. Autenticación OIDC — Cosign obtiene un token de identidad OIDC del entorno (por ejemplo, el proveedor OIDC de GitHub Actions) o le solicita autenticarse a través de un navegador.
  3. Emisión de certificado — Cosign envía la clave pública y el token OIDC a Fulcio. Fulcio verifica el token, extrae la identidad (correo electrónico, URI del workflow, etc.) y emite un certificado X.509 de corta duración que vincula la identidad a la clave pública.
  4. Firma — Cosign firma el digest de la imagen usando la clave privada efímera.
  5. Registro de transparencia — La firma, el certificado y el digest de la imagen se registran en Rekor. Esta entrada tiene marca de tiempo y es inmutable.
  6. Carga de firma — La firma se envía al registro OCI como un artefacto complementario.
  7. Destrucción de clave — La clave privada efímera se descarta. Nunca se almacena en ningún lugar.

El resultado es una imagen firmada con una cadena verificable: el log de Rekor prueba que la firma fue creada en un momento específico por una identidad específica, y el certificado de Fulcio prueba que la identidad fue autenticada en el momento de la firma.

Verificación de Firmas

La firma solo es útil si los consumidores verifican. Cosign proporciona comandos de verificación sencillos tanto para escenarios basados en claves como para keyless.

Verificación con una Clave Pública

Si la imagen fue firmada con un par de claves, verifique usando la clave pública:

cosign verify --key cosign.pub ghcr.io/myorg/myapp@sha256:abc123...

Cosign obtiene la firma del registro OCI, la verifica contra la clave pública y muestra el resultado. Si la firma es válida, imprime el payload de la firma. Si no, sale con un error.

Verificación Keyless

Para imágenes firmadas con keyless, la verificación se basa en la identidad en lugar de una clave. Se especifica quién se espera que haya firmado la imagen:

# Verify that a specific GitHub Actions workflow signed the image
cosign verify \
  --certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/myorg/myapp@sha256:abc123...

Este comando verifica que:

  • La imagen tiene una firma válida.
  • El certificado de firma fue emitido por Fulcio.
  • La identidad en el certificado coincide con el --certificate-identity especificado.
  • El emisor OIDC coincide con --certificate-oidc-issuer.
  • El evento de firma está registrado en el log de transparencia de Rekor.

También puede utilizar coincidencia con expresiones regulares para políticas más flexibles:

cosign verify \
  --certificate-identity-regexp "https://github.com/myorg/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/myorg/myapp@sha256:abc123...

Aplicación de Verificación en Admission Controllers

La verificación manual es útil para depuración, pero los entornos de producción necesitan aplicación automatizada. Los admission controllers de Kubernetes pueden rechazar cualquier imagen que no tenga una firma válida.

Kyverno es un motor de políticas popular con soporte integrado de verificación de Cosign:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-cosign-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

El Sigstore Policy Controller (anteriormente Cosign Policy Webhook) proporciona funcionalidad similar con un enfoque nativo de Sigstore:

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-signed-images
spec:
  images:
    - glob: "ghcr.io/myorg/**"
  authorities:
    - keyless:
        identities:
          - issuer: "https://token.actions.githubusercontent.com"
            subject: "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main"
        ctlog:
          url: https://rekor.sigstore.dev

Con cualquiera de los enfoques, cualquier pod que haga referencia a una imagen sin firmar (o firmada incorrectamente) de su registro será rechazado en el momento de la admisión.

Integración de la Firma en Pipelines CI/CD

GitHub Actions con Firma Keyless

GitHub Actions tiene soporte nativo de OIDC, lo que lo convierte en el entorno ideal para la firma keyless. Aquí hay un workflow completo que compila, envía y firma una imagen container:

name: Build and Sign Container Image

on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write
  id-token: write  # Required for keyless signing

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

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

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

      - name: Build and push image
        id: build
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

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

      - name: Sign the image
        env:
          DIGEST: ${{ steps.build.outputs.digest }}
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${DIGEST}

Puntos clave:

  • El permiso id-token: write habilita el proveedor OIDC de GitHub Actions, que Cosign utiliza para la firma keyless.
  • La imagen se firma por digest, no por tag, utilizando la salida del paso de compilación.
  • No se necesitan secretos ni claves — el token OIDC de GitHub es la identidad.

Ejemplo de GitLab CI

GitLab CI también soporta tokens OIDC para la firma keyless. El enfoque es similar pero utiliza el mecanismo de ID token de GitLab:

stages:
  - build
  - sign

variables:
  IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}

build:
  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 $IMAGE .
    - docker push $IMAGE
    - echo "DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE | cut -d@ -f2)" >> build.env
  artifacts:
    reports:
      dotenv: build.env

sign:
  stage: sign
  image: alpine:3.19
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  before_script:
    - apk add --no-cache cosign
  script:
    - cosign sign --yes ${CI_REGISTRY_IMAGE}@${DIGEST}

El bloque id_tokens instruye a GitLab para generar un token OIDC con la audiencia sigstore. Cosign recoge el token de la variable de entorno SIGSTORE_ID_TOKEN automáticamente.

Almacenamiento de Firmas en Registros OCI

Por defecto, Cosign almacena las firmas en el mismo registro OCI que la imagen firmada. La firma se envía como un tag separado siguiendo la convención sha256-<digest>.sig. Esto significa:

  • No se necesita infraestructura adicional para el almacenamiento de firmas.
  • Las firmas viajan con la imagen cuando se replican o duplican.
  • La mayoría de los registros principales (GHCR, Docker Hub, ECR, GCR, ACR, Harbor) soportan artefactos OCI y funcionan con Cosign de forma nativa.

Si necesita almacenar firmas en un registro diferente (por ejemplo, un almacén de firmas dedicado), puede usar la variable de entorno COSIGN_REPOSITORY:

export COSIGN_REPOSITORY=ghcr.io/myorg/signatures
cosign sign --yes ghcr.io/myorg/myapp@sha256:abc123...

Attestations y Adjunción de SBOM

Las firmas prueban quién compiló una imagen. Las attestations van más allá — prueban cómo fue compilada y qué contiene. Cosign soporta adjuntar metadatos estructurados a las imágenes en forma de attestations in-toto.

Adjunción de SLSA Provenance con cosign attest

SLSA (Supply-chain Levels for Software Artifacts) provenance describe el proceso de compilación: qué código fuente se utilizó, qué builder se ejecutó, qué pasos se realizaron. Puede adjuntar una attestation de SLSA provenance a una imagen:

# Create a provenance statement (simplified example)
cat > provenance.json <<'EOF'
{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "ghcr.io/myorg/myapp",
      "digest": {
        "sha256": "abc123..."
      }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://github.com/myorg/myapp/.github/workflows/release.yml",
      "resolvedDependencies": [
        {
          "uri": "git+https://github.com/myorg/myapp@refs/heads/main",
          "digest": {
            "sha1": "def456..."
          }
        }
      ]
    },
    "runDetails": {
      "builder": {
        "id": "https://github.com/actions/runner"
      }
    }
  }
}
EOF

# Attest the image with the provenance statement (keyless)
cosign attest --yes \
  --predicate provenance.json \
  --type slsaprovenance \
  ghcr.io/myorg/myapp@sha256:abc123...

En la práctica, se utilizaría un generador de SLSA (como slsa-github-generator) para producir la provenance automáticamente en lugar de elaborarla manualmente.

Adjunción de SBOMs

Un Software Bill of Materials (SBOM) lista cada componente dentro de su imagen container. Cosign puede adjuntar un SBOM a una imagen para que los consumidores puedan inspeccionar su contenido:

# Generate an SBOM using Syft
syft ghcr.io/myorg/myapp@sha256:abc123... -o spdx-json > sbom.spdx.json

# Attach the SBOM as an attestation (recommended approach)
cosign attest --yes \
  --predicate sbom.spdx.json \
  --type spdxjson \
  ghcr.io/myorg/myapp@sha256:abc123...

Alternativamente, puede usar el comando más antiguo cosign attach sbom, aunque el enfoque basado en attestations es preferible porque está firmado y es verificable:

# Older approach (attached but not signed)
cosign attach sbom --sbom sbom.spdx.json \
  ghcr.io/myorg/myapp@sha256:abc123...

Verificación de Attestations

Los consumidores pueden verificar attestations de la misma manera que las firmas. El comando cosign verify-attestation verifica tanto la firma en la attestation como la identidad del firmante:

# Verify SLSA provenance attestation
cosign verify-attestation \
  --type slsaprovenance \
  --certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/myorg/myapp@sha256:abc123...

# Verify SBOM attestation
cosign verify-attestation \
  --type spdxjson \
  --certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/myorg/myapp@sha256:abc123...

También puede utilizar motores de políticas como Kyverno para verificar attestations en el momento de la admisión, asegurando que solo las imágenes con provenance o SBOMs válidos se desplieguen en sus clústeres.

Consideraciones de Seguridad y Compromisos

La firma de artefactos es una herramienta poderosa, pero es importante entender contra qué protege y contra qué no.

Contra Qué Protege la Firma

  • Manipulación después de la compilación — Si alguien modifica una imagen después de que ha sido firmada, la firma ya no se verificará. Esto detecta compromisos del registro, ataques man-in-the-middle y corrupción accidental.
  • Suplantación de identidad — Con la firma keyless, la identidad del firmante está criptográficamente vinculada a la firma. Un atacante no puede falsificar una firma que pretenda provenir de su pipeline CI sin comprometer el proveedor OIDC.
  • Repudio — El log de transparencia de Rekor proporciona un registro a prueba de manipulación de los eventos de firma. Los firmantes no pueden negar haber firmado un artefacto.

Contra Qué NO Protege la Firma

  • Código fuente malicioso — La firma prueba que la imagen fue compilada por un pipeline autorizado. No prueba que el código fuente esté libre de vulnerabilidades o puertas traseras. Una cuenta de desarrollador comprometida que envíe código malicioso producirá una imagen legítimamente firmada.
  • Entornos de compilación comprometidos — Si el runner de CI en sí está comprometido (como en el escenario de SolarWinds), el atacante puede producir artefactos firmados. La firma prueba la identidad, no la integridad del entorno de compilación.
  • Vulnerabilidades en dependencias — Una imagen firmada aún puede contener CVEs conocidos. La firma y el escaneo de vulnerabilidades son complementarios, no sustitutos.
  • Elusión de políticas — La firma solo funciona si la verificación se aplica. Si su admission controller tiene excepciones, o si los equipos pueden desplegar sin pasar por él, la firma no proporciona protección para esas rutas.

Supuestos del Modelo de Confianza

La firma keyless se basa en varios supuestos de confianza:

  • Confianza en el proveedor OIDC — Se confía en que GitHub, GitLab o Google autentican correctamente las identidades. Un compromiso del proveedor OIDC socava todo el modelo.
  • Confianza en Fulcio — Se confía en que la instancia de Fulcio de Sigstore verifica correctamente los tokens OIDC y emite certificados solo a identidades autenticadas.
  • Confianza en Rekor — Se confía en que el log de transparencia es de solo adición y a prueba de manipulación. La instancia pública de Rekor de Sigstore es operada por la comunidad; para entornos de alta seguridad, puede querer ejecutar la suya propia.

Para organizaciones con requisitos de cumplimiento estrictos, ejecutar una infraestructura privada de Sigstore (CA privada de Fulcio, log privado de Rekor) proporciona control total sobre la cadena de confianza.

Consideraciones de Gestión de Claves

Si elige la firma basada en claves en lugar de keyless:

  • Almacene las claves privadas en un KMS (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault). Cosign tiene soporte nativo de KMS: cosign sign --key awskms:///arn:aws:kms:...
  • Rote las claves en un calendario regular y tenga un plan de revocación.
  • Evite almacenar claves privadas como secretos de CI/CD — típicamente se registran, almacenan en caché y replican de maneras que aumentan la exposición.
  • Use claves separadas para diferentes entornos (staging vs. producción) para limitar el radio de impacto.

Conclusión

La firma de imágenes container con Sigstore y Cosign es uno de los pasos más impactantes que puede tomar para asegurar su cadena de suministro de software. Ya no es difícil, ya no requiere una profunda experiencia criptográfica y ya no demanda una infraestructura compleja de gestión de claves. Con la firma keyless, un pipeline CI/CD puede firmar cada imagen que produce con cero secretos que gestionar y total auditabilidad a través del log de transparencia de Rekor.

Pero la firma es un bloque de construcción, no una solución mágica. Responde a la pregunta "¿fue esta imagen producida por un proceso autorizado?" No responde "¿es seguro ejecutar esta imagen?" Una estrategia completa de seguridad de la cadena de suministro combina la firma con el escaneo de vulnerabilidades, la generación de SBOM, SLSA provenance, la aplicación de políticas en la admisión, el monitoreo de seguridad en tiempo de ejecución y controles de acceso rigurosos en los repositorios de código fuente y la infraestructura de compilación.

Comience agregando cosign sign --yes a su pipeline CI/CD. Luego agregue verificación en su admission controller. Después agregue attestations y SBOMs. Cada paso reduce la brecha entre compilar software y confiar en él.

Las herramientas están maduras, el ecosistema está creciendo y el costo de no firmar es cada vez más difícil de justificar. La pregunta ya no es si firmar sus artefactos — es qué tan rápido puede convertirlo en el estándar.