Skip to main content

Formatos de imágenes de contenedor por dentro

Escrito por
Headshot of Agata Krajewska

Agata Krajewska

18 de noviembre de 2020

0 minutos de lectura

En los últimos años, desde el lanzamiento de Docker, los contenedores se han convertido cada vez más en el mecanismo estándar para la entrega de software.

Vemos un número creciente de soluciones basadas en contenedores y, aunque sin duda celebramos la innovación en este ámbito, es necesario establecer ciertos estándares en torno al formato y el entorno de ejecución.

Debido al rápido crecimiento del proyecto Docker, las imágenes de Docker se convirtieron en un estándar para muchos fines. Sin embargo, hay un interés generalizado en contar con una especificación de contenedores abierta y única que:

  • no esté vinculada a construcciones de nivel superior, como un cliente o una pila de orquestación específicos,

  • no esté estrechamente asociada a ningún proveedor comercial o proyecto en particular,

  • sea portable en una amplia variedad de sistemas operativos, hardware, arquitecturas de CPU, nubes públicas, etc.

OCI (Open Container Initiative) es un proyecto de Linux Foundation para diseñar estándares abiertos de virtualización a nivel del sistema operativo, especialmente de contenedores Linux.

Docker donó a OCI tanto un borrador de especificación para el formato base y el entorno de ejecución como el código asociado a una implementación de referencia de esa especificación.

Varias herramientas de compilación de código abierto ya admiten el formato de imágenes OCI, entre ellas:

  • BuildKit: una versión optimizada y reescrita del motor de compilación de Docker

  • Podman: una implementación alternativa de la herramienta de línea de comandos de Docker

  • Buildah: una alternativa de línea de comandos para escribir archivos Dockerfile

Snyk admite todos los formatos de imágenes de contenedor habituales y, por eso, podemos integrarnos con una amplia variedad de herramientas de todo el ecosistema. En esta publicación, veremos algunas de ellas y mostraremos cómo funciona Snyk con estas herramientas.

Archivo de Docker

Primero, veamos el formato de archivo de Docker. Forma parte de la versión 1 obsoleta de la especificación de imágenes de Docker y puedes encontrar la especificación completa aquí. Cuando ejecutas docker save, este es el formato predeterminado que genera el cliente de Docker. Se usa para mantener la compatibilidad con otras herramientas. Los registros de contenedores usan un formato más reciente para almacenar las imágenes

Si ejecutas Docker de forma local y usas la CLI de Snyk o cualquiera de nuestras integraciones de contenedores, usaremos tu instancia local de Docker para extraer la imagen, guardarla en el sistema de archivos e iniciar el análisis.

Exploremos cómo es ese archivo y qué contiene que pueda interesarle a Snyk.

En esta publicación usaré la imagen ubuntu:bionic. Primero, guardemos el archivo en tu sistema de archivos local:

docker save --output ubuntu-docker.tar ubuntu:bionic

La distribución bionic tiene tres capas de imagen, que veremos más de cerca.

Después de descomprimir el archivo tar, podemos inspeccionar su contenido:

[ubuntu-docker] ll                                                                                                               
total 24
drwxr-xr-x  5 agatakrajewska  staff   160B 25 Sep 23:33 46076a325f0de3f745254638b8b0f0de343685b34e7ca6ec5cd0b6b7930eb7fa
drwxr-xr-x  5 agatakrajewska  staff   160B 25 Sep 23:33 468327b5cd7ce539db695bd0ef05dae8a4ff77b02870a8e823ed74dedad4bd55
-rw-r--r--  1 agatakrajewska  staff   3.3K 25 Sep 23:33 56def654ec22f857f480cdcc640c474e2f84d4be2e549a9d16eaba3f397596e9.json
drwxr-xr-x  5 agatakrajewska  staff   160B 25 Sep 23:33 8bf067b107a6f7444876e33c6ed85652355f679ac98ebab97ab3ebad63f0dff3
-rw-r--r--  1 agatakrajewska  staff   356B  1 Jan  1970 manifest.json
-rw-r--r--  1 agatakrajewska  staff    89B  1 Jan  1970 repositories

Veamos manifest.json:

[ubuntu-docker] cat manifest.json | jq                                                                                           
[
  {
    "Config": "56def654ec22f857f480cdcc640c474e2f84d4be2e549a9d16eaba3f397596e9.json",
    "RepoTags": [
      "ubuntu:bionic"
    ],
    "Layers": [
      "8bf067b107a6f7444876e33c6ed85652355f679ac98ebab97ab3ebad63f0dff3/layer.tar",
      "468327b5cd7ce539db695bd0ef05dae8a4ff77b02870a8e823ed74dedad4bd55/layer.tar",
      "46076a325f0de3f745254638b8b0f0de343685b34e7ca6ec5cd0b6b7930eb7fa/layer.tar"
    ]
  }
]

El archivo nos dirige a la configuración del contenedor, donde encontramos información muy útil, como la arquitectura, la configuración, las capas del sistema de archivos raíz, etc. Además, probablemente la parte más interesante desde el punto de vista de Snyk, la propiedad Layers nos dirige a las capas reales de la imagen, que no son más que directorios dentro del archivo. Si entramos en uno de estos directorios, podemos ver qué contiene:

[8bf067b107a6f7444876e33c6ed85652355f679ac98ebab97ab3ebad63f0dff3] ll                                                            
total 128160
-rw-r--r--  1 agatakrajewska  staff     3B 25 Sep 23:33 VERSION
-rw-r--r--  1 agatakrajewska  staff   401B 25 Sep 23:33 json
-rw-r--r--  1 agatakrajewska  staff    63M 25 Sep 23:33 layer.tar

Profundicemos un poco más y descomprimamos layer.tar:

[layer] ll                                                                                                                       
total 0
drwxr-xr-x  87 agatakrajewska  staff   2.7K 21 Sep 18:17 bin
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 boot
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:17 dev
drwxr-xr-x  68 agatakrajewska  staff   2.1K 21 Sep 18:17 etc
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 home
drwxr-xr-x   8 agatakrajewska  staff   256B 23 May  2017 lib
drwxr-xr-x   3 agatakrajewska  staff    96B 21 Sep 18:16 lib64
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 media
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 mnt
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 opt
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 proc
drwx------   4 agatakrajewska  staff   128B 21 Sep 18:17 root
drwxr-xr-x   5 agatakrajewska  staff   160B 21 Sep 18:14 run
drwxr-xr-x  68 agatakrajewska  staff   2.1K 21 Sep 18:17 sbin
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 srv
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 sys
drwxrwxrwt   2 agatakrajewska  staff    64B 21 Sep 18:17 tmp
drwxr-xr-x  10 agatakrajewska  staff   320B 21 Sep 18:14 usr
drwxr-xr-x  13 agatakrajewska  staff   416B 21 Sep 18:17 var

Como puedes ver arriba, layer.tar es simplemente un conjunto de cambios en el sistema de archivos de la capa de imagen, con todas las dependencias y otros binarios, según el contenido de la imagen. Después de extraer y analizar el contenido

de esas capas, podemos mostrarte la lista de rutas vulnerables.

Ahora ejecutemos snyk test en la imagen del archivo de Docker guardado. Podemos analizar un archivo local especificando el prefijo docker-archive::

snyk container test docker-archive:ubuntu-docker.tar o analizar la imagen desde tu repositorio local de Docker:

snyk container test ubuntu:bionic

Ambos comandos generan exactamente el mismo resultado:

Testing ubuntu:bionic...

✗ Low severity vulnerability found in tar
  Description: Loop with Unreachable Exit Condition ('Infinite Loop')
  Info: <https://snyk.io/vuln/SNYK-UBUNTU1804-TAR-312298>
  Introduced through: tar@1.29b-2ubuntu0.1, pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2
  From: tar@1.29b-2ubuntu0.1
  From: pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2 > debconf@1.5.66ubuntu1 > perl/perl-base@5.26.1-6ubuntu0.3 > dpkg@1.19.0.5ubuntu2.3 > tar@1.29b-2ubuntu0.1

✗ Low severity vulnerability found in tar
  Description: NULL Pointer Dereference
  Info: <https://snyk.io/vuln/SNYK-UBUNTU1804-TAR-559435>
  Introduced through: tar@1.29b-2ubuntu0.1, pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2
  From: tar@1.29b-2ubuntu0.1
  From: pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2 > debconf@1.5.66ubuntu1 > perl/perl-base@5.26.1-6ubuntu0.3 > dpkg@1.19.0.5ubuntu2.3 > tar@1.29b-2ubuntu0.1

# vuln list continues in here
...

Organization:      example-org
Package manager:   deb
Project name:      docker-image|ubuntu
Docker image:      ubuntu:bionic
Licenses:          enabled

Tested 90 dependencies for known issues, found 31 issues.

Pro tip: use `--file` option to get base image remediation advice.
Example: $ snyk test --docker ubuntu:bionic --file=path/to/Dockerfile

Imagen OCI

En los últimos meses también agregamos el análisis de archivos OCI a la lista de funciones de la CLI de Snyk.

Veamos la especificación y qué encontramos en el archivo. También usaré ubuntu:bionic en esta sección.

Si quieres inspeccionar por tu cuenta un archivo tar de OCI, puedes ejecutar el siguiente comando:

skopeo copy --override-os linux docker://ubuntu:bionic oci-archive:ubuntu.tar

Aquí uso Skopeo para guardar la imagen. Es una utilidad de cli que facilita mucho realizar distintas operaciones con imágenes.

Para guardar una imagen ubuntu en formato OCI, solo tenemos que especificar el prefijo oci-archive: en la salida tar.

Ten en cuenta que también uso la opción --override-os: estoy en macOS y las imágenes de Ubuntu de Docker Hub oficiales solo están disponibles para Linux. Más adelante también hablaremos de las imágenes con varias arquitecturas.

Una vez que descomprimimos el archivo tar, podemos inspeccionar su contenido:

[ubuntu] ll                                                                                                                      
total 16
drwxr-xr-x  3 agatakrajewska  staff    96B 28 Oct 11:18 blobs
-rw-r--r--  1 agatakrajewska  staff   186B 28 Oct 11:18 index.json
-rw-r--r--  1 agatakrajewska  staff    31B 28 Oct 11:18 oci-layout

En la raíz encontramos un interesante index.json. Inspeccionemos su contenido:

[ubuntu] cat index.json | jq                                                                                                     
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:afa93a8ce255ca452ca8c88f4b5c821a466cf0a3e0148a31d0d97dfdb91d9aef",
      "size": 658
    }
  ]
}

Este es nuestro manifiesto opcional de nivel superior, que nos dirige a manifiestos de imágenes específicos. Contiene información sobre un conjunto de imágenes que pueden abarcar distintas arquitecturas y sistemas operativos. Si quieres saber más, aquí está la especificación completa.

Ahora inspeccionemos el manifiesto específico de nuestra imagen, al que nos dirigió index.json, en el directorio blobs/sha256:

[sha256] cat afa93a8ce255ca452ca8c88f4b5c821a466cf0a3e0148a31d0d97dfdb91d9aef | jq                                               
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:33a51d09088285451e7a7525d4bd64fc15563264afe5a91ef84a8b3042018899",
    "size": 2426
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:171857c49d0f5e2ebf623e6cb36a8bcad585ed0c2aa99c87a055df034c1e5848",
      "size": 26701612
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:419640447d267f068d2f84a093cb13a56ce77e130877f5b8bdb4294f4a90a84f",
      "size": 852
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:61e52f862619ab016d3bcfbd78e5c7aaaa1989b4c295e6dbcacddd2d7b93e1f5",
      "size": 162
    }
  ]
}

Empezamos a encontrar contenido interesante, como la configuración del contenedor, donde de nuevo vemos mucha información útil sobre la arquitectura de las imágenes, las capas de la imagen base, la configuración, etc. También tenemos información sobre las capas de la imagen, que nos indica cuáles están disponibles en nuestra imagen ubuntu:bionic.

Como vemos abajo, el contenido de la primera capa es exactamente el mismo que el del archivo de Docker.

[171857c49d0f5e2ebf623e6cb36a8bcad585ed0c2aa99c87a055df034c1e5848] ll                                                            
total 0
drwxr-xr-x  87 agatakrajewska  staff   2.7K 21 Sep 18:17 bin
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 boot
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:17 dev
drwxr-xr-x  68 agatakrajewska  staff   2.1K 21 Sep 18:17 etc
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 home
drwxr-xr-x   8 agatakrajewska  staff   256B 23 May  2017 lib
drwxr-xr-x   3 agatakrajewska  staff    96B 21 Sep 18:16 lib64
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 media
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 mnt
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 opt
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 proc
drwx------   4 agatakrajewska  staff   128B 21 Sep 18:17 root
drwxr-xr-x   5 agatakrajewska  staff   160B 21 Sep 18:14 run
drwxr-xr-x  68 agatakrajewska  staff   2.1K 21 Sep 18:17 sbin
drwxr-xr-x   2 agatakrajewska  staff    64B 21 Sep 18:14 srv
drwxr-xr-x   2 agatakrajewska  staff    64B 24 Apr  2018 sys
drwxrwxrwt   2 agatakrajewska  staff    64B 21 Sep 18:17 tmp
drwxr-xr-x  10 agatakrajewska  staff   320B 21 Sep 18:14 usr
drwxr-xr-x  13 agatakrajewska  staff   416B 21 Sep 18:17 var

Puedes analizar tu archivo OCI local ejecutando:

snyk container test oci-archive:ubuntu.tar

Plataformas y arquitecturas variadas

Al inspeccionar los manifiestos del archivo de Docker y de la imagen OCI, vimos que ambos incluían información sobre la arquitectura. Volvamos a ver el contenido del manifiesto de configuración de nuestro contenedor, en el directorio ubuntu-docker, donde guardamos nuestro docker archive:

[sha256] cat 56def654ec22f857f480cdcc640c474e2f84d4be2e549a9d16eaba3f397596e9.json | jq
{
  ...
  "architecture": "amd64",
  "os": "linux",
  ...
}

Entre otros datos, vemos la arquitectura y el sistema operativo: en mi caso, amd64 y linux. ¿Qué pasa si nuestra imagen se basa en arm64 o si estamos en una plataforma windows?

Tenemos suerte: Snyk también admite otras plataformas. Solo tienes que pasar la opción -—platform a la CLI de Snyk.

Ya hablamos del formato de archivo de docker save. Veamos el formato en que se almacenan las imágenes en Docker Hub: el esquema v2, que tiene aspectos muy interesantes por dentro. Esta es la segunda versión del esquema v2 y se creó para cumplir dos objetivos principales. Según la documentación de Docker:

El primero es permitir imágenes con varias arquitecturas mediante un «manifiesto fat» que hace referencia a manifiestos de imágenes para las versiones de una imagen específicas de cada plataforma. El segundo es acercar el motor de Docker a imágenes direccionables por contenido, mediante la compatibilidad con un modelo de imágenes cuya configuración se puede resumir mediante un hash para generar un ID de imagen.

Con nuestro Docker local, primero podemos inspeccionar el «manifiesto fat» de la imagen que nos interesa para comprobar qué variantes de plataforma están disponibles. Como explicamos arriba, esto significa que un mismo repositorio puede alojar varias imágenes para distintas arquitecturas.

docker manifest inspect ubuntu:bionic

Como resultado, veremos una matriz de manifiestos:

{
   ...
  "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 943,
         "digest": "sha256:45c6f8f1b2fe15adaa72305616d69a6cd641169bc8b16886756919e7c01fa48b",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 943,
         "digest": "sha256:e80b8affb2361dc632c1fa8fcbf6b6514f750eb6ef99b7e7f825a55f849bfd89",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 943,
         "digest": "sha256:01a2038b20d165ab7df81934f9849bdfbc59bd6f6322c5d11e341504f66ec266",
         "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
         }
      },
...

Ahora que sabemos que la imagen ubuntu está disponible para distintas plataformas, veamos cómo analizar la imagen ubuntu para la arquitectura de plataforma arm64.

Asegúrate de que las funciones experimentales estén habilitadas en Docker en tu equipo y ejecuta:

snyk container test --platform=linux/arm64 ubuntu:bionic

Veamos el resultado:

Testing ubuntu:bionic...

✗ Low severity vulnerability found in tar
  Description: Loop with Unreachable Exit Condition ('Infinite Loop')
  Info: <https://snyk.io/vuln/SNYK-UBUNTU1804-TAR-312298>
  Introduced through: tar@1.29b-2ubuntu0.1, pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2
  From: tar@1.29b-2ubuntu0.1
  From: pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2 > debconf@1.5.66ubuntu1 > perl/perl-base@5.26.1-6ubuntu0.3 > dpkg@1.19.0.5ubuntu2.3 > tar@1.29b-2ubuntu0.1

✗ Low severity vulnerability found in tar
  Description: NULL Pointer Dereference
  Info: <https://snyk.io/vuln/SNYK-UBUNTU1804-TAR-559435>
  Introduced through: tar@1.29b-2ubuntu0.1, pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2
  From: tar@1.29b-2ubuntu0.1
  From: pam/libpam-runtime@1.1.8-3.6ubuntu2.18.04.2 > debconf@1.5.66ubuntu1 > perl/perl-base@5.26.1-6ubuntu0.3 > dpkg@1.19.0.5ubuntu2.3 > tar@1.29b-2ubuntu0.1

# vuln list continues in here
...

Organization:      example-org
Package manager:   apk
Project name:      docker-image|ubuntu:bionic
Docker image:      alpine:3.12
Platform:          linux/arm64
Licenses:          enabled

Tested 90 dependencies for known issues, found 31 issues.

Pro tip: use `--file` option to get base image remediation advice.
Example: $ snyk test --docker ubuntu:bionic --file=path/to/Dockerfile

Podemos ver todas las rutas vulnerables que se detectaron y también la información de «Platform» en el resultado del análisis.

En esta publicación, exploramos los dos formatos principales de imágenes de contenedor, Docker y OCI, y mostramos cómo Snyk puede interactuar con ambos, incluso en varias arquitecturas.

Para terminar, sigue las prácticas recomendadas de seguridad para crear imágenes Docker optimizadas para aplicaciones de Node.js y Java:

  1. 10 prácticas recomendadas de seguridad para Docker: encontrarás detalles sobre las prácticas de seguridad que debes seguir al crear imágenes base de Docker y al descargarlas. También conocerás Docker Content Trust.

  2. ¿Desarrollas en Java? Este recurso te resultará útil: Docker para desarrolladores de Java: 5 aspectos que debes conocer para no poner en riesgo tu seguridad

  3. 10 prácticas recomendadas para contenerizar aplicaciones web de Node.js con Docker: si desarrollas en Node.js, te encantará esta guía paso a paso, que muestra cómo crear imágenes base de Docker seguras y eficientes para tus aplicaciones de Node.js.

Puedes acceder gratis a todas estas funciones en Snyk: regístrate para crear una cuenta.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Publicado en:

Leer más

Blog

Verano en los estadios: la gira Snyk Connect Fan Zone

La gira Fan Zone de Snyk llevó talleres de seguridad de IA, networking y competencia amistosa a 8 ciudades y 3 sesiones virtuales. Los asistentes desarrollaron habilidades, compartieron ideas y crecieron juntos.

Blog

Snyk VulnBench JS 1.0: ¿Pueden los LLM encontrar los mismos errores dos veces?

Snyk VulnBench JS 1.0: 300 análisis repetidos muestran que los hallazgos de seguridad de los LLM varían en cada ejecución, mientras que SAST y los modelos detectan distintas vulnerabilidades.

blog feature toolkit
Blog

Publicación maliciosa del paquete elementary-data en PyPI roba credenciales en la nube de ingenieros de datos

Los atacantes aprovecharon una vulnerabilidad de inyección de scripts en GitHub Actions para publicar una versión maliciosa de la CLI de Python elementary-data (v0.23.3), que incluía una puerta trasera para robar credenciales de perfiles de dbt, claves de proveedores de nube y secretos SSH en entornos de ingeniería de datos.