RHEL8 Virtualization Server

Description

Je note ici l'ensemble des actions que je mène pour mettre en place un hyperviseur sous CentOS 8.

En ces temps de confinement, cela permet de continuer à travailler sur certaines applications, garder la main et faire de la veille technologique.

Globalement, beaucoup de chose se font aisément, les divers fournisseurs de distributions font ce qu'il faut pour avoir des solutions utilisables, même avec les versions Open Source.

Aussi, ayant un PC dans un coin avec 4 CPU et 8GB de RAM, c'est suffisant pour manipuler les bases. Cela permet de faire tourner 3 machines en parallèles.

Points d'attention sur la sécurité

Il est facile de monter rapidement ces solutions. De mon côté, je m'attache à étudier la solution technique mais aussi la sécurité de ce qui est mis en place. Aussi, les règles de base que j'applique sont :

  • Réduction de la surface d'attaque:
  • je n'installe que ce qui est nécessaire et suffisant
  • Le pare-feu est activé et seuls les flux nécessaires et suffisants sont ouverts
  • moindre privilèges: les applications installées doivent tourner avec des id différents
  • Je n'installe que
  • les paquets venant de chez CentOS/RHEL
  • des paquets signés
  • des logiciels recompilés depuis les sources si pas dans le référentiel

De manière globale, les recos de l'ANSSI

RedHat est un acteur fort dans ce périmètre et s'attache à fournir des solutions sécurisées de base. C'est notamment le cas avec l'utilisation de selinux. Point que beaucoup ont trop souvent l'habitude de désactiver :(

Installation du serveur

Création d'une clé usb bootable

Ici on réalise une action assez standard récupération de l'iso et copie sur une clé USB

 curl -O http://mirror.in2p3.fr/linux/CentOS/8.1.1911/isos/x86_64/CentOS-8.1.1911-x86_64-dvd1.iso
 sudo dd if=CentOS-8.1.1911-x86_64-dvd1.iso of=/dev/sdXXX  bs=512

/!\ - bien choisir le /dev/sdxxx qui correspond à sa clef USB (dmesg ou journalctl)

Installation du système de base

En appliquant le principe d'installer ce qui est juste nécessaire, la démarche est : * installer le système avec le rôle de serveur de virtualisation * ajouter des service * faire les configurations spécifiques

Installation de base

Là encore pas de chose très spéciales, on boote sur la clé USB et on suis les instructions : * Nom de machine * choix du partitionnement * choix du rôle du serveur * Configuration du réseau * Mot de passe root * Utilisateur supplémentaire administrateur

Service cockpit

Cockpit est une application qui permet de configurer le serveur au travers d'une interface https. J'ai tendance à éviter d'installer des applications qui font tout toutes seules pour favoriser l'apprentissage en profondeur de ce qui est installé. Cela évite aussi d'augmenter la surface d'attaque du serveur. Ici, le risque est réduit parce que je suis sur une infra locale avec des systèmes de tests. Donc je l'installe pour découvrir.

Cockpit est installé par défaut mais le service n'est pas lancé.

systemctl enable cockpit.service
systemctl start cockpit.service

Ensuite, on accède à l'interface via l'url https://:9090/ et on se connecte avec un des comptes du système. Etant en phase de découverte, je me connecte root.

Cockpit est évolutif et peut être étoffé à l'aide de plugins. Dans mon cas, voulant faire un serveur de virtu, j'ai ajouté les paquets suivants: * cockpit-machines: gestion de libvirt * cockpit-storaged: Gestion du stockage * cockpit-podman: gestion docker * cockpit-dashboard: état du serveur.

On retrouve assez naturellement les éléments de configuration d'un système d'hyper convergence. C'est la direction qu'à prise RedHat il y a plusieurs années et c'est la raison pour laquelle IBM l'a acheté !:)

Après un tour rapide de l'interface, on peut faire pas mal de choses simples mais on est vite bloqué dès qu'on sort des sentiers balisés. Or dans mon cas, je sors de ces sentiers.

Configuration du réseau

C'est ici que je sors des sentiers battus pour la raison suivante.

Partie d'une configuration du réseau des VM par défaut, c'est du NAT qui est configuré. Pour pouvoir accéder directement à ces machines, j'ai plusieurs solutions : * passer le réseau en bridge: alors les vm seront vue sur mon réseau local * passer le réseau en mode routé

La solution bridge est séduisante mais je n'ai pas trop envie que des VM, qui servent de tests, aient accès au reste de mon infra. Aussi, il faut pouvoir isoler au maximum le réseau des VM tout en y ayant accès. Les flux peuvent entrer mais pas sortir. Mais, si on bloque tout en sortie, on se heurte à des soucis pour faire des mises à jour ou installer de nouvelles choses.

La solution routée est pas mal mais passerelle par défaut est ma box sur laquelle je ne peux ajouter de route. Il faudrait alors que j'ajoute la route à chacun des postes depuis lesquels je souhaite me connecter aux VM. De plus, je partage cette infra avec des personnes qui sont elles aussi confinées dont l'accès se fait forcément par la box... qui ne sait pas faire le routage.

Doc Libvirt

Pour avancer sur le sujet, j'ai pris la décision d'opter pour une première solution qui est : * garder le NAT et filtrer les flux sortants et entrants * Créer un VM "bastion" qui est le point d'entrée pour les connexions depuis l'extérieur

     [BOX G]--.--[BOX P] 
              |
              |
         (tcp/xxxx)
              |
              |
         -----------
        |   BOX     |
         -----------
              |
              |
         (tcp/yyyy)
              |
              |
 -------------|---------------------------------
| hyper        \                                |
| viser         \ ----------------------        |
|                |            |        |        |
|             [Bastion:22]    |        |        |
|                           [VM2]    [VM3]      |
|                                               |
 -----------------------------------------------

Avec cette solution, il faut jouer avec le pare feu de l'hyper viseur pour ajouter le pNAT pour router le flux entrant vers le bastion.

C'est là que cockpit atteint ses limites car on ne peut faire ce genre de configurations aisément. C'est aussi là que je rouve un nouveau challenge car je découvre nftable et l'instrumentation qui en est faite par RedHat au travers de firewalld

Je suis en plein dans le truc des solutions qui font "papa/maman" mais qui sont restrictives dès qu'on veut faire "plus" ou "différemment". Donc cela demande un effort d'apprentissage des 2 technos ainsi que le modèle mis en place par RedHat.

Parce que ça ne fait que 3j que j'ai commencé à regardé tout ça entre d'autres choses, j'ai réussi à atteindre l'objectif de faire le pNAT pour donner les accès mais ce n'est pas satisfaisant. En effet, je n'ai pas encore réussi à tout configurer avec "firewalld". Je n'arrive pas à positionner, de manière permanente 2 règles accept au bon endroit.

Lorsque que je relance le pare-feu, j'ai une règle qui est mal placée car elle est automatiquement placée APRÈS les règles mises par "libvirtd" quand il lance le réseau virtuel. J'en ai une autre qui ne reste pas. Çà ressemble à ce soucis

Pour que ça fonctionne, il faut : * autoriser le flux tcp/yyyy entrant sur l'hyperviseur * forwarder ce flux vers le bastion tcp/22 * autoriser ce forward à traverser le pare-feu.

Si j'utilise les outils mis à disposition par CentOS, je dois utiliser firewall-cmd (je ne détaille pas ici la gestion des zones public/libvirt... peut-être pus tard ou dans un autre doc car ce n'est pas le sujet du moment)

#  On autorise le flux à entrer sur l'hyper viseur.
firewall-cmd --zone=public --add-port=yyyy/tcp
# Ajout du forward
firewall-cmd --zone=public --add-forward-port=port=yyyy:proto=tcp:toport=22:toaddr=<@ip_bastion>

Je suis ensuite obligé de faire les actions manuelles suivantes

# Lister la chaine qui m'initéresse avec les numéro de handle.
# Util pour le insert d'après
$ nft list table filter -a

# Dans la chain FORWARD, je repère le handle de la première règle
# et j'insère la règle qui me convient pour autoriser le forward des flux
# établis depuis réseau local vers le réseau virtuel.
#                                       \/ ici c'est 12 mais ça peut-être autre chose
$ nft insert rule  filter FORWARD handle 12 iifname enp2s0 oifname virbr0 ct state new,untracked accept


# Enfin j'ajoute la règle qui permet au ssh d'arriver sur le bastion.
$ nft add rule  filter OUTPUT  oifname "virbr0" meta l4proto tcp tcp dport 22 counter accept

En écrivant ces lignes, j'ai chercher des informations et je suis tombé sur virsh nwfilter-list ... Ciel, encore un truc intéressant à creuser !!:P

Cela sera tout pour aujourd'hui, je continue sur le sujet plus tard...

Annexes

Kickstart serveur virtualisation

Ci-dessous le fichier kickstart généré par l'installation du rôle serveur virtualisation.

#version=RHEL8
ignoredisk --only-use=sda
autopart --type=lvm
# Partition clearing information
clearpart --none --initlabel
# Use graphical install
graphical
# Use CDROM installation media
cdrom
# Keyboard layouts
keyboard --vckeymap=fr-oss --xlayouts='fr (oss)'
# System language
lang fr_FR.UTF-8

# Network information
network  --bootproto=dhcp --device=enp2s0 --onboot=off --ipv6=auto --no-activate
network  --hostname=localhost.localdomain
repo --name="AppStream" --baseurl=file:///run/install/repo/AppStream
# Root password
rootpw --iscrypted *
# Run the Setup Agent on first boot
firstboot --enable
# Do not configure the X Window System
skipx
# System services
services --enabled="chronyd"
# System timezone
timezone America/New_York --isUtc

%packages
@^virtualization-host-environment
@remote-system-management
kexec-tools

%end

%addon com_redhat_kdump --enable --reserve-mb='auto'

%end

%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty