Firmar una imagen con Cosign no impide que Kubernetes despliegue otra imagen sin firma. Para convertir la firma en un control efectivo necesitas una puerta de admisión que compruebe la imagen antes de aceptar el recurso.

Sigstore policy-controller realiza esa verificación mediante recursos ClusterImagePolicy y puede admitir, advertir o rechazar el despliegue según la autoridad configurada.

En esta guía vas a instalar policy-controller, definir una política keyless limitada a un repositorio, activarla en un namespace de ensayo y comprobar dos resultados: una imagen con la identidad autorizada se admite y una imagen sin firma válida se rechaza.

Resultado esperado. El API server solo aceptará, dentro del namespace protegido, imágenes del repositorio definido que tengan una firma verificable emitida para el issuer y el subject autorizados.

Qué controla esta política

El pipeline firma una imagen almacenada en un registro OCI. Cuando alguien intenta crear un Deployment, Pod, Job u otro recurso con un PodSpec, el API server llama al webhook de policy-controller.

El controlador resuelve la referencia de la imagen a un digest, selecciona las ClusterImagePolicy cuyo patrón coincide y valida la firma contra las autoridades declaradas.

Todas las ClusterImagePolicy que coincidan deben cumplirse: entre políticas la relación es AND. Dentro de una misma política basta con que una de sus autoridades sea válida: entre autoridades la relación es OR.

Por tanto, una política adicional puede volver más restrictiva una imagen que ya satisface otra.

La firma se produce antes del despliegue; policy-controller la verifica durante la admisión.

Antes de comenzar

Necesitas:

  • Un clúster Kubernetes de ensayo o un namespace donde puedas realizar pruebas sin afectar producción.
  • kubectl configurado con permisos para instalar CRD, webhooks y recursos de alcance de clúster.
  • Helm 3.
  • Cosign para verificar la imagen antes de probar la admisión.
  • Acceso del controlador al registro OCI y, para firmas keyless públicas, a los servicios de Sigstore.
  • Una imagen firmada y otra imagen no firmada o firmada por una identidad distinta, preferentemente del mismo repositorio.
  • El issuer y el subject exactos de la identidad autorizada.

Los comandos están escritos para Bash. Sustituye los valores ORGANIZACION, REPOSITORIO, las referencias de imágenes y la versión del chart antes de ejecutar.

Precaución. No habilites el namespace antes de instalar y validar la política. Una cobertura incompleta puede bloquear imágenes auxiliares, initContainers o sidecars inyectados. Conserva otra sesión con acceso administrativo al clúster.

Compatibilidad que debes validar

Al 31 de agosto de 2026, la última versión publicada de policy-controller es v0.15.1. El proyecto continúa en desarrollo activo.

También existen incidencias abiertas sobre el descubrimiento de algunas firmas creadas con Cosign v3, tanto keyless como con clave.

Por eso, no asumas que cosign verify y el webhook interpretarán de la misma forma cualquier formato de firma.

La API distingue signatureFormat: legacy|bundle y utiliza legacy de forma predeterminada. La documentación actual indica limitaciones del formato bundle para firmas planas. Este ejemplo declara legacy, pero no debe publicarse como combinación recomendada hasta comprobar qué formato produce la versión de Cosign elegida.

Registra las versiones de Cosign, del chart y de la aplicación, verifica primero la imagen fuera del clúster y ejecuta este procedimiento con la misma combinación que utilizarás en CI.

Paso 1: registra el contexto y los permisos

Confirma que trabajas en el clúster correcto:

kubectl config current-context
kubectl version
helm version
cosign version

Comprueba los permisos principales:

kubectl auth can-i create \
  customresourcedefinitions.apiextensions.k8s.io

kubectl auth can-i create \
  validatingwebhookconfigurations.admissionregistration.k8s.io

kubectl auth can-i create \
  clusterimagepolicies.policy.sigstore.dev

kubectl auth can-i patch namespaces

Registra además qué namespaces ya tienen habilitado el controlador:

kubectl get namespaces \
  -l policy.sigstore.dev/include=true

Una respuesta no en los permisos requiere intervención de quien administra el clúster. No sustituyas estos permisos por un rol más amplio sin revisar el alcance.

Paso 2: crea el namespace de ensayo sin activar la política

Prepara un namespace aislado, pero todavía sin el label opt-in:

kubectl create namespace firma-lab \
  --dry-run=client -o yaml | kubectl apply -f -

kubectl label namespace firma-lab \
  policy.sigstore.dev/include- --overwrite

kubectl get namespace firma-lab --show-labels

El resultado no debe incluir policy.sigstore.dev/include=true.

Si el label no existía, kubectl puede informar que el namespace no estaba etiquetado. No ocultes errores de conexión o RBAC: confirma siempre el estado final con kubectl get namespace.

Este orden conserva una vía de recuperación mientras instalas y validas el controlador.

Paso 3: instala policy-controller con una versión fijada

Añade el repositorio oficial y revisa las versiones disponibles:

helm repo add sigstore \
  https://sigstore.github.io/helm-charts

helm repo update

helm search repo sigstore/policy-controller \
  --versions | head

Selecciona una versión que hayas validado con tu versión de Kubernetes y asígnala explícitamente. No dejes el marcador del ejemplo:

export POLICY_CONTROLLER_CHART_VERSION="VERSION_VALIDADA"

helm upgrade --install \
  policy-controller \
  sigstore/policy-controller \
  --namespace cosign-system \
  --create-namespace \
  --version "$POLICY_CONTROLLER_CHART_VERSION" \
  --atomic \
  --wait

--atomic revierte el release si la instalación no llega al estado esperado. Esto no sustituye la revisión de CRD y webhooks.

Comprueba la instalación:

helm list -n cosign-system

kubectl get deployments,pods \
  -n cosign-system

kubectl get crd \
  clusterimagepolicies.policy.sigstore.dev

kubectl get \
  validatingwebhookconfigurations,mutatingwebhookconfigurations \
  | grep -E 'policy.sigstore.dev|clusterimagepolicy'

Los pods deben estar Running y disponibles. El CRD y las configuraciones de webhook deben existir.

Si el chart utiliza nombres diferentes, inspecciona los recursos del release:

helm get manifest policy-controller \
  -n cosign-system

Paso 4: verifica la imagen fuera del clúster

Define las referencias por digest y la identidad exacta utilizada para firmar.

El siguiente ejemplo representa una firma keyless emitida desde GitHub Actions:

export SIGNED_IMAGE="ghcr.io/ORGANIZACION/APLICACION@sha256:DIGEST_FIRMADO"

export UNSIGNED_IMAGE="ghcr.io/ORGANIZACION/APLICACION@sha256:DIGEST_NO_AUTORIZADO"

export COSIGN_ISSUER="https://token.actions.githubusercontent.com"

export COSIGN_SUBJECT="https://github.com/ORGANIZACION/REPOSITORIO/.github/workflows/firmar.yml@refs/heads/main"

Verifica la imagen que debería ser admitida:

cosign verify "$SIGNED_IMAGE" \
  --certificate-oidc-issuer="$COSIGN_ISSUER" \
  --certificate-identity="$COSIGN_SUBJECT"

La verificación debe terminar con código 0 y mostrar que se comprobaron la firma, la identidad y los claims del digest.

Si falla aquí, no actives el namespace. Corrige primero la firma, la identidad, el registro o la compatibilidad de versiones.

La clave privada, si tu organización utiliza firmas con clave, debe permanecer en CI o en un KMS. Nunca la incluyas en una ClusterImagePolicy, un ConfigMap, una captura o el repositorio de manifiestos.

Paso 5: crea una ClusterImagePolicy en modo warn

Guarda este manifiesto como cip-firma-keyless.yaml y reemplaza todos los valores de ejemplo:

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: donweb-firma-keyless
spec:
  mode: warn
  images:
    - glob: "ghcr.io/ORGANIZACION/APLICACION@sha256:*"
  authorities:
    - name: github-actions
      signatureFormat: legacy
      keyless:
        url: https://fulcio.sigstore.dev
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subject: https://github.com/ORGANIZACION/REPOSITORIO/.github/workflows/firmar.yml@refs/heads/main
      ctlog:
        url: https://rekor.sigstore.dev

El patrón está limitado a un repositorio y a referencias por digest. No empieces con "**" en producción.

Usa un subject exacto cuando el flujo de firma sea estable. Una expresión regular demasiado amplia puede aceptar identidades no previstas.

mode: warn permite observar incumplimientos de esta política sin bloquearlos.

signatureFormat: legacy hace explícito el formato esperado por esta autoridad. Debe coincidir con el formato realmente publicado por el pipeline.

Una imagen que no coincida con ninguna política puede seguir siendo rechazada por la configuración global no-match-policy, cuyo comportamiento predeterminado es denegar cuando no se configura. Revisa todos los contenedores que aparecerán en el namespace.

Valida el manifiesto antes de persistirlo:

kubectl apply \
  --dry-run=server \
  -f cip-firma-keyless.yaml

kubectl diff \
  -f cip-firma-keyless.yaml

kubectl diff devuelve código 1 cuando encuentra diferencias; eso no representa necesariamente un error. Un código mayor que 1 sí requiere investigación.

Aplica y examina la política:

kubectl apply \
  -f cip-firma-keyless.yaml

kubectl get clusterimagepolicy \
  donweb-firma-keyless

kubectl describe clusterimagepolicy \
  donweb-firma-keyless

Revisa el YAML completo y los logs del controlador:

kubectl get clusterimagepolicy \
  donweb-firma-keyless \
  -o yaml

kubectl logs \
  -n cosign-system \
  -l app.kubernetes.io/name=policy-controller \
  --all-containers \
  --tail=200

Algunas versiones exponen status o conditions. Si aparecen, no deben indicar errores de reconciliación. La ausencia de esas condiciones no prueba por sí sola un fallo.

No avances si el API rechazó el recurso o los logs muestran errores de sintaxis, confianza o acceso al registro.

Paso 6: inspecciona no-match-policy

Antes del opt-in, inspecciona el comportamiento global para imágenes sin política coincidente:

kubectl get configmap \
  config-policy-controller \
  --namespace cosign-system \
  -o yaml

Busca data.no-match-policy.

Los valores admitidos son:

  • warn: admite la imagen, pero muestra una advertencia.
  • allow: admite la imagen sin exigir una política coincidente.
  • deny: rechaza la imagen.
  • Ausente: la documentación indica que el comportamiento predeterminado es rechazar.

Registra el valor porque afecta a todas las imágenes de los namespaces incluidos, no solo al repositorio de este ejemplo.

Paso 7: activa solo el namespace de ensayo

Una vez validadas la política y la configuración global, habilita la admisión:

kubectl label namespace firma-lab \
  policy.sigstore.dev/include=true \
  --overwrite

kubectl get namespace firma-lab \
  --show-labels

El label debe aparecer exactamente como:

policy.sigstore.dev/include=true

El comportamiento opt-in evita que la instalación afecte automáticamente a todos los namespaces.

La activación del namespace ocurre después de validar la política y antes de las pruebas controladas.

Paso 8: prueba el modo warn

Crea primero el recurso con la imagen firmada:

kubectl create deployment \
  firma-valida \
  --namespace firma-lab \
  --image="$SIGNED_IMAGE"

kubectl rollout status \
  deployment/firma-valida \
  --namespace firma-lab \
  --timeout=120s

Después prueba el digest no autorizado:

kubectl create deployment \
  firma-invalida-warn \
  --namespace firma-lab \
  --image="$UNSIGNED_IMAGE"

En warn, el segundo recurso puede admitirse, pero kubectl debe presentar una advertencia de la política.

Captura la salida y revisa los logs del controlador:

kubectl logs \
  -n cosign-system \
  -l app.kubernetes.io/name=policy-controller \
  --all-containers \
  --tail=200

Las etiquetas exactas dependen de la versión del chart.

Si el selector no devuelve pods, obtén las etiquetas disponibles:

kubectl get pods \
  -n cosign-system \
  --show-labels

Ajusta después el selector del comando de logs.

Paso 9: cambia a enforce y repite las pruebas

Elimina los recursos de prueba para que las siguientes solicitudes recorran nuevamente la admisión:

kubectl delete deployment \
  firma-valida \
  firma-invalida-warn \
  --namespace firma-lab \
  --ignore-not-found

Cambia la política a enforce:

kubectl patch clusterimagepolicy \
  donweb-firma-keyless \
  --type=merge \
  -p '{"spec":{"mode":"enforce"}}'

Comprueba el cambio:

kubectl get clusterimagepolicy \
  donweb-firma-keyless \
  -o jsonpath='{.spec.mode}{"\n"}'

La salida esperada es:

enforce

Repite la prueba positiva:

kubectl create deployment \
  firma-valida \
  --namespace firma-lab \
  --image="$SIGNED_IMAGE"

kubectl rollout status \
  deployment/firma-valida \
  --namespace firma-lab \
  --timeout=120s

La solicitud debe admitirse y el pod debe quedar disponible.

Ejecuta ahora la prueba negativa:

kubectl create deployment \
  firma-invalida \
  --namespace firma-lab \
  --image="$UNSIGNED_IMAGE"

La respuesta esperada es un rechazo inmediato del API server atribuido al webhook policy.sigstore.dev.

El Deployment no debe crearse:

kubectl get deployment \
  firma-invalida \
  --namespace firma-lab

La salida esperada debe indicar que el recurso no existe.

Un ImagePullBackOff no valida esta política: significa que el objeto ya fue admitido y que el fallo ocurrió después, al descargar la imagen.

Imagen relacionada: tercera imagen mostrada arriba.

La prueba negativa debe fallar durante la admisión, antes de crear el recurso.

Cómo comprobar el resultado

Conserva como evidencia:

  • Contexto y versión de Kubernetes.
  • Versión de Helm y Cosign.
  • Versión del chart y de policy-controller.
  • Formato de firma utilizado.
  • Manifiesto exacto de la ClusterImagePolicy.
  • Valor de no-match-policy.
  • Label del namespace.
  • Referencias por digest de ambas imágenes.
  • Salida de cosign verify.
  • Creación y estado del despliegue firmado.
  • Rechazo de admisión del despliegue no autorizado.
  • Eventos y logs relevantes del webhook.
  • Fecha y hora UTC de las pruebas.

La aceptación mínima es:

  • La imagen autorizada se admite.
  • El pod autorizado queda Ready.
  • La imagen no autorizada se rechaza durante la admisión.
  • El Deployment inválido no se crea.
  • Ningún otro namespace resulta afectado.

Rollback seguro

Si la política bloquea cargas necesarias, retira primero el namespace del alcance del webhook:

kubectl label namespace firma-lab \
  policy.sigstore.dev/include-

kubectl get namespace firma-lab \
  --show-labels

Confirma que el label ya no aparezca.

Después puedes volver la política a warn:

kubectl patch clusterimagepolicy \
  donweb-firma-keyless \
  --type=merge \
  -p '{"spec":{"mode":"warn"}}'

Si ya no necesitas la política:

kubectl delete clusterimagepolicy \
  donweb-firma-keyless

Antes de desinstalar el controlador, confirma que no queden namespaces opt-in:

kubectl get namespaces \
  -l policy.sigstore.dev/include=true

Si la lista está vacía y decidiste retirar completamente el componente:

helm uninstall policy-controller \
  --namespace cosign-system

kubectl rollout undo no revierte una política de admisión.

El rollback correcto empieza retirando el label que incluye el namespace en el alcance del webhook.

Problemas frecuentes

La imagen pasa cosign verify, pero policy-controller la rechaza

Compara:

  • El digest.
  • El issuer.
  • El subject.
  • El patrón glob.
  • El valor de signatureFormat.
  • Las versiones de Cosign, del chart y de policy-controller.

Revisa si la firma se almacenó con un formato que la versión del controlador puede descubrir.

Existen incidencias abiertas con determinadas firmas de Cosign v3, por lo que una verificación local correcta no garantiza automáticamente que el webhook encuentre el mismo objeto de firma.

El error indica no signatures found

Confirma que:

  • La firma esté publicada en el registro.
  • El registro soporte el método de almacenamiento utilizado.
  • El webhook pueda acceder al registro.
  • La firma se encuentre junto a la imagen o en el source configurado.
  • La referencia corresponda al digest firmado.
  • El formato coincida con signatureFormat.

Verifica la misma referencia por digest con Cosign desde un entorno que utilice credenciales equivalentes.

Las imágenes sin firma se admiten

Comprueba:

  • El label del namespace.
  • spec.mode.
  • El patrón de imagen.
  • El registro del webhook.
  • El valor de no-match-policy.
  • Los logs de policy-controller.

En warn, el recurso se admite deliberadamente con una advertencia.

Revisa también todas las CIP coincidentes. Una política con static: pass no anula otra política restrictiva que falle, porque las CIP coincidentes se combinan con lógica AND. Solo permite el paso cuando es la política aplicable que debe satisfacerse para esa imagen.

Se rechaza una imagen que no pertenece al repositorio de la política

El comportamiento predeterminado puede rechazar imágenes que no coinciden con ninguna política dentro de un namespace habilitado.

Revisa:

  • no-match-policy.
  • Las imágenes auxiliares.
  • Los sidecars.
  • Los initContainers.
  • Los contenedores agregados por otros webhooks.
  • Todos los patrones glob existentes.

No cambies globalmente a allow sin evaluar todos los namespaces opt-in.

El registro es privado

El controlador necesita credenciales para descubrir firmas y attestations.

Por defecto puede utilizar los imagePullSecrets del PodSpec. Si las firmas están en otra ubicación, configura source y signaturePullSecrets según la documentación oficial.

El Secret debe existir en el namespace del workload y ser de tipo:

kubernetes.io/dockerconfigjson

Un sidecar inyectado bloquea el despliegue

La política evalúa las imágenes presentes en el recurso admitido, incluidas las que agregan otros webhooks.

Identifica todas las imágenes finales y define una política o autoridad explícita para ellas antes de activar enforce.

Buenas prácticas para producción

  • Fija la versión del chart y conserva sus valores de instalación.
  • Registra la versión real de la aplicación incluida en el chart.
  • Usa imágenes por digest en despliegues y pruebas.
  • Declara explícitamente el formato de firma esperado.
  • Restringe issuer y subject.
  • Evita expresiones regulares amplias.
  • Mantén la clave privada fuera del clúster.
  • Utiliza un KMS cuando corresponda.
  • Empieza con warn.
  • Mide incumplimientos antes de pasar a enforce.
  • Amplía el alcance por namespace.
  • Revisa todas las políticas coincidentes.
  • Recuerda que varias ClusterImagePolicy se combinan con lógica AND.
  • Incluye sidecars, initContainers y jobs auxiliares en el inventario.
  • Supervisa disponibilidad, latencia y errores del webhook.
  • Documenta excepciones con responsable, justificación y vencimiento.
  • Prueba el rollback antes de ampliar el alcance.


Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting