Press "Enter" to skip to content

Cómo firmar imágenes Docker utilizando Docker Content Trust

Hace poco, tuvimos el requerimiento de un cliente para realizar el firmado de sus imágenes Docker almacenadas en Azure Container Registry (ACR). Dado que era una tarea relativamente nueva para nosotros, tuvimos que documentarnos en el tema y realizar varias pruebas de concepto.

Luego de algunos intentos y de resolver varios problemas que se nos presentaron en el camino, logramos definir un proceso claro y repetible para el firmado de las imágenes Docker, a la vez que lo implementamos exitosamente en el cliente.

Debido a la dificultad que presentó realizar esta tarea y a la falta de consistencia en la documentación encontrada decidí escribir este artículo, en el cual trato de integrar todo el conocimiento y herramientas necesarias para realizar el correcto firmado de imágenes Docker siguiendo el protocolo Docker Content Trust (DCT).

🔰 Requerimientos Iniciales

🌉 La Arquitectura de Docker Content Trust (DCT)

Los componentes necesarios para realizar el proceso de firmado de imágenes Docker bajo el esquema DCT son los siguientes:

🔑 Las Keys para el firmado

Para el firmado de imágenes Docker necesitaremos principalmente 3 tipos de Keys (claves). Las describimos a continuación:

  • Root Key: Es la clave raíz para todo un registro de contenedores (container registry) y es la clave base sobre la cual se crean las Repository keys. En caso de perder la Root Key, es imposible recuperarla.
  • Repository Key: Es una clave correspondiente a un repositorio de imágenes en particular y servirá para firmar sólo los tags pertenecientes a dicho repositorio. Es creada a partir de la Root Key por lo que de perderse ésta ya no podrán crearse nuevas Repository Keys.
  • Delegation Key: Es la key correspondiente a un usuario con permisos para firmar las imágenes de un repositorio particular. Estos permisos se asignan mediante un proceso llamado delegación.
Different Key Types to sign Docker images
Different Key Types to sign Docker images

🛡️ El Notary Server

Cuando se generan las firmas de las imágenes Docker, éstas no se guardan con la imagen Docker en sí sino que se almacenan en un servidor Llamado Notary Server. Hay que tener en cuentan que no todos los registros de contenedores de imágenes soportan DCT y por ende cuentan con un Notary Server habilitado.

En nuestro caso usaremos Azure Container Registry (ACR) para almacenar nuestras imágenes el cual nos permite habilitar el protocolo DCT en sus registros de contenedores. En este caso, el Notary Server es accesible por la misma URL del registro de contenedor (usando https) como en la imagen de ejemplo.

Azure Container Registry ACR, Notary Server and DCT
Azure Container Registry and Notary Server

Cuando activamos el Notary Server, éste se habilita para el registro de contenedores, es decir para todos los repositorios almacenados en dicho registro. Adicionalmente, un repositorio de imágenes puede tener tags firmados y no firmados conviviendo sin problemas.

🚀 Desplegando un registro de contenedores en Azure (ACR)

Antes de todo debemos tener desplegado un registro de contenedores Docker, en nuestro caso haremos uso de Azure Container Registry (ACR) puesto que incluye el soporte de Docker Content Trust para el firmado de imágenes.

🐳 Creando el registro

Al momento de crear el registro de contenedores, le damos un nombre y seleccionamos como SKU a Premium puesto que este Tier es el que nos permitirá habilitar el servicio de Docker Content Trust:

Creating an Azure Container Registry
Creating an Azure Container Registry

En la siguiente página habilitamos el acceso público al registro, dejamos las demás opciones por defecto y creamos el registro:

Creating an Azure Container Registry
Creating an Azure Container Registry

Una vez creado el registro debemos habilitar DCT, para ello en la lista de opciones del registro (panel izquierdo) vamos a Policies Content trust, lo habilitamos seleccionando Status Enabled y guardamos (Save):

Enabling Docker Content Trust in ACR
Enabling Docker Content Trust in ACR

👥 Asignando permisos para el firmado de imágenes

Adicionalmente debemos asignar permisos (rol) a nuestro usuario para que éste tenga la capacidad de firmar las imágenes almacenadas en nuestro registro.

En el panel izquierdo vamos a Access control (IAM) y en la parte inferior del panel principal, en la sección Grant access to this resource seleccionamos la opción Add role assignment:

Adding AcrImageSigner role to User
Accessing to Access control (IAM) in ACR

En la lista de roles que se mostrarán buscamos el rol AcrImageSigner, lo seleccionamos y hacemos click en Next:

Adding AcrImageSigner role to User: Selecting Role
Adding AcrImageSigner role to User: Selecting Role

En la página siguiente seleccionamos +Select members y se nos mostrará a la derecha un panel en el cual buscaremos a nuestro usuario ingresando el email. Seleccionamos a nuestro usuario y ya aparecerá en la sección Members del panel principal. Seleccionamos Review + assign guardando los cambios:

Adding AcrImageSigner role to User: Applying
Adding AcrImageSigner role to User: Applying

Verificamos los permisos asignados regresando a la página inicial de Access control (IAM), seleccionamos la opción View my access y se nos mostrará un panel derecho con la lista de roles asignados a nuestro usuario. Revisamos que el rol AcrImageSigner se encuentre en la lista:

Adding AcrImageSigner role to User: Verifying
Adding AcrImageSigner role to User: Verifying

Con esto ya contamos con un registro de contenedores con DCT habilitado y con los permisos adecuados para realizar el firmado de imágenes.

Para el caso de los registros de contenedores que no soporten DCT, la solución viene por desplegar un Notary Server de manera independiente. El procedimiento se sale del alcance del presente artículo, pero usted puede guiarse del proyecto oficial de Notary en GitHub.

✒️ El Proceso de Firmado

Antes de todo debemos iniciar sesión en Azure y el registro de contenedores sobre el cual trabajaremos:

az login
az acr login --name johnrc.azurecr.io

Verificamos que estamos correctamente conectados ejecutando:

# az acr list -o table
NAME   RESOURCE  LOCATION SKU     LOGIN             CREATION             ADMIN
       GROUP                      SERVER            DATE                 ENABLED
------ --------- -------- ------- ----------------- -------------------- -------
johnrc rg_johnrc eastus   Premium johnrc.azurecr.io 2023-05-01T01:27:43Z False

🗝️ Generando las Delegation Keys

Lo primero que tenemos que realizar es generar una Delegation Key para la firma de las imágenes. Para ello, ejecutamos:

docker trust key generate john

Se nos pedirá un password para encriptar la nueva Delegation Key generada. El comando creará un par de archivos, una clave pública y una clave privada. El archivo de clave privada se almacena siempre en la carpeta ~/.docker/trust/private, y el archivo de clave pública en la carpeta desde la cual ejecutamos el comando:

~/files # docker trust key generate john
Generating key for john...
Enter passphrase for new john key with ID 6d6ade9:
Repeat passphrase for new john key with ID 6d6ade9:
Successfully generated and loaded private key. Corresponding public key available: /root/files/john.pub
Generating Delegation Key
Generating a Delegation Key using DCT

Podemos verificar la creación de la delegation key con el cliente de Notary, ejecutamos:

notary key list
# notary key list

ROLE    GUN    KEY ID           LOCATION
----    ---    -------------    --------
john           6d6ade9e896df    /root/.docker/trust/private

En caso de que ya contemos con un par de archivos de clave pública y privada que queramos reutilizar (creadas anteriormente con OpenSSL por ejemplo), podemos cargar la clave privada con el siguiente comando:

docker trust key load private.key --name john
~/files # docker trust key load private.key --name john
Loading key from "private.key"...
Enter passphrase for new john key with ID 778705c:
Repeat passphrase for new john key with ID 778705c:
Successfully imported key from private.key
Loading a Delegation Private Key from file
Loading a Delegation Private Key from file

🔐 Creando las reglas de Delegación

Una regla de delegación permite indicar quien puede firmar imágenes en un repositorio específico. Para ello necesitamos el nombre del usuario, el archivo de clave pública generado en el paso anterior y el repositorio sobre el cual se asignarán los permisos.

El archivo de clave pública puede estar en distintos formatos (.pub/.crt/.pem), esto depende de si generamos la delegation key con docker trust, o de si la creamos manualmente con una utilidad como OpenSSL.

docker trust signer add --key john.pub john johnrc.azurecr.io/java11-utils

En este ejemplo el nombre del usuario es john, el archivo de clave pública es john.pub y el repositorio johnrc.azurecr.io/java11-utils.

 # docker trust signer add --key john.pub john johnrc.azurecr.io/java11-utils
Adding signer "john" to johnrc.azurecr.io/java11-utils...
Initializing signed repository for johnrc.azurecr.io/java11-utils...
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new root key with ID 86b2ff1:
Repeat passphrase for new root key with ID 86b2ff1:
Enter passphrase for new repository key with ID a2dbb4a:
Repeat passphrase for new repository key with ID a2dbb4a:
Successfully initialized "johnrc.azurecr.io/java11-utils"
Successfully added signer: john to johnrc.azurecr.io/java11-utils

Dado que es la primera vez que creamos una delegación en el repositorio johnrc.azurecr.io/java11-utils, DCT procede a inicializarlo y para ello se crean la Root Key y Repository Key correspondientes. Se nos pedirán passwords para encriptar la Root Key y Repository Key. Se recomienda usar passwords complejos y diferentes así como guardarlos en un lugar seguro para su uso posterior.

Verificamos las claves creadas. Podemos ver las claves raíz (root), de usuario (john) y de repositorio (targets):

# notary key list
ROLE     GUN                        KEY ID               LOCATION
----     ---                        ------               --------
root                                18d9dffb748b55280ee  /root/.docker/trust/private
john                                42e9f806d309c9c4828  /root/.docker/trust/private
targets  ...zurecr.io/java11-utils  5a9647e8d848082f4d1  /root/.docker/trust/private

La próxima vez que creemos una delegación sobre un repositorio diferente, por ejemplo johnrc.azurecr.io/python9-utils, sólo se creará una nueva Repository Key para dicho repositorio y se reutilizará la Root Key ya creada anteriormente. Vale recordar que la Root Key se crea sólo una vez y las diferentes Repository Keys se generan basadas en ella.

Creating a Delegation Rule using a public key file
Creating a Delegation Rule using a public key file

✍️ Firmando y subiendo una imagen Docker

Para realizar nuestra prueba de firmado, crearemos una imagen Docker de ejemplo en el repositorio johnrc.azurecr.io/java11-utils colocándole el tag signed:

# docker build -t johnrc.azurecr.io/java11-utils:signed .
[+] Building 25.1s (6/6) FINISHED
 => [internal] load build definition from Dockerfile_alpine_java11               0.0s
 => => transferring dockerfile: 269B                                             0.0s
 => [internal] load .dockerignore                                                0.0s
 => => transferring context: 2B                                                  0.0s
 => [internal] load metadata for docker.io/library/alpine:3.17.2                 1.2s
 => CACHED [1/2] FROM docker.io/library/alpine:3.17.2@sha256:ff6bdca1701f3a      0.0s
 => [2/2] RUN apk add git util-linux && apk add openjdk11                       22.8s
 => exporting to image 1.1s
 => => exporting layers 1.1s
 => => writing image sha256:1baba7d5e04a91b85fb4e4347aeb03ba456e4b36ef6bae       0.0s
 => => naming to johnrc.azurecr.io/java11-utils:signed
#
# docker images
REPOSITORY                       TAG       IMAGE ID       CREATED      SIZE
johnrc.azurecr.io/java11-utils   signed    1baba7d5e04a   3 hours ago   297MB

Ya estamos listos para firmar y subir nuestra nueva imagen Docker al registro de contenedor de Azure. En el momento de subir la imagen, la información de firmado se guardará en el Notary Server. Ejecutamos:

docker push --disable-content-trust=false johnrc.azurecr.io/java11-utils:signed

En este ejemplo habilitamos el firmado de la imagen con la opción –disable-content-trust=false e indicamos el repositorio y tag de la imagen a firmar como johnrc.azurecr.io/java11-utils:signed

# docker push --disable-content-trust=false johnrc.azurecr.io/java11-utils:signed
The push refers to repository [johnrc.azurecr.io/java11-utils]
6cb6697bc6eb: Layer already exists
7cd52847ad77: Layer already exists
signed: digest: sha256:6ed4ea21547483944b71568e53d6f7664a6ea4b3c94d8a0a8640d1d030879b0c size: 741
Signing and pushing trust metadata
Enter passphrase for john key with ID 778705c:
Successfully signed johnrc.azurecr.io/java11-utils:signed

En este ejemplo tenemos localmente 2 imágenes: la que acabamos de crear y subir firmada (tag signed) y otra ya existente (tag v1) que se subió sin la opción –disable-content-trust=false (sin firmar):

# docker images
REPOSITORY                       TAG       IMAGE ID       CREATED       SIZE
johnrc.azurecr.io/java11-utils   signed    1baba7d5e04a   3 hours ago   297MB
johnrc.azurecr.io/java11-utils   v1        8d25badcd502   3 hours ago   14.4MB

🔍 Verificando las firmas e identificando tags firmados

El hecho de que le hayamos colocado el tag signed a una imagen no quiere decir que la imagen ya esté firmada. Para verificar si una imagen está realmente firmada o no vamos a utilizar el siguiente comando:

docker trust inspect --pretty johnrc.azurecr.io/java11-utils:signed
# docker trust inspect --pretty johnrc.azurecr.io/java11-utils:signed

Signatures for johnrc.azurecr.io/java11-utils:signed

SIGNED TAG   DIGEST                                                         SIGNERS
signed       6ed4ea21547483944b71568e53d6f7664a6ea4b3c94d8a0a8640d1d03087   john

List of signers and their keys for johnrc.azurecr.io/java11-utils:signed

SIGNER    KEYS
john      778705c2e4f4

Administrative keys for johnrc.azurecr.io/java11-utils:signed

  Repository Key:  a62a0297649e16318b32853af85dd8bcbc7fb276da62fed6f5d875fbda8f
  Root Key:  4f719a5c838d9d22eccec72ed59c49935783fb2bb92ba289d5334bad14d5

Podemos ver que se indica quién firmó la imagen (john), el id de la clave de delegación utilizada (778705c2e4f4), y el Repository/Root Keys utilizados.

En caso analizemos el tag v1 con el mismo comando, veremos que el resultado es diferente:

# docker trust inspect --pretty johnrc.azurecr.io/java11-utils:v1

No signatures for johnrc.azurecr.io/java11-utils:v1

List of signers and their keys for johnrc.azurecr.io/java11-utils:v1

SIGNER    KEYS
john      42e9f806d309

Administrative keys for johnrc.azurecr.io/java11-utils:v1

  Repository Key:       5a9647e8d84808d7003b4416f29f18810da9dbbd55c62a90cbf93c02f4d1
  Root Key:     368988454a765d285cd9489aec92d0a676dc797f07d94f92096bc08d0b086

El resultado indica claramente que la imagen analizada no cuenta con firma alguna.

Otra manera de verificar los tags firmados y no firmados en un repositorio Docker pero que funciona sólo para Azure Container Registry es ejecutando el siguiente comando de Azure CLI:

az acr repository show-tags -n johnrc.azurecr.io --repository java11-utils --detail -o table

Podemos ver en la columna Name los tags almacenados en el registro y en la columna Signed si dicho tag está firmado o no:

# az acr repository show-tags -n johnrc.azurecr.io --repository java11-utils --detail -o table
CreatedTime    Digest                          LastUpdateTime       Name    Signed
-------------  ------------------------------  -------------------  ------  --------
2023-04-21T17  sha256:826dc034c5846fa3599bdec  2023-04-21T17:06:37  latest  False
2023-04-21T19  sha256:6ed4ea21547483944b71568  2023-04-21T19:25:53  signed  True

0 0 votos
Calificación del artículo
Suscribirse
Notificar de
guest
0 Comentarios
Inline Feedbacks
View all comments
error: Contenido protegido!
0
Déjanos tus comentarios!x
()
x