Firma de módulos Docker con Docker Content Trust

Introducción

La firma de artefactos docker, busca tener la garantía de que una imagen Docker no ha sido alterada una vez generada. Implica dos procesos:

  • Proceso de firma: Integrado en la generación de la imagen y previo a publicarla en un registro Docker.

  • Verificación de la firma: Dependiente del entorno donde se va a ejecutar la imagen (Docker, Kubernetes…). Implica verificar la firma justo después de descargar la imagen y antes de ejecutarla.

Tanto en la firma como en la verificación intervienen una serie de elementos de infraestructura y procesos que se detallan en el presente documento.

Proceso de firma y verificación

Docker contempla la firma de artefactos mediante un procedimiento denominado Docker Content Trust (DCT).

En este proceso entra en juego Notary (https://github.com/theupdateframework/notary ). Se trata de una herramienta que siguiendo la especificación de DCT, permite firmar y publicar y gestionar contenido confiable mediante firma.

Notary se integra con Docker durante la generación de las imágenes, para incluirles una firma electrónica, los datos de la firma se almacenan en el servidor de Notary para poder ser verificados posteriormente cuando se descarguen.

Una vez firmada la imagen, esta se despliega en un registro Docker desde donde se podrá descargar.

La verificación de la imagen al descargarla, de forma previa a su ejecución se puede realizar de forma directa por Docker si se activa DCT. El problema viene cuando la imagen se despliega en Kubernetes, como se hace con Rancher 2 y Openshift. En este caso, es necesaria otra pieza, un Admision Controller que haga la verficación. En esta guía se ha elegido Connaisseur, que se puede instalar directamente sobre Kubernetes.

Existen otros Admision Controller para la verificación de la firma en Kubernetes. En concreto se han estudiado dos:

Se ha elegido Connaisseur por su facilidad de instalación y configuración

Instalación de Notary

NOTA: En esta guía se hace referencia a la variable de entorno $DEVOP_TOOLS_BASE como directorio base donde se han instalado las devop-tools de https://github.com/mmorancassy/devops-tools/. Dicha variable no existe y se habrá de sustituir por la ruta correspondiente

Notary se debe integrar en las herramientas de devops de la máquina donde estas están instaladas, de manera que se despliega dockerizado e integrado en el mismo docker-compose que arranca toda la suite (gitlab, Jenkins, nexus…)

El despliegue de Notary implica incluir tres nuevos servicios docker a las herramientas. En concreto:

  • Notary-server

  • Notary-signer

  • Notary-db

Descarga de Notary

Notary se dockeriza desde el propio código fuente de su repositorio en github (https://github.com/theupdateframework/notary.git).

Para ello se clona dicho repositorio en el directorio $DEVOP_TOOLS_BASE/devops-tools

cd $DEVOP_TOOLS_BASE/devops-tools git clone https://github.com/theupdateframework/notary.git

En un paso posterior se integra este directorio en el fichero docker-compose de las devops-tools para incluir la generación de las imágenes docker de Notary a partir de este código fuente.

Certificados

Notary-server y Notary-signer necesitan sendos certificados para exponer via https sus endpoints. Se deben crear mediante el script $DEVOP_TOOLS_BASE/devops-tools/centos7-notary.sh

.$DEVOP_TOOLS_BASE/devops-tools/centos7-notary.sh mkdir $DEVOP_TOOLS_BASE/devops-tools/notary/certs mv /tmp/tls-notary $DEVOP_TOOLS_BASE/devops-tools/notary/certs

 

Modificar la configuración del docker-compose

Para incluir los servicios de Notary en el docker-compose desde el que se arrancan todos los servicios dockerizados, se debe modificar el fichero $DEVOP_TOOLS_BASE/devops-tools/cicd-tools/docker-compose.fullcicd.yml y añadir las variables de entorno utilizadas en dichos servicios, en el fichero $DEVOP_TOOLS_BASE/devops-tools/cicd-tools/.env

Para ello:

nano $DEVOP_TOOLS_BASE/devops-tools/cicd-tools/.env

Y añadir la siguiente configuración:

 

Y añadir la siguiente configuración:

Que incluye: Los 3 servicios de Notary

Y mapea varios volúmenes en la imagen de Jenkins ya existente:

 

Configuración de Notary

A través del docker-compose, pasándola por parámetros, se debe externalizar la configuración tanto del notary-signer, como el notary-server, así como de la base de datos.

Añadir el siguiente contenido a server-config.json

 

Y añadir el siguiente contenido a signer-config.json

Asímismo hay que configurar la Base de datos con un Script de migración (por si se hacen actualizaciones) y los datos iniciales:

Y añadirle el siguiente contenido:

Ejecutar:

Y añadirle el siguiente contenido:

 

Y añadir el siguiente contenido:

 

Instalar y configurar Notary CLI

Notary CLI es una herramienta de Notary utilizada desde la parte cliente (En nuestro caso Jenkins) para firmar los artefactos y comunicarse con el Notary-server. Por lo que es necesario descargarlo y posteriormente mapearlo al contenedor de Jenkins.

Notary CLI utiliza una configuración para conectarse al servidor de notary. Para crear dicha configuración:

Y añadir el siguiente contenido:

 

Crear el directorio de claves de Notary

Establecer la confianza entre el Jenkins y Notary

Al ser el certificado de Notary un certificado autofirmado, es necesario establecer la confianza en Jenkins. Para ello se ha mapeado el cacert.crt al directorio de certificados de confianza (/etc/ssl/certs/) del contenedor de Jenkins. Este se ha hecho en un paso previo en el docker-compose, aquí simplemente se ilustra:

En caso de que se quiera hacer este cambio en caliente. Ejecutar:

Nota: en imágenes RHEL el directorio y comando son:

 

Generación de Claves de Firma

Se debe generaar una clave de firma para la plataforma. Para ilustrar los siguientes pasos y comandos de esta guía la llamaremos: onesaitkey con password: onesaitkeypwd

La forma de generarla ha sido accediendo al contenedor de Jenkins y generando la clave directamente:

Al estar el directorio del contenedor /root/.docker/trust mapeado en la máquina 10.1.0.17 al directorio $DEVOP_TOOLS_BASE/devops-tools/notary/keys, dichas claves quedan disponibles sin tener que acceder al contenedor.

Añadir el firmante onesaitkey para el artefacto pasando la password: onesaitkeypwd

 

Para facilitar la generación de claves y añadir firmantes, existe la posibilidad de crear sendos pipelines de Jenkins, que ejecuten esta tarea.

Pipeline de generación de claves

Este pipeline ejecuta el comando:

Admite como parámetros KEY_NAME y KEY_PASSWORD

El código del pipeline es:

Pipeline de asociación de claves a imágenes

Este pipeline ejecuta el comando:

Admite como parámetros SIGN_KEY_NAME, IMAGE_TO_SIGN, SIGNER_KEY_PASSWORD y ROOT_KEY_PASSWORD

El código del pipeline es:

 

Firma

La firma se realiza en la generación de los docker en Jenkins. Para ello, sobre la estructura habitual de los proyectos:

  • Añadir al Jenkinsfile el login en el registro de Nexus seguro:

  • Añadir en image-generation.sh los export necesarios para DCT

Donde en pushImage se ha añadido la password de la clave de firma:

 

Verificación en Kubernetes - Connaisseur

Docker Content Trust como tal no es válido para para verificar la firma en Kubernetes. Para hacer la verificación de firma en Kubernetes, es necesario añadir un Admision Controller que realice tal tarea de forma automática.

Instalación de Connaisseur

Instalar kubectl y configurarlo para que apunte al cluster donde se quiere realizar la instalación. La configuración de kubectl se realiza en el fichero ~.kube/config, que tiene que tener la configuración indicada para kubectl en Rancher en la sección de clusters.

La instalación de connaisseur se hace siguiendo los pasos indicados en https://github.com/sse-secure-systems/connaisseur :

Instalar previamente: kubectl, helm, git, make, openssl e yq

Clonar el repositorio:

git clone https://github.com/sse-secure-systems/connaisseur.git

Acceder al directorio connaisseur y modificar el fichero Makefile para añadir la opción --debug a todos los comandos helm del Makefile. Pej:

Configurar en el fichero connaisseur/helm/values.yaml, el servidor de notary, certificado de la CA, clave de firma de root para los artefactos Docker y política de firma en el fichero helm/values.yaml

En concreto los valores a configurar son:

  • notary.host: <notary_server>:<notary_server_port>

  • notary.selfsigned: true

  • notary.selfsignedCert: <certificado de la CA del notary-server ($DEVOP_TOOLS_BASE/devops-tools/notary/notary-cli-config/certs/cacert.crt)>

  • notary.rootPubKey: <clave pública de firma>. Se extrae del directorio de claves de notary generadas en Jenkins según el procedimiento que se indica a continuación.

  • policy: Añadir las políticas a aplicar. Por defecto está ignorar la firma de todo excepto de los artefactos de onesait-banking:

El procedimiento para extraer el valor de la propiedad notary.rootPubKey es ejecutar en la máquina donde están las devops-tools:

La password solicitada es la de la clave de firma

El valor a proveer al fichero values.yaml está el fichero root-pub.pem

 

Una vez configurado el vichero values.yaml, ejecutar desde el directorio connaisseur:

En caso de querer modificar algún valor, se puede actualizar con: