Compartir comentarios
Las respuestas se generan en base a la documentación.

Validating image inputs

Las imágenes de contenedor son las entradas de compilación más comunes. Cada instrucción FROM descarga una imagen, y las referencias COPY --from descargan imágenes adicionales. Validar estas imágenes protege la cadena de suministro de tu compilación frente a registros comprometidos, actualizaciones inesperadas e imágenes base no autorizadas.

Esta guía te enseña a escribir políticas que validen las entradas de imágenes, avanzando desde la lista de permitidos básica hasta las comprobaciones de atestaciones avanzadas.

Requisitos previos

Debes comprender los conceptos básicos de las políticas a partir de la Introducción: la creación de archivos de políticas, la sintaxis básica de Rego y cómo se evalúan las políticas durante las compilaciones.

¿Qué son las entradas de imágenes?

Las entradas de imágenes provienen de dos instrucciones de Dockerfile:

# Instrucciones FROM
FROM alpine:3.22
FROM golang:1.25-alpine AS builder

# Referencias COPY --from
COPY --from=builder /app /app
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

Cada una de estas referencias desencadena la evaluación de una política. Tu política puede inspeccionar los metadatos de la imagen, verificar las atestaciones y aplicar restricciones antes de que continúe la compilación.

Lista de permitidos para repositorios específicos

La política de imágenes más simple restringe qué repositorios se pueden usar. Esto evita que los desarrolladores utilicen imágenes arbitrarias que no hayan sido revisadas.

Crea una política que solo permita Alpine:

Dockerfile.rego
package docker

default allow := false

allow if input.local

allow if {
  input.image.repo == "alpine"
}

decision := {"allow": allow}

Esta política:

  • Deniega todas las entradas por defecto
  • Permite el contexto de compilación local
  • Permite cualquier imagen del repositorio alpine (cualquier etiqueta o digest)

Pruébalo con un Dockerfile:

Dockerfile
FROM alpine
RUN echo "hello"
$ docker build .

La compilación se realiza con éxito. Intenta cambiar a FROM ubuntu:

$ docker build .

La compilación falla porque ubuntu no coincide con el repositorio permitido.

Comparar versiones semánticas

Restringe las imágenes a rangos de versiones específicos utilizando las funciones semver de Rego:

package docker

default allow := false

allow if input.local

# Permitir Go 1.21 o posterior
allow if {
  input.image.repo == "golang"
  semver.is_valid(input.image.tag)
  semver.compare(input.image.tag, "1.21.0") >= 0
}

decision := {"allow": allow}

La función semver.compare(a, b) compara versiones semánticas y devuelve:

  • -1 si la versión a es menor que b
  • 0 si las versiones son iguales
  • 1 si la versión a es mayor que b

Utiliza semver.is_valid() para comprobar si una etiqueta es una versión semántica válida antes de compararla.

Restringir a rangos de versiones específicos:

allow if {
  input.image.repo == "node"
  version := input.image.tag
  semver.is_valid(version)
  semver.compare(version, "20.0.0") >= 0  # 20.0.0 o posterior
  semver.compare(version, "21.0.0") < 0   # anterior a 21.0.0
}

Esto permite únicamente las versiones de Node.js 20.x. El patrón funciona para cualquier imagen que utilice versionado semántico.

Estas funciones semver son funciones integradas estándar de Rego documentadas en la referencia de políticas de OPA.

Requerir referencias por digest

Las etiquetas como alpine:3.22 pueden cambiar (alguien podría subir una nueva imagen con la misma etiqueta). Los digests como alpine@sha256:abc123... son inmutables.

Requerir que los usuarios proporcionen digests

Puedes exigir que los usuarios especifiquen siempre los digests en sus Dockerfiles:

package docker

default allow := false

allow if input.local

allow if {
  input.image.isCanonical
}

decision := {"allow": allow}

El campo isCanonical es true cuando la referencia del usuario incluye un digest. Esta política permitiría:

FROM alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412

Pero rechazaría las referencias basadas únicamente en etiquetas como FROM alpine:3.22.

Fijar a digests específicos

Alternativamente (o además), puedes validar que el digest real de una imagen coincida con un valor específico, independientemente de cómo haya escrito el usuario la referencia:

allow if {
  input.image.repo == "alpine"
  input.image.checksum == "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412"
}

decision := {"allow": allow}

Esto comprueba el digest de contenido real de la imagen descargada. Permitiría ambos casos:

FROM alpine:3.22
FROM alpine@sha256:4b7ce...

Siempre y cuando la imagen resuelta tenga el digest especificado. Esto es útil para fijar imágenes base críticas a versiones conocidas como seguras.

Restringir registros

Controla desde qué registros pueden realizar descargas tus compilaciones. Esto ayuda a aplicar las políticas corporativas o a restringir las descargas a fuentes de confianza.

package docker

default allow := false

allow if input.local

# Permitir imágenes de Docker Hub
allow if {
  input.image.host == "docker.io"  # Docker Hub
  input.image.repo == "alpine"
}

# Permitir imágenes de un registro interno
allow if {
  input.image.host == "registry.company.com"
}

decision := {"allow": allow}

El campo host contiene el nombre de host del registro. Las imágenes de Docker Hub usan "docker.io" como valor de host. Prueba con:

FROM alpine                                    # Permitido (Docker Hub)
FROM registry.company.com/myapp:latest         # Permitido (registro de la empresa)
FROM ghcr.io/someorg/image:latest              # Denegado (registro incorrecto)

Utiliza fullRepo cuando necesites la ruta completa, incluido el registro:

allow if {
  input.image.fullRepo == "docker.io/library/alpine"
}

Validar restricciones de plataforma

Las imágenes multiarquitectura admiten diferentes sistemas operativos y arquitecturas de CPU. Puedes restringir las compilaciones a plataformas específicas:

package docker

default allow := false

allow if input.local

allow if {
  input.image.os == "linux"
  input.image.arch in ["amd64", "arm64"]
}

decision := {"allow": allow}

Esta política:

  • Define las arquitecturas compatibles en una lista
  • Comprueba que input.image.os coincida con Linux
  • Verifica que input.image.arch se encuentre en la lista de compatibles

Los campos os y arch provienen del manifiesto de la imagen, reflejando la plataforma real de la imagen. Esto funciona con la selección automática de plataformas de Docker: las políticas validan lo que Buildx resuelve, no lo que especificas.

Inspeccionar metadatos de imágenes

Las imágenes contienen metadatos como variables de entorno, etiquetas y directorios de trabajo. Puedes validarlos para asegurarte de que las imágenes cumplen con los requisitos.

Comprobar variables de entorno específicas:

package docker

default allow := false

allow if input.local

allow if {
  input.image.repo == "golang"
  input.image.workingDir == "/go"
  some ver in input.image.env
  startswith(ver, "GOLANG_VERSION=")
  some toolchain in input.image.env
  toolchain == "GOTOOLCHAIN=local"
}

decision := {"allow": allow}

Esta política valida la imagen oficial de Go comprobando que:

  • El directorio de trabajo es /go
  • El entorno tiene configurado GOLANG_VERSION
  • El entorno incluye GOTOOLCHAIN=local

El campo input.image.env es un array de cadenas en formato CLAVE=VALOR. Utiliza la iteración some de Rego para buscar en el array.

Comprobar etiquetas de imagen:

allow if {
  input.image.labels["org.opencontainers.image.vendor"] == "Example Corp"
  input.image.labels["org.opencontainers.image.version"] != ""
}

El campo labels es un mapa, por lo que accedes a los valores utilizando la notación de corchetes.

Requerir atestaciones y procedencia

Las imágenes modernas incluyen atestaciones: metadatos legibles por máquina sobre cómo se compiló la imagen. Las atestaciones de procedencia (provenance) describen el proceso de compilación, y las de SBOM enumeran el software interno.

Requerir procedencia:

package docker

default allow := false

allow if input.local

allow if {
  input.image.hasProvenance
}

decision := {"allow": allow}

El campo hasProvenance es true cuando la imagen tiene atestaciones de procedencia o SBOM attestations.

Verificar firmas de GitHub Actions

Para imágenes compiladas con GitHub Actions, verifica que provengan de flujos de trabajo de confianza inspeccionando los metadatos de las firmas:

allow if {
  input.image.repo == "myapp"
  input.image.hasProvenance
  some sig in input.image.signatures
  valid_github_signature(sig)
}

# Asistente para validar la firma de GitHub Actions
valid_github_signature(sig) if {
  sig.signer.certificateIssuer == "CN=sigstore-intermediate,O=sigstore.dev"
  sig.signer.issuer == "https://token.actions.githubusercontent.com"
  startswith(sig.signer.buildSignerURI, "https://github.com/myorg/")
  sig.signer.runnerEnvironment == "github-hosted"
}

decision := {"allow": allow}

Este patrón funciona con cualquier flujo de trabajo de GitHub Actions que utilice firmas sin clave (keyless) de Sigstore. Los metadatos de la firma proporcionan una prueba criptográfica del origen de la compilación. Para ver ejemplos completos de verificación de firmas, consulta Ejemplos de políticas.

Combinar múltiples comprobaciones

Las políticas reales a menudo combinan varias comprobaciones. Múltiples condiciones en una sola regla allow significan AND: todas deben ser verdaderas:

package docker

default allow := false

allow if input.local

# Las imágenes de producción necesitan todo
allow if {
  input.image.repo == "alpine"
  input.image.isCanonical
  input.image.hasProvenance
}

decision := {"allow": allow}

Múltiples reglas allow significan OR: cualquier regla puede coincidir:

package docker

default allow := false

allow if input.local

# Permitir Alpine con comprobaciones estrictas
allow if {
  input.image.repo == "alpine"
  input.image.isCanonical
}

# Permitir Go con diferentes comprobaciones
allow if {
  input.image.repo == "golang"
  input.image.workingDir == "/go"
}

decision := {"allow": allow}

Utiliza este patrón para aplicar diferentes requisitos a diferentes imágenes base.

Siguientes pasos

Ahora comprendes cómo validar imágenes de contenedor en las políticas de compilación. Para continuar aprendiendo: