---
title: "Firma de módulos Docker con Docker Content Trust"
canonical: "https://onesaitplatform-es.refined.site/space/DOC/2216103968/Firma%20de%20m%C3%B3dulos%20Docker%20con%20Docker%20Content%20Trust"
format: markdown
---
> Macro (toc)

# 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](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.

![image](media://ecacc4da-8359-439b-b199-a290875077f4)

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](https://github.com/IBM/portieris)
- Connaisseur: [https://github.com/sse-secure-systems/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/](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](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:

```
#Notary config
NOTARY_CERTS=../notary/certs
NOTARY_SERVER_CONFIG=../notary/fixtures/server-config.json
NOTARY_SIGNER_CONFIG=../notary/fixtures/signer-config.json
NOTARY_DB_MIGRATIONS=../notary/migrations/migrate.sh
NOTARY_DB_INITDB=../notary/mysql-initdb.d
NOTARY_CLI=../notary/cli/notary
NOTARY_CLI_HOME=../notary/notary-cli-config
NOTARY_KEYS=../notary/keys
```


```
nano $DEVOP_TOOLS_BASE/devops-tools/cicd-tools/docker-compose.fullcicd.yml
```

Y añadir la siguiente configuración:

Que incluye: Los 3 servicios de Notary

```
notary-server:
    build:
      context: ../docker-images/notary
      dockerfile: server.Dockerfile
    networks:
      - mdb
      - sig
    volumes:
      - ${NOTARY_CERTS}:/opt/certs/
      - ${NOTARY_SERVER_CONFIG}:/server-config.json
      - ${NOTARY_DB_MIGRATIONS}:/migrate.sh
    ports:
      - "8080"
      - "4443:4443"
    entrypoint: /usr/bin/env sh
    command: -c "/migrate.sh && notary-server -config=/server-config.json"
    container_name: notary-server
    depends_on:
      - notary-db
      - notary-signer

  notary-signer:
    build:
      context: ../docker-images/notary
      dockerfile: signer.Dockerfile
    networks:
      mdb:
      sig:
        aliases:
          - notarysigner
    volumes:
      - ${NOTARY_CERTS}:/opt/certs/
      - ${NOTARY_SIGNER_CONFIG}:/signer-config.json
      - ${NOTARY_DB_MIGRATIONS}:/migrate.sh
    entrypoint: /usr/bin/env sh
    command: -c "/migrate.sh && notary-signer -config=/signer-config.json"
    container_name: notary-signer
    depends_on:
      - notary-db

  notary-db:
    networks:
      - mdb
    volumes:
      - ${NOTARY_DB_INITDB}:/docker-entrypoint-initdb.d
      - ${DATA_PATH}/notary/notary_data:/var/lib/mysql
    image: mariadb:10.4
    environment:
      - TERM=dumb
      - MYSQL_ALLOW_EMPTY_PASSWORD="true"
    command: mysqld --innodb_file_per_table
    container_name: notary-db
```

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

```
jenkinscicd:
    image: onesaitplatform/jenkins:${JENKINS_VERSION}
    container_name: jenkinscicd
    environment:
      - RESTORE_DEFAULTS=false
      - RESTORE_MAVEN_SETTINGS=false
    volumes:
      - ${DATA_PATH}${JENKINS_DATA}:/var/jenkins_home:rw
      - ${DATA_PATH}${PLATFORM_CONFIG}:/tmp/op:rw
      - ${DOCKER_DATA}:/var/run/docker.sock
      - ${NOTARY_CLI}:/bin/notary
      - ${NOTARY_CLI_HOME}:/root/.notary
      - ${NOTARY_CLI_HOME}/cacert.crt:/etc/ssl/certs/cacert-notary.crt
      - ${NOTARY_KEYS}:/root/.docker/trust
    restart: on-failure

```


### 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.

```
mkdir –p $DEVOP_TOOLS_BASE/devops-tools/notary/fixtures
cd $DEVOP_TOOLS_BASE/devops-tools/notary/fixtures
nano server-config.json
```

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

```
{
       "server": {
          	"http_addr": ":4443",
             "tls_key_file": "/opt/certs/notary-server.key",
                			"tls_cert_file": "/opt/certs/notary-server.crt"
        			},
        			"trust_service": {
               			 "type": "remote",
              "hostname": "notary-signer",
                			 "port": "7899",
              "tls_ca_file": "/opt/certs/cacert.crt",
              "key_algorithm": "ecdsa",
              "tls_client_cert": "/opt/certs/notary-server.crt",
              "tls_client_key": "/opt/certs/notary-server.key"
        },
        			       "logging": {
               "level": "debug"
        },
        "storage": {
                "backend": "mysql",
                "db_url": "server@tcp(notary-db:3306)/notaryserver?parseTime=True"
        }
}
```


```
nano signer-config.json
```

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

```
{
        "server": {
                "grpc_addr": ":7899",
                "tls_cert_file": "/opt/certs/notary-signer.crt",
                "tls_key_file": "/opt/certs/notary-signer.key",
                "client_ca_file": "/opt/certs/cacert.crt"
        },
        "logging": {
                "level": "debug"
        },
        "storage": {
                "backend": "mysql",
                "db_url": "signer@tcp(notary-db:3306)/notarysigner?parseTime=True"
        }
}
```

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

```
mkdir –p $DEVOP_TOOLS_BASE/devops-tools/notary/migrations
nano migrate.sh
```

Y añadirle el siguiente contenido:

```
#!/usr/bin/env sh

# When run in the docker containers, the working directory
# is the root of the repo.

iter=0

case $SERVICE_NAME in
        notary_server)
                MIGRATIONS_PATH=${MIGRATIONS_PATH:-migrations/server/mysql}
                DB_URL=${DB_URL:-mysql://server@tcp(notary-db:3306)/notaryserver}
                # have to poll for DB to come up
                until migrate -path=$MIGRATIONS_PATH -database="${DB_URL}" up
                do
                        iter=$(( iter+1 ))
                        if [[ $iter -gt 30 ]]; then
                                echo "notaryserver database failed to come up within 30 seconds"
                                exit 1;
                        fi
                        echo "waiting for $DB_URL to come up."
                        sleep 1
                done
                echo "notaryserver database migrated to latest version"
                ;;
        notary_signer)
                MIGRATIONS_PATH=${MIGRATIONS_PATH:-migrations/signer/mysql}
                DB_URL=${DB_URL:-mysql://signer@tcp(notary-db:3306)/notarysigner}
                # have to poll for DB to come up
                until migrate -path=$MIGRATIONS_PATH -database="${DB_URL}" up
                do
                        iter=$(( iter+1 ))
                        if [[ $iter -gt 30 ]]; then
                                echo "notarysigner database failed to come up within 30 seconds"
                                exit 1;
                        fi
                        echo "waiting for $DB_URL to come up."
                        sleep 1
                done
                echo "notarysigner database migrated to latest version"
                ;;
esac
```

Ejecutar:

```
chmod 777 migrations.sh
mkdir –p $DEVOP_TOOLS_BASE/devops-tools/notary/mysql-initdb.d
cd $DEVOP_TOOLS_BASE/devops-tools/notary/mysql-initdb.d
nano initial-notaryserver.sql
```

Y añadirle el siguiente contenido:

```
CREATE DATABASE IF NOT EXISTS `notaryserver`;

CREATE USER "server"@"%" IDENTIFIED BY "";

GRANT ALL PRIVILEGES ON `notaryserver`.* TO "server"@"%";
```


```
nano initial-notarysigner.sql
```

Y añadir el siguiente contenido:

```
CREATE DATABASE IF NOT EXISTS `notarysigner`;

CREATE USER "signer"@"%" IDENTIFIED BY "";

GRANT ALL PRIVILEGES ON `notarysigner`.* TO "signer"@"%";
```


### 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.

```
mkdir –p $DEVOP_TOOLS_BASE/devops-tools/notary/cli
cd devops-tools/notary/cli
wget https://github.com/theupdateframework/notary/releases/download/v0.6.1/notary-Linux-amd64
mv $DEVOP_TOOLS_BASE/notary-Linux-amd64 notary
chmod 755 notary
```

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

```
mkdir –p $DEVOP_TOOLS_BASE/devops-tools/notary/notary-cli-config
cd $DEVOP_TOOLS_BASE/devops-tools/notary/notary-cli-config
cp ../certs/cacert.crt .
nano config.json
```

Y añadir el siguiente contenido:

```
{
   "remote_server": {
     "url": "https://10.1.0.17:4443",
     "root_ca": "cacert.crt"
   }
}
```


### Crear el directorio de claves de Notary

```
mkdir –p devops-tools/notary/keys
```

### 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:

```
jenkinscicd:
			·····
    volumes:
			·····
      - ${NOTARY_CLI_HOME}/cacert.crt: /etc/ssl/certs/cacert-notary.crt
			····

```

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

```
docker exec –it jenkinscicd /bin/bash
update-ca-certificates
```

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

```
/etc/pki/ca-trust/source/anchors/
update-ca-trust
```


# 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:

```
docker exec –it jenkinscicd /bin/bash
docker trust key generate onesaitkey --dir /root/.docker/trust --> password: onesaitkeypwd
```

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**

```
docker trust signer add --key /root/.docker/trust/ onesaitkey.pub onesaitkey <registry_url>/<path>/<module>
```


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:

```
docker trust key generate <nombre_clave> --dir /root/.docker/trust
```

Admite como parámetros KEY_NAME y KEY_PASSWORD

![image](media://67fcdddf-6d52-4738-a366-f48dad1d6e97)

El código del pipeline es:

```
pipeline {
   // Execute the Pipeline, or stage, on any available agent  
   agent { 
           node { 
              label 'obpsmaster' 
            } 
          }
   parameters { 
       string(name: 'KEY_NAME', 
              defaultValue: '', 
              description: 'Nombre de la clave a generar')
       string(name: 'KEY_PASSWORD', 
              defaultValue: '', 
              description: 'Password de la clave a generar')
   }    
   stages {
	   stage('generate key') {  
		steps { 
		   sh """ docker trust key generate ${params.KEY_NAME} --dir /root/.docker/trust << EOF
${params.KEY_PASSWORD}
${params.KEY_PASSWORD}
EOF """
		}          
	   }
     }
    post {
        always {
          echo "key generated"
        }  
    }
}
```

### Pipeline de asociación de claves a imágenes

Este pipeline ejecuta el comando:

```
docker trust signer add --key /root/.docker/trust/<nombre_clave_firma>.pub <nombre_clave_firma> <nombre_imagen_docker>
```

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

![image](media://a686b232-c921-4ea8-9940-5c60c33d88fd)

El código del pipeline es:

```
pipeline {
   // Execute the Pipeline, or stage, on any available agent  
   agent { 
           node { 
              label 'obpsmaster' 
            } 
          }
   
   
   parameters { 
       string(name: 'SIGN_KEY_NAME', 
              defaultValue: '', 
              description: 'Clave de firma a utilizar')
       string(name: 'IMAGE_TO_SIGN', 
              defaultValue: '', 
              description: 'Lista de imagenes a generar (una imagen por linea)')
       string(name: 'SIGNER_KEY_PASSWORD', 
              defaultValue: '', 
              description: 'Password de firma')
       string(name: 'ROOT_KEY_PASSWORD', 
              defaultValue: '', 
              description: 'Password de root')
   }    
   
   stages {
       stage ('create signing script'){
           steps {
               script {
                   sh """ cat > dialogscript.sh << EOF
#!/usr/bin/expect -f

set timeout -1

set SIGN_KEY_NAME [lindex \\\$argv 0]
set IMAGE_TO_SIGN [lindex \\\$argv 1]
set SIGNER_KEY_PASSWORD [lindex \\\$argv 2]
set ROOT_KEY_PASSWORD [lindex \\\$argv 3]

spawn docker trust signer add --key /root/.docker/trust/\\\$SIGN_KEY_NAME.pub productkey \\\$IMAGE_TO_SIGN

expect "Enter passphrase for root key with ID"

send -- "\\\$ROOT_KEY_PASSWORD\\r"

expect "Enter passphrase for new repository key with ID"

send -- "\\\$SIGNER_KEY_PASSWORD\\r"

expect "Repeat passphrase for new repository key with ID"

send -- "\\\$SIGNER_KEY_PASSWORD\\r"

expect eof              
"""
              sh "chmod 766 dialogscript.sh"
            }
           }
       }
	   stage('add signer') {  
		steps { 
		      sh """export DOCKER_CONTENT_TRUST_SERVER="https://10.1.0.17:4443" && export DOCKER_CONTENT_TRUST=1 && ./dialogscript.sh ${params.SIGN_KEY_NAME} ${params.IMAGE_TO_SIGN} ${params.SIGNER_KEY_PASSWORD} ${params.ROOT_KEY_PASSWORD}"""
		    }
		          
	   }
	   
	   stage('remove script'){
	       steps{
	           sh "rm dialogscript.sh"
	       }
	   }
     }
   
    post {
        always {
          echo "key generated"
        }  
    }
  
}
```


# 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:

```
sh "docker login -u <user> -p <password> <registro>:<puerto>"
```

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

```
export DOCKER_CONTENT_TRUST_SERVER=https://<notary-server>:<notary-server-port>
export DOCKER_CONTENT_TRUST=1
pushImage datagrid-manager $TAG
export DOCKER_CONTENT_TRUST=0

```

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

```
pushImage()
{
               echo "Hace el tag de la imagen"
               docker tag $GIT_GROUP/$1:$2 10.1.0.17:14443/$GIT_GROUP/$1:$2
               echo "Hace el push de la imagen"
               docker push 10.1.0.17:14443/$GIT_GROUP/$1:$2 << EOF
onesaitkeypwd
EOF
}
```


# 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](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](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:

```
helm install connaisseur helm --wait --debug
```

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:

```
- pattern: "10.1.0.17:14443/onesait-banking/*:*"

verify: true
```

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

```
cd $DEVOP_TOOLS_BASE/devops-tools/notary/keys/private
sed '/^role:\sroot$/d' $(grep -iRl "role: root" .) > root-priv.key
openssl ec -in root-priv.key -pubout -out root-pub.pem
```

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:

```
make install
```

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

```
make upgrade
```

**           **