--- title: "Cómo firmar imágenes de contenedor con Cosign en Ubuntu" description: "Instala Cosign en Ubuntu 24.04, firma imágenes de contenedor con claves propias o en modo keyless desde GitHub Actions y verifica las firmas antes de desplegar." category: "Seguridad de Contenedores" language: es source: https://cubepath.com/es/docs/seguridad-de-contenedores/firma-de-imagenes-de-contenedor-con-cosign updated: 2026-09-25 --- # Cómo firmar imágenes de contenedor con Cosign en Ubuntu Cosign es la herramienta del proyecto Sigstore para firmar y verificar imágenes de contenedor. La firma se guarda en el mismo registro que la imagen, de modo que cualquiera con la clave pública (o conociendo la identidad del firmante) puede comprobar que la imagen que va a desplegar es exactamente la que se construyó. En este tutorial instalarás Cosign en Ubuntu 24.04, firmarás una imagen con un par de claves propio, la verificarás y después automatizarás la firma sin claves (keyless) desde GitHub Actions. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor o equipo con Ubuntu 24.04 LTS (x86_64), por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. - Docker Engine instalado, para construir y publicar la imagen de prueba. - Una cuenta en un registro OCI con permisos de escritura. Los ejemplos usan GitHub Container Registry (`ghcr.io`) con un token personal con el permiso `write:packages`; funciona igual con Docker Hub, Harbor o cualquier registro compatible. - Para la parte keyless: un repositorio en GitHub con GitHub Actions habilitado. En los comandos, sustituye `your_user` por tu usuario u organización de GitHub, en minúsculas. ## Paso 1: Instalar Cosign Cosign se distribuye como un binario estático. Descarga la última versión y el archivo de sumas de comprobación: ```bash cd /tmp curl -fsSLO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 curl -fsSLO https://github.com/sigstore/cosign/releases/latest/download/cosign_checksums.txt ``` Verifica la integridad del binario antes de instalarlo: ```bash sha256sum --ignore-missing -c cosign_checksums.txt ``` ```output cosign-linux-amd64: OK ``` Instálalo en `/usr/local/bin`: ```bash sudo install -m 0755 cosign-linux-amd64 /usr/local/bin/cosign cosign version ``` ```output GitVersion: v3.0.2 GitTreeState: clean ... ``` En servidores arm64 usa `cosign-linux-arm64` en lugar de `cosign-linux-amd64`. ## Paso 2: Publicar una imagen de prueba Cosign firma imágenes que ya están en un registro. Inicia sesión en GHCR (Docker guarda las credenciales en `~/.docker/config.json` y Cosign las reutiliza): ```bash docker login ghcr.io -u your_user ``` Crea una imagen mínima y publícala: ```bash mkdir -p ~/demo-app && cd ~/demo-app printf 'FROM nginx:1.27-alpine\n' > Dockerfile docker build -t ghcr.io/your_user/demo-app:v1.0.0 . docker push ghcr.io/your_user/demo-app:v1.0.0 ``` Obtén la referencia por digest. Los tags son mutables (alguien puede volver a publicar `v1.0.0` con otro contenido), así que siempre se firma el digest: ```bash IMAGE=$(docker inspect --format '{{index .RepoDigests 0}}' ghcr.io/your_user/demo-app:v1.0.0) echo "$IMAGE" ``` ```output ghcr.io/your_user/demo-app@sha256:4f1c3e8a9b2d... ``` ## Paso 3: Generar un par de claves Genera las claves en un directorio que no forme parte de ningún repositorio Git: ```bash mkdir -p ~/cosign && cd ~/cosign cosign generate-key-pair ``` Cosign pide una contraseña para cifrar la clave privada. Usa una contraseña fuerte y guárdala en tu gestor de secretos: ```output Enter password for private key: Enter password for private key again: Private key written to cosign.key Public key written to cosign.pub ``` - `cosign.key`: clave privada cifrada. No la subas nunca a un repositorio. - `cosign.pub`: clave pública. Puedes distribuirla libremente a quien tenga que verificar. Restringe los permisos de la clave privada: ```bash chmod 600 ~/cosign/cosign.key ``` > Consejo: en producción es preferible que la clave privada no salga de un KMS. Cosign admite claves en AWS KMS, Google Cloud KMS, Azure Key Vault y HashiCorp Vault, por ejemplo con `cosign generate-key-pair --kms awskms:///alias/cosign`. ## Paso 4: Firmar la imagen Firma la imagen por digest con la clave privada. Puedes añadir anotaciones que quedan dentro del contenido firmado, como el commit de origen: ```bash cosign sign --yes --key ~/cosign/cosign.key \ -a git-commit=abc1234 \ "$IMAGE" ``` Cosign pide la contraseña de la clave, sube la firma al registro junto a la imagen y registra una entrada en Rekor, el registro de transparencia público de Sigstore. `--yes` acepta esa confirmación sin preguntar. > Importante: la entrada de Rekor es pública y contiene el digest de la imagen y la firma. Si la existencia de tus imágenes es confidencial, valóralo antes de usar la instancia pública. ## Paso 5: Verificar la firma Cualquier equipo con Cosign y la clave pública puede verificar la imagen, tanto por digest como por tag: ```bash cosign verify --key ~/cosign/cosign.pub ghcr.io/your_user/demo-app:v1.0.0 ``` ```output Verification for ghcr.io/your_user/demo-app:v1.0.0 -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The signatures were verified against the specified public key [{"critical":{"identity":{"docker-reference":"ghcr.io/your_user/demo-app"},"image":{"docker-manifest-digest":"sha256:4f1c3e8a9b2d..."},"type":"cosign container image signature"},"optional":{"git-commit":"abc1234"}}] ``` Para exigir además que la firma lleve una anotación concreta: ```bash cosign verify --key ~/cosign/cosign.pub -a git-commit=abc1234 ghcr.io/your_user/demo-app:v1.0.0 ``` Comprueba también que la verificación falla con una imagen que no has firmado tú, por ejemplo la imagen base: ```bash cosign verify --key ~/cosign/cosign.pub nginx:1.27-alpine ``` ```output Error: no signatures found ``` Si la imagen tuviera firmas hechas con otra clave, el error sería `no matching signatures`. La firma pertenece al contenido (el digest), no al tag: si reconstruyes la imagen con cualquier cambio y la publicas con el mismo tag, el digest cambia y la verificación falla hasta que la vuelvas a firmar. En un script de despliegue basta con comprobar el código de salida de `cosign verify` (0 solo si la firma es válida). ## Paso 6: Firmar sin claves desde GitHub Actions Con la firma keyless no hay clave privada que custodiar. El runner de GitHub obtiene un token OIDC, Fulcio (la autoridad de certificación de Sigstore) emite un certificado de pocos minutos ligado a la identidad del workflow y la firma queda registrada en Rekor. Al verificar, compruebas quién firmó en lugar de qué clave. En tu repositorio, crea `.github/workflows/build.yml`: ```yaml name: build-and-sign on: push: tags: ["v*"] jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: build uses: docker/build-push-action@v6 with: push: true tags: ghcr.io/your_user/demo-app:${{ github.ref_name }} - uses: sigstore/cosign-installer@v3 - name: Firmar la imagen run: cosign sign --yes "ghcr.io/your_user/demo-app@${DIGEST}" env: DIGEST: ${{ steps.build.outputs.digest }} ``` `id-token: write` es el permiso que permite al job pedir el token OIDC. No hace falta ningún secreto adicional. Publica un tag para lanzar el workflow: ```bash git tag v1.0.1 git push origin v1.0.1 ``` Cuando termine, verifica la imagen indicando la identidad esperada (el workflow y la referencia) y el emisor OIDC de GitHub: ```bash cosign verify \ --certificate-identity-regexp '^https://github.com/your_user/demo-app/\.github/workflows/build\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/your_user/demo-app:v1.0.1 ``` ```output Verification for ghcr.io/your_user/demo-app:v1.0.1 -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates ``` Con esta verificación, solo se aceptan imágenes firmadas por ese workflow al ejecutarse sobre un tag `v*`. Una imagen firmada desde un fork, otra rama u otro repositorio no pasa. ### Firmar con clave propia en CI Si prefieres claves propias en el pipeline, guarda el contenido de `cosign.key` en un secreto `COSIGN_PRIVATE_KEY` y la contraseña en `COSIGN_PASSWORD`. Cosign puede leer la clave directamente de una variable de entorno, sin escribirla en disco: ```yaml - name: Firmar la imagen con clave run: cosign sign --yes --key env://COSIGN_PRIVATE_KEY "ghcr.io/your_user/demo-app@${DIGEST}" env: DIGEST: ${{ steps.build.outputs.digest }} COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} ``` ## Solución de problemas **`no signatures found`.** La imagen no está firmada, el digest ha cambiado desde la firma o estás verificando en un registro distinto (por ejemplo, una copia en otro registro que no incluye la firma). Comprueba el digest actual con `docker buildx imagetools inspect ghcr.io/your_user/demo-app:v1.0.0`. **`UNAUTHORIZED` o `denied` al firmar.** Cosign usa las credenciales de Docker. Repite `docker login ghcr.io` y confirma que el token tiene `write:packages`. **`none of the expected identities matched` en keyless.** La identidad del certificado no coincide con `--certificate-identity-regexp`. El mensaje de error muestra la identidad real; ajusta la expresión (nombre del archivo del workflow, rama o tag). **`error getting credentials` en GitHub Actions.** Falta `permissions: id-token: write` en el job. ## Conclusión Has instalado Cosign, has firmado y verificado una imagen con claves propias y has automatizado la firma keyless ligada a la identidad de un workflow de GitHub Actions. El siguiente paso lógico es exigir la firma en el propio clúster: con Kyverno puedes rechazar cualquier Pod cuya imagen no esté firmada con tu clave o tu workflow. También puedes adjuntar un SBOM firmado con `cosign attest` y firmar los artefactos que no son imágenes (binarios, charts) con `cosign sign-blob`.