Skip to content
Snippets Groups Projects
Verified Commit 375afc73 authored by Quentin Duchemin's avatar Quentin Duchemin
Browse files

Begin to remove CI

parent 42281e20
No related branches found
No related tags found
1 merge request!50Remove CI
Showing
with 39 additions and 389 deletions
image: docker:19.03.0
# Disable TLS just for the docker daemon running locally, TLS is still used to deploy built images!
variables:
DOCKER_TLS_CERTDIR: ""
DOCKER_DRIVER: overlay2
GIT_SUBMODULE_STRATEGY: recursive
services:
- docker:19.03.0-dind
stages:
- ci-base
- build
- security-tests
- push
# Hidden key meant to be included in other jobs, for factorization
# Login to registry and pull built image
.pull-modified-image: &pull-modified-image
image: $REGISTRY_PROD/pica-ci-base
tags: [build]
before_script:
- sh image_modified_last_commit.sh
- source variables
- echo $REGISTRY_PASSWORD | docker login $REGISTRY -u $REGISTRY_USERNAME --password-stdin
- docker pull $MODIFIED_IMAGE_FULL_TEST
# Build the base image used for all further steps : this is done only when pica-ci's Dockerfile is modified
pica-ci-base:
stage: ci-base
tags: [build]
before_script:
- echo $REGISTRY_PROD_PASSWORD | docker login $REGISTRY_PROD -u $REGISTRY_PROD_USERNAME --password-stdin
script:
- docker build -f pica-ci-base/Dockerfile . -t $REGISTRY_PROD/pica-ci-base:latest
- docker push $REGISTRY_PROD/pica-ci-base:latest
after_script:
- docker logout $REGISTRY_PROD
rules:
- if: '$CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "dev-ci"'
changes:
- "pica-ci-base/Dockerfile"
when: manual
allow_failure: true
- when: never
# Build the image that was modified
build:
stage: build
tags: [build]
image: $REGISTRY_PROD/pica-ci-base
before_script:
- sh image_modified_last_commit.sh
- source variables
# First login on the production registry, in case the image is based on another registry image
- echo $REGISTRY_PROD_PASSWORD | docker login $REGISTRY_PROD -u $REGISTRY_PROD_USERNAME --password-stdin
script:
# Build the image
- docker build -f $MODIFIED_IMAGE/Dockerfile $MODIFIED_IMAGE -t $MODIFIED_IMAGE_FULL_TEST
- docker logout $REGISTRY_PROD
# Then login on the test registry and push the image
- echo $REGISTRY_PASSWORD | docker login $REGISTRY -u $REGISTRY_USERNAME --password-stdin
- docker push $MODIFIED_IMAGE_FULL_TEST
after_script:
- docker logout $REGISTRY
# Build if Dockerfile changed and previous stage completed successfully
rules:
- changes:
- "pica-*/Dockerfile"
- "meta-*/Dockerfile"
when: on_success
- changes:
- "pica-*/**"
- "meta-*/**"
when: manual
allow_failure: true
- when: never
# Run CoreOS' Clair and make the CI failed if a critical vulnerability isn't in the whitelist
clair:
stage: security-tests
<<: *pull-modified-image
script:
- docker run -d --name db arminc/clair-db:latest
- docker run -p 6060:6060 -d --link db:postgres --name clair --restart on-failure arminc/clair-local-scan:latest
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
- ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r clair-report.json -l clair.log -w $MODIFIED_IMAGE/clair-whitelist.yml --threshold="High" $MODIFIED_IMAGE_FULL_TEST
artifacts:
paths:
- clair-report.json
- clair.log
rules:
- changes:
- "pica-*/Dockerfile"
- "pica-*/clair-whitelist.yml"
- "meta-*/Dockerfile"
- "meta-*/clair-whitelist.yml"
when: on_success
- changes:
- "pica-*/**"
- "meta-*/**"
when: manual
allow_failure: true
- when: never
# Run docker-bench-security and upload the results
docker-bench-security:
stage: security-tests
<<: *pull-modified-image
script:
# Change the Docker Compose to use the "testing" image, not yet pushed on production registry
- sed -i -e "s|$MODIFIED_IMAGE_FULL|$MODIFIED_IMAGE_FULL_TEST|g" $MODIFIED_IMAGE/docker-compose.yml
# If *.example secrets files exist, remove the .example extension to be able to start the container
# Indeed these file are used in Docker Compose with env_file directive
- if [[ -d $MODIFIED_IMAGE/secrets ]]; then for i in $MODIFIED_IMAGE/secrets/*.example ; do cp $i $(echo $i| cut -d '.' -f1,2); done; fi;
# Let docker-compose create the required volumes and networks
- "sed -i -e 's/external: true/external: false/g' $MODIFIED_IMAGE/docker-compose.yml"
- cat $MODIFIED_IMAGE/docker-compose.yml
- cd $MODIFIED_IMAGE
# Login on the production registry, in case there is another image in Docker Compose stored on the production registry
- docker logout $REGISTRY
- echo $REGISTRY_PROD_PASSWORD | docker login $REGISTRY_PROD -u $REGISTRY_PROD_USERNAME --password-stdin
- docker-compose up -d
- git clone https://github.com/docker/docker-bench-security.git
- cd docker-bench-security
- sh docker-bench-security.sh -c container_images,container_runtime,docker_security_operations,community_checks -l ../../report.txt
after_script:
- docker logout $REGISTRY_PROD
artifacts:
paths:
- report.txt
rules:
- changes:
- "pica-*/**"
- "meta-*/**"
when: manual
allow_failure: true
- when: never
# Push the generated image on the production registry,
# once it passed all security tests and has been successfully built
# and run on the test virtual machine
push-prod:
stage: push
<<: *pull-modified-image
script:
- docker tag $MODIFIED_IMAGE_FULL_TEST $MODIFIED_IMAGE_FULL
- echo $REGISTRY_PROD_PASSWORD | docker login $REGISTRY_PROD -u $REGISTRY_PROD_USERNAME --password-stdin
# MODIFIED_IMAGE_FULL already should include the registry URL
- docker push $MODIFIED_IMAGE_FULL
after_script:
- docker logout $REGISTRY_PROD
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
changes:
- "pica-*/**"
when: manual
- when: never
......@@ -2,10 +2,12 @@
Ce dépôt centralise les Dockerfiles et autre ressources utilisées pour construire **et** déployer les images Docker tournant en production sur l'infrastructure de Picasoft.
Certains services ne respectent pas encore la structure de ce dépôt (exemple : `nextcloud-docker`, ou Traefik qui reste géré uniquement sur les VM) : ils seront migrés prochainement.
## Philosophie
Historiquement, Picasoft procède de la manière suivante :
* Construire ses Dockerfile à la main et les pousser sur un registre privé.
* Construire les images Docker à partir de Dockerfiles sur une machine quelconque, puis les pousser manuellement sur un registre privé.
* Gérer un Docker Compose global par VM, qui contient la configuration et les secrets.
Cette approche pose plusieurs problèmes. Comment savoir ce qu'il y a dans une image, si on perd le Dockerfile ? Quel Dockerfile correspond à quelle version de l'image ? Si on perd l'accès à la machine, comment récupérer la configuration, remonter rapidement le service ? Comment versionner les changements de configuration ? Revenir à la version d'il y a deux semaines ?
......@@ -13,54 +15,44 @@ Cette approche pose plusieurs problèmes. Comment savoir ce qu'il y a dans une i
L'objectif de ce dépôt est de rendre possible le déploiement de n'importe quel service Picasoft avec la procédure suivante :
* Cloner le dépôt
* Se rendre dans le répertoire du service
* Lancer un `docker-compose up -d`
* Lancer un `docker-compose up -d` pour démarrer le service
Son objectif secondaire est de pouvoir revenir à l'état antérieur d'un service. En versionnant toute la configuration nécessaire, revenir à une ancienne version du service revient à :
* Cloner le dépôt
* Checkout un commit quelconque
* Lancer le service normalement
Pour y arriver, il construit automatiquement les images de l'ensemble des services et les met à disposition sur un registre, avec une version. Mise à part les données à proprement parler du service, il n'y a donc aucune différence entre un service lancé sur une de nos machines et un service lancé à partir de ce dépôt sur une machine virtuelle quelconque, ce qui s'inscrit dans la philosophie Docker.
Mises à part les données à proprement parler du service, il n'y a donc aucune différence entre un service lancé sur une de nos machines et un service lancé à partir de ce dépôt sur une machine virtuelle quelconque, ce qui s'inscrit dans la philosophie Docker.
## Contenu du dépôt
Ce dépôt contient toutes les ressources permettant de déployer les services que nous maintenons sur n'importe quelle machine virtuelle de l'infrastructure de Picasoft, sans prérequis.
Cela signifie que le bon fonctionnement d'un service n'est pas dépendant de la machine virtuelle sur laquelle on essaye de le lancer.
Le dépôt est organisé en dossiers. Chaque dossier correspond à un *service* Picasoft au sens large, c'est-à-dire à un ensemble de conteneurs liés entre eux *techniquement* (*e.g.* Mattermost et sa base de données) et/ou *sémantiquement* (*e.g.* sites web statiques, relai et boîte à mail).
Ainsi, chaque service versionné sur ce dépôt contiendra :
Ainsi, chaque dossier de ce dépôt contiendra, selon les situations :
* Un `Dockerfile` permettant de construire l'image,
* Zéro, un ou plusieurs `Dockerfile` permettant de construire les images personnalisées pour Picasoft,
* Un fichier `docker-compose.yml` permettant de lancer le service,
* Si possible, un ou des fichiers de configuration permettant de personnaliser le service selon nos besoins,
* Un fichier d'exemple de secrets (mot de passe...) nécessaire au lancement du service,
* Un `README.md` résumant les paramètres modifiables sur le dépôt, les mécanismes pour en rajouter, etc,
* Pour les images maison, un `CHANGELOG.md` résumant les modifications faites au fil des version.
Il n'y a pas de Docker Compose global : chaque service a son propre Docker Compose.
Il n'y a pas de Docker Compose global : chaque service a son propre Docker Compose, lui permettant d'être déployé indépendamment des autres.
Un exemple concret peut être trouvé au niveau de [pica-mattermost](./pica-mattermost) ou [pica-etherpad](./pica-etherpad).
## Mais je commence par où, bordel ?
Ce dépôt peut faire peur, mais pour la plupart des usages il est très simple à exploiter.
## Guides d'utilisation
### Je veux lancer un service existant sur une machine virtuelle
### Lancer ou redémarrer un service sur une machine de Picasoft
Si le service que vous souhaitez lancer est référencé sur ce dépôt, lisez [la documentation de déploiement](./doc/guide_deploiement.md#déploiement-en-production).
Lire [la documentation de déploiement et de maintenance]().
### Je veux mettre à jour un service
### Mettre à jour un service ou sa configuration
Il faut pour cela utiliser la chaîne d'intégration de ce dépôt, qui construit automatiquement les images Docker. [Lisez la documentation utilisateur de la chaîne d'intégration.](./doc/guide_utilisateur_ci.md).
Lire [la documentation de mise à jour des services]().
### Je veux mettre en place un nouveau service
### Mettre en place un nouveau service
Il faudra d'une part [respecter les conventions utilisées par la chaîne d'intégration](./doc/guide_developpeur_ci.md#formalisme-du-dpt).
Ensuite, il faudra jeter un oeil [aux bonnes pratiques](./doc/guide_bonnes_pratiques.md) à l'oeuvre sur l'infrastructure de Picasoft (partie **Formalisme du dépôt**).
Lire [la documentation de versionnage d'un nouveau service]() et [les bonnes pratiques pour Docker]().
Un dossier [template](./template) prêt à copier est aussi disponible.
### Je veux améliorer la chaîne d'intégration
Lisez la [documentation "développeur" pour la chaîne d'intégration](./doc/guide_developpeur_ci.md), qui rentre plus dans le détail.
# Mini guide pour mitiger une CVE (WIP)
Une CVE est mise en liste blanche en l'ajoutant au fichier `clair-whitelist.yml`, dont on trouvera un exemple [ici](../pica-mattermost/clair-whitelist.yml). Il faut spécifier le nom de la CVE, le paquet affecté et la raison de la mise en liste blanche.
Une mise en liste blanche est **acceptable** si :
* Clair détecte des vulnérabilités sur le paquet `linux`. En effet, le noyau utilisé par le conteneur est celui de l'hôte. On utilisera le motif `Vulnérabilité Linux`,
* Le paquet est à jour et ne peut pas être installé dans une version différente à cause de dépendances d'autres paquets,
* Le paquet ne peut pas être supprimé,
* Il n'existe pas de contre-mesure à la vulnérabilité,
* La vulnérabilité, même si elle est classée en criticité `High`, a peu de conséquences pour Picasoft. Cela peut être le cas pour une attaque par déni de service causant un usage de 100% du processeur, qui n'aura d'impact que sur Etherpad en raison des limitations de ressources.
Si jamais il reste un paquet contenant une vulnérabilité `High`, et que les conditions ci-dessus ne sont pas remplies, voici deux choses à tenter avant de le whitelister ou de décider de ne pas déployer la mise à jour.
## Suppression d'un paquet inutile
On prend l'exemple de la `CVE-2020-8492 (High)` détectée par Clair lors d'un build de `pica-db-backup-rotation` ([commit](https://gitlab.utc.fr/picasoft/projets/dockerfiles/-/commit/078d448a53da4be9330fd0ac9304a7eb3a26e969), [log de Clair](https://gitlab.utc.fr/picasoft/projets/dockerfiles/-/jobs/883005))
![](./images/clair_log_1.png)
Comme on peut le voir sur la capture d'écran ci-dessus, Clair a détecté une nouvelle vulnérabilité avec le label `High`. Cette vulnérabilité concerne plusieurs versions de Python, donc un premier réflexe est de vérifier quelle est la version de Python utilisée dans l'image construite.
Pour cela, récupère la version qui a été poussée sur le registry de test et on lance un conteneur temporaire:
```
$ docker pull docker pull registry.picasoft.net/pica-db-backup-rotation:1.0
$ docker run --rm -it registry.picasoft.net/pica-db-backup-rotation:1.0 bash
```
À l'intérieur du conteneur, on vérifie les versions de Python
```
root@91ed958145c1:/# python --version
Python 3.8.2
root@91ed958145c1:/# python2 --version
bash: python2: command not found
```
Ceci semble surprenant, on n'a pas de `python2` et notre `python3` n'est pas vulnérable. Pas de `python2`... vraiment ?
```
root@95889d5957b6:/# apt list --installed | grep -i python
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
libpython2.7-minimal/now 2.7.16-2+deb10u1 amd64 [installed,local]
libpython2.7-stdlib/now 2.7.16-2+deb10u1 amd64 [installed,local]
libpython2.7/now 2.7.16-2+deb10u1 amd64 [installed,local]
```
On découvre ainsi qu'il y a bien une partie de Python2 dans notre image. Mais d'où vient-elle ?
```
root@95889d5957b6:/# apt-cache rdepends libpython2.7
libpython2.7
Reverse Depends:
libmailutils5
mailutils
```
Et `mailutils` ?
```
root@95889d5957b6:/# apt-cache rdepends mailutils
mailutils
Reverse Depends:
```
Il semblerait qu'on arrive à une impasse, mais non ! On peut toujours exécuter une à une les commandes du [Dockerfile](https://gitlab.utc.fr/picasoft/projets/dockerfiles/-/blob/078d448a53da4be9330fd0ac9304a7eb3a26e969/pica-db-backup-rotation/Dockerfile) pour retrouver qu'est-ce qui a installé `mailutils`.
On finit par trouver que c'est cette ligne:
```
&& apt-get -y install cron \
```
En effet, lorsqu'on installe cron avec la commande précédente, `apt` installe énormément de dépendances qui ne sont pas nécessaires pour notre cas. On peut donc utiliser l'option `--no-install-recommends` pour installer seulement le nécessaire et ainsi [corriger la CVE](https://gitlab.utc.fr/picasoft/projets/dockerfiles/-/commit/5b6bd31225581c40738e9a131a59dec4c8417848).
![](./images/clair_log_2.png)
On remarque que maintenant le nombre de vulnérabilités est passé de 73 à 60, et il n'y a plus aucune vulnérabilité en `High`. On remarque aussi que les CVE-2019-17455 et CVE-2019-18224 qui étaient dans la `clair-whitelist.yml` ne sont plus présentes.
**Fusionner `pica-mail-mta` et `pica-mail-mda` et ajouter le fichier Compose `mail.yml`**.
......@@ -9,7 +9,7 @@ usage() {
echo "to be sure that it works independently of the former configuration, and then launch 'docker-compose up -d'."
echo "This way, you can test your Dockerfile | docker-compose on the testing VM as if it was a brand new VM."
echo -e "\nAlso, it will temporarily replace all occurences of 'picasoft.net' by 'test.picasoft.net' for convenience."
echo -e "\nThis script will also use the image uploaded on the testing registry, not the production registry."
echo -e "\nThis script will also build and pull all the required images based on the docker-compose.yml file."
echo -e "\nUSE THIS SCRIPT ONLY ON THE TESTING VM."
exit 1
}
......@@ -75,10 +75,12 @@ cd "$1"
create_dumb_secrets
# Except for registry URL, useful to push to production registry after build
echo -e "\n==== Replace production URL with testing URL in all files ===="
for f in $(grep -l -r ".picasoft.net" .); do
echo -e "\tFound in" ${f}
sed -i "s/.picasoft.net/.test.picasoft.net/g" ${f}
sed -i "s/registry.test.picasoft.net/registry.picasoft.net/g" ${f}
done
echo -e "\n==== Remove and re-create named external volumes ===="
......@@ -95,7 +97,10 @@ echo -e "\n==== Remove old images ===="
# For some reasons, sometime docker-compose does not pull the newer image. Force this!
docker-compose config | grep "image:" | cut -d ':' -f 2- | xargs docker image rm || true
echo -e "\n==== Pull new versions of images ===="
echo -e "\n==== Build custom images ===="
docker-compose build
echo -e "\n==== Pull new versions of external images ===="
docker-compose pull
echo -e "\n==== Lauch $1 ===="
......
#!/bin/sh
# Retrieve the name and the version of the image that was modified in the latest commit
# This script should become obsolete as soon as a proper way of getting the modified files is added to Gitlab CI
# Image name, without registry nor tag
RES=""
for i in $(git log -m -1 --name-only --pretty="format:" --first-parent)
do
case "$i" in
*pica*|*meta*)
RES=$(echo $i | cut -d '/' -f1)
break
;;
esac
done
if [ "$RES" = "" ]; then
echo "Error : no folder starting by pica or meta modified in last commit."
echo "Please check README.md for further understanding."
exit 1;
fi
echo "export MODIFIED_IMAGE=${RES}" > variables
echo "Using modified folder : $RES"
# Image name with wanted registry and tag, fetched from Docker Compose
RES=$(cat $RES/docker-compose.yml | grep $RES: | cut -d ':' -f2- | cut -d '/' -f2- | tr -d ' ' | head -n 1)
if [ "$RES" = "" ]; then
echo "Error : no reference to $RES found in docker-compose.yml."
echo "Please check the repository README : the name of the folder must be the same as name of the image."
exit 1
fi
echo "export MODIFIED_IMAGE_FULL_TEST=registry.test.picasoft.net/${RES}" >> variables
echo "export MODIFIED_IMAGE_FULL=registry.picasoft.net/${RES}" >> variables
FROM registry:2.7
(WIP)
[Docker Registry](https://docs.docker.com/registry/) used by the CI/CD in this repo for image testing.
## Usage
### First time
_This guide assumes that a (manually deployed) registry is already running in `pica01-test`_
1. Generate a password in [picapass](https://gitlab.utc.fr/picasoft/interne/pass/)
2. Generate a new `htpasswd` file with `htpasswd -Bn <username>`. This will prompt you for the password and will display results on stdout
3. In `pica01-test`, create `/DATA/docker/dockerfiles/meta-registry-test/secrets/htpasswd` and paste there the output of the previous command. Make sure permissions are correct (`660`).
### Updating this image
Update: Same as in general [README.md](/README.md) (don't forget to increase the version number in `docker-compose.yml`). The CI will build an image and deploy it to the old test registry.
Deploy: not the same !!
### Deploy updates
**Do not use `docker_test.sh` or `docker_prod.sh` to deploy this`.** Deploy manually:
1. `cd /DATA/docker/dockerfiles/ && git pull`
2. Pull the new image from the old registry (E.g. `docker pull registry.test.picasoft.net/meta-registry-test:0.1`)
3. `cd /DATA/docker/dockerfiles/meta-registry-test/ && docker-compose up -d`
### Notes
`./secrets/htpasswd.example` contains an example file generated by `htpasswd` for user `picauser` and password `picapassword`.
generalwhitelist:
version: "3.7"
volumes:
registry-data:
name: "registry-data"
networks:
docker_default:
external: true
services:
registry:
container_name: registry
restart: always
image: registry.test.picasoft.net/meta-registry-test:0.1
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
REGISTRY_STORAGE_DELETE_ENABLED: "true"
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
volumes:
- registry-data:/var/lib/registry
- ./secrets/htpasswd:/auth/htpasswd
labels:
- "traefik.frontend.rule=Host:registry.test.picasoft.net"
- "traefik.port=5000"
- "traefik.enable=true"
networks:
- docker_default
picauser:$2y$05$axvpLiYg.ADRa2I8xAU/weLb7urnUIgoehsQuKut1/dlIhDOQ.iPy
**Ajouter le docker-compose et la documentation de mise à jour**
FROM docker:19.03.0
RUN apk update && \
apk add build-base \
git \
iproute2 \
libffi-dev \
openssl-dev \
py-pip \
python2-dev \
sed \
wget \
&& pip install docker-compose
......@@ -4,7 +4,11 @@ Picasoft réalise régulièrement des backups de ses bases de données. Le servi
Il est flexible et se configure via des fichiers JSON.
La rotation est effectuée une fois par jour.
## Mise à jour
Effectuer les changements désirés dans le Dockerfile, effectuer les changements désirés dans la configuration, mettre à jour le fichier `CHANGELOG.md` et changer la version du service dans le `docker-compose.yml`.
Il n'est pas nécessaire de modifier le numéro de version pour une simple mise à jour de configuration.
## Configuration
......
generalwhitelist:
......@@ -6,6 +6,12 @@ Il est capable de gérer les bases de données `postgres`, `mysql` ou `mongo` po
Il est flexible et se configure via un fichier JSON.
## Mise à jour
Effectuer les changements désirés dans le Dockerfile, effectuer les changements désirés dans la configuration, mettre à jour le fichier `CHANGELOG.md` et changer la version du service dans le `docker-compose.yml`.
Il n'est pas nécessaire de modifier le numéro de version pour une simple mise à jour de configuration.
## Configuration
Le fichier dans [`config/backup_config.json`](./config/backup_config.json) recensent les informations sur les différentes bases de données qui doivent être backupées.
......
generalwhitelist:
À compléter (mise à jour...)
generalwhitelist:
CVE-2019-9169: glibc -> Pas de contre-mesure
CVE-2017-12424: shadow -> Pas de contre-mesure
CVE-2019-18224: libidn2 -> Pas de contre-mesure dans Debian Buster, attendre une mise à jour
CVE-2019-11068: libxslt -> dépendance de PHP, pas de contre-mesure
**Doit être fusionné avec le dossier `pica-etherpad` (deux Dockerfiles, un Docker Compose)**.
This image limits the [mysql binary log](https://dev.mysql.com/doc/refman/8.0/en/binary-log.html) to 30 hours (=108000 seconds) via the [binlog_expire_logs_seconds](https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#sysvar_binlog_expire_logs_seconds) variable.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment