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:
IBM Portieris: https://github.com/IBM/portieris
Connaisseur: https://github.com/sse-secure-systems/connaisseur
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: