Sécurité

Prérequis docker : 17.12.0-ce

Docker Bench Security

Docker Bench for Security est un script qui vérifie des dizaines de pratiques recommandées concernant le déploiement de conteneurs Docker en production.

Téléchargez git !

git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sh docker-bench-security.sh

Daemon Docker

Pour les améliorer son score pour la sécurité de Docker, la configuration suivante est souhaité pour le daemon docker : vim /etc/docker/daemon.json

{
		//Active le mode debug
         "debug": true,
        //Socket de connexion
         "hosts": ["unix:///var/run/docker.sock","tcp://0.0.0.0:2375"],
        //Niveau de log
         "log-level": "Warn",
        //Paramètre Utilisateur / Groupe pour les espaces de noms d'utilisateurs
         "userns-remap": "default",
         //Désactive la communication entre conteneurs
         "icc": false,
        //Possibilité de restauration en direct du daemon lorsque les conteneurs sont toujours en cours d'exécution
         "live-restore": true,
        //Pilote par défaut pour les journaux de conteneur
         "log-driver": "syslog",
        //Utiliser un proxy userland pour le trafic de loopback
         "userland-proxy": false,
        //Définir no-new-privileges par défaut pour les nouveaux conteneurs
         "no-new-privileges": true
}

User namespace

sources https://docs.docker.com/engine/security/userns-remap/ https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_atomic_host/7/html-single/getting_started_with_containers/index#user_namespaces_options

Introduction

Les espaces de noms d’utilisateurs sont une fonctionnalité permettant à la fois l’isolation des privilèges et la séparation des identifications d’utilisateurs dans plusieurs ensembles de processus. Avec l’assistance administrative, il est possible de créer un conteneur avec des droits d’administration apparents sans octroyer de privilèges élevés aux processus utilisateur. À l’instar de l’espace de noms PID, les espaces de nom d’utilisateur sont imbriqués et chaque nouvel espace de nom d’utilisateur est considéré comme un enfant de l’espace de nom d’utilisateur qui l’a créé.

Un espace de nom d’utilisateur contient une table de mappage convertissant les ID utilisateur du point de vue du conteneur en point de vue du système. Cela permet, par exemple, à l’utilisateur root d’avoir l’ID utilisateur 0 dans le conteneur, mais est en réalité traité comme un ID utilisateur 1 400 000 par le système pour les contrôles de propriété. Une table similaire est utilisée pour les mappages d’identifiants de groupe et les contrôles de propriété.

Pour faciliter l’isolement des privilèges des actions administratives, chaque type d’espace de nom est considéré comme appartenant à un espace de nom d’utilisateur basé sur l’espace de nom d’utilisateur actif au moment de la création. Un utilisateur disposant de privilèges d’administrateur dans l’espace de nom d’utilisateur approprié sera autorisé à effectuer des actions d’administration dans cet autre type d’espace de nom. Par exemple, si un processus dispose d’une autorisation administrative pour modifier l’adresse IP d’une interface réseau, il peut le faire tant que son propre espace de noms d’utilisateurs est identique à (ou ancêtre de) l’espace de noms d’utilisateurs propriétaire de l’espace de noms réseau. Par conséquent, l’espace de noms d’utilisateur initial dispose d’un contrôle administratif sur tous les types d’espaces de noms du système.

Mise en oeuvre

Il existe deux options pour réaliser un user namespace, le premier est d’utiliser un compte système déjà existant et l’autre est d’utiliser le compte créer par défaut via Docker. Dans cette mise en oeuvre nous allons utiliser la deuxième option.

Pour l’OS Centos 7.5 il y a des particularités à réaliser. Premièrement il faut crée ou modifier le fichier daemon.json. Ajoutez la partie userns-remap et la valeur default pour utiliser l’utilisteur dockremap créer par défaut.

vim /etc/docker/daemon

{
         "debug": true,
         "hosts": ["unix:///var/run/docker.sock","tcp://0.0.0.0:2375"],
         "log-level": "Warn",
         "userns-remap": "default"
}

Ajoutez l’option namespace.unpriv_enable = 1 à la ligne de commande du noyau (vmlinuz *). Pour ce faire, utilisez la commande grubby comme suit (en remplaçant la version exacte de vmlinuz par celle de votre système): grubby --args="namespace.unpriv_enable=1" --update-kernel=/boot/vmlinuz-3.10.0-693.21.1.el7.x86_64

Ajoutez une valeur au noyau user.max_user_namespaces ajustable afin qu’elle soit définie de manière permanente comme suit: echo "user.max_user_namespaces = 15076" >> /etc/sysctl.conf

L’affectation par défaut de l’option –userns-remap crée un utilisateur et un groupe nommé dockremap. Les numéros UID et GID associés sont mappés sur ce compte dans les fichiers / etc / subuid et / etc / subgid , respectivement. Actuellement, un seul UID et un seul GID peuvent être mappés par démon.

Créer les fichiers subuid et subgid pour renseigner le compte dockremap.

echo dockremap:231072:65536 >> /etc/subuid
echo dockremap:231072:65536 >> /etc/subgid

Réalisez un rédemarrage de la machine afin de prendre en compte les modifications.

Ensuite exéutez les commandes suivante pour tester les modifications

docker run --rm -it -v /etc:/etc debian bash
cd /etc/
ls -la
drwxr-xr-x.  2 65534 65534   4096 Aug 29 14:46 wpa_supplicant
drwxr-xr-x.  4 65534 65534   4096 Apr 11  2018 xdg
drwxr-xr-x.  2 65534 65534   4096 Apr 11  2018 xinetd.d
drwxr-xr-x.  6 65534 65534   4096 Aug 29 14:46 yum
-rw-r--r--   1 65534 65534   1000 Feb 16  2018 yum.conf
drwxr-xr-x.  2 65534 65534   4096 Apr 13  2018 yum.repos.d
drwxr-xr-x   3 65534 65534   4096 Aug 29 14:47 zabbix
...

Si vous essayez de créer un fichier ou modifier dans ce filsystem ce n’est pas autorisé car l’appel système réaliser est faite par un utilisateur non root.

Confiance de contenue image

Il est possible avec docker de récupérer des images sur un dépôt publique. Ce dépôt se compose d’image offciel et d’image tierce. La différence entre ce type d’image est la réputation de l’entreprise qui propose leurs image officiel et leurs niveau de sécurité qui est réaliser. Cela peut être la vérification des binaires télécharger, l’utilisation d’un utilisateur sans droits, etc…

Mise en oeuvre

Pour mettre en oeuvre la restriction dans le téléchargement d’image docker uniquement officiel de dépôt publique de docker, il faut ajouter une variable d’environnement docker nommée “DOCKER_CONTENT_TRUST” echo "DOCKER_CONTENT_TRUST=1" | tee -a /etc/environment

Conteneur

En utilisant l’indicateur –restart sur l’exécution de Docker, vous pouvez spécifier une stratégie de rédemarrage indiquant comment un conteneur doit ou non être redémarré à la sortie.

docker run -itd --restart=on-failure:5 debian

Capabilités

Par défaut, Docker démarre les conteneurs avec un ensemble restreint de fonctionnalités. Qu’est-ce que ça veut dire?

Les capacités transforment la dichotomie binaire «racine / non racine» en un système de contrôle d’accès à grain fin. Les processus (comme les serveurs Web) qui ont juste besoin de se lier sur un port inférieur à 1024 n’ont pas besoin de s’exécuter en tant que root: ils peuvent simplement se voir accorder la capacité net_bind_service à la place. Et il existe de nombreuses autres capacités, pour presque tous les domaines spécifiques où les privilèges root sont généralement nécessaires.

Cela signifie beaucoup pour la sécurité des conteneurs; voyons pourquoi!

Les serveurs typiques exécutent plusieurs processus en tant que root , notamment le démon SSH, le démon cron , les démons de journalisation, les modules du noyau, les outils de configuration réseau, etc. Un conteneur est différent, car presque toutes ces tâches sont gérées par l’infrastructure autour du conteneur:

Les accès SSH sont généralement gérés par un seul serveur exécuté sur l’hôte Docker; cron , si nécessaire, doit s’exécuter en tant que processus utilisateur, dédié et adapté à l’application qui a besoin de son service de planification, plutôt qu’en tant qu’installation à l’échelle de la plateforme; la gestion des journaux est également généralement confiée à Docker ou à des services tiers tels que Loggly ou Splunk; la gestion du matériel n’est pas pertinente, ce qui signifie que vous n’avez jamais besoin d’exécuter udevd ou des démons équivalents dans des conteneurs; la gestion du réseau se fait en dehors des conteneurs, ce qui impose autant que possible la séparation des problèmes, ce qui signifie qu’un conteneur ne devrait jamais avoir besoin d’exécuter des commandes ifconfig , route ou ip (sauf lorsqu’un conteneur est spécifiquement conçu pour se comporter comme un routeur ou un pare-feu, de cours). Cela signifie que dans la plupart des cas, les conteneurs n’ont absolument pas besoin de privilèges root «réels». Et par conséquent, les conteneurs peuvent fonctionner avec un ensemble de capacités réduites; ce qui signifie que la «racine» dans un conteneur a beaucoup moins de privilèges que la véritable «racine». Par exemple, il est possible de:

refuser toutes les opérations de «montage»; refuser l’accès aux sockets bruts (pour éviter l’usurpation de paquets); refuser l’accès à certaines opérations du système de fichiers, comme la création de nouveaux nœuds de périphérique, le changement du propriétaire des fichiers ou la modification des attributs (y compris l’indicateur immuable); refuser le chargement du module; et plein d’autres. Cela signifie que même si un intrus parvient à remonter à la racine dans un conteneur, il est beaucoup plus difficile de causer de graves dommages ou de remonter à l’hôte.

Cela n’affecte pas les applications Web classiques, mais réduit considérablement les vecteurs d’attaque par des utilisateurs malveillants.`

docker run --rm -it alpine sh -c 'apk add -U libcap; capsh --print'
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Ambient set =
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
 secure-no-ambient-raise: no (unlocked)
uid=0(root)
gid=0(root)
groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

exemple sur un docker-compose de la configuration de capabilités du service :

nginx:
    image: nginx
    cap_drop: 
      - ALL
    cap_add:
    - AUDIT_WRITE
    - CHOWN
    - DAC_OVERRIDE
    - FOWNER
    - FSETID
    - KILL
    - MKNOD
    - NET_BIND_SERVICE
    - NET_RAW
    - SETFCAP
    - SETGID
    - SETPCAP
    - SETUID
    - SYS_CHROOT

Limiter les ressources exploiter par un conteneur

Dans un environnement de production, afin d’éviter une surcharge de consommation des ressources matériels par un conteneur impactant l’ensemble des conteneurs fonctionnant sur la machine ont définit des limites de ressources. Ainsi le conteneur ne peut dépasser un ensemble de ressources et garantie une haute disponbilité des autres services en cas de défaillance du conteneur. Cette configuration est renseigner dans le manifeste docker-compose.yml Docker-compose Docs

version: '2.2'
services:
  elastic:
    image: elastic-debian:7.3.2
    container_name: elastic
    mem_limit: 4000m
    mem_reservation: 4000m
    cpus: '2'

Cette fonctionnalité est uniquement disponible sur la version 2 de docker-compose.

Scan de vulnérabilités dans les layers des images docker

docker run --net=host -d --name db arminc/clair-db:latest
docker run --net=host --add-host postgres:127.0.0.1 -d --name clair --net=host arminc/clair-local-scan:$CLAIR_LOCAL_SCAN_VERSION
apk add -U wget ca-certificates
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
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
touch clair-whitelist.yml
while( ! wget -q -O /dev/null http://$(hostname -i):6060/v1/namespaces ) ; do sleep 1 ; done
retries=0
echo "Waiting for clair daemon to start"
while( ! wget -T 10 -q -O /dev/null http://$(hostname -i):6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
./clair-scanner -c http://$(hostname -i):6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true