Uubu.org

Systèmes d'information: une approche OpenSource

Virtualisation - concepts et optimisation

La virtualisation en entreprise: optimisation des ressources

Le 17 mars 2015 , Aucun commentaire pour cet article


La virtualisation nécessite une optimisation à de nombreux niveaux pour obtenir des hautes performances. Que ce soit les paramètres de l'hôte, de l'invité, l'accélérations hardware, les optimisations de cache, les entrées/sorties, ou la présentation des périphériques, chaque paramètre a son importance. Bien évidemment, l'accélération hardware présents dans les micro-processeurs moderne supporte une grande partie du travail d'optimisation, mais nous allons voir comment optimiser Linux et qemu/kvm. Ce duo est en effet la solution la plus performante du marché, encore faut-il s'avoir exploiter tout son potentiel.

 

Technologie VMX

Le VMM gère les VMCS comme de simples tâches, et lance les vm avec les transitions vm-entries. Nous verrons plus loin comment les cgroups peuvent influencer l'application des VMCS. L'enjeu de la virtualisation est donc de réduire au maximum les vm-exits, et pour cela, nous avons plusieurs fonctionnalités à disposition. HAP et les APIC modernes (x2APIC) qui incluent EOI pour réduire les vm-exists. Ces 2 technologies devraient être systématiquement utilisées, il n'y a pas de raison de s'en priver. On peut vérifier les performances avec une commande du type: perf stat --event "kvm:*" --all-cpus sleep 10.

 

Cache de pages

Les pages de caches sont toutes gérées par le VMM par défaut. Les processeurs moderne incluent HAP qui, d'une part ne vide plus le TLB, mais gère les pages de cache au niveau des invités, évitant au vmm de gérer les shadow copy coûteuses.

Au niveau de l'OS, les hugepages de 2M, voir 1G sont utiles pour réduire le nombre de pages. Avec des tailles de page de 2Mo, le TLB peut ainsi maintenir l'adressage de 16Go de RAM sur les cpu modernes, ce qui réduit grandement les vm-exist. Couplé avec HAP vu précédemment, les vm-exits sont considérablement réduits.

Sous Linux, il existe 2 gestionnaires de grandes pages, hugetlbpage et transparent_hugepage. Ce dernier est le plus perfomant pour la virtualisation mais n'est pas recommandé pour certaines applications comme les bases de données, on préfèrera dans ce cas utiliser hugetlbpages.

 

CFS

Sous Linux, le scheduler prend en compte 5 modes de gestion des processus:

normal (ou other):  applique les tâches normalement
batch: Ne permet pas à ces tâches d'être préemptés, ce qui résulte d'une meilleur utilisation du cache CPU, mais rend ces tâches moins interactives.
idle: Ces tâches sont lancées quand il n'y à plus de tâche prêtes dans le système. Dans ce système, c'est à l'utilisateur de classer ses tâches en utilisant chrt, parce que le scheduler n'est pas capable de classifier une tâche.
fifo: Temps-réel, les tâches n'ont pas de quanta finis, mais s'exécutent jusqu'à la fin ou jusqu'à ce que le cpu soit relâché.
rr: Temps-réel, le scheduler définis un quanta et planifie les tâches basés sur l'algorithme round-robin.

Il prend également en compte la priorité des tâches. Il utilise des priorités de 0 à 99 pour les tâches temps-réels, et de 100 à 139 pour les autres tâches. Par défaut, une tâche a une priorité de 120, et des outils tels que nice/snice permettent d'ajuter la priorité dans une plage relative de -20 à +19, ce qui permet de gérer la priorité dans la plage 100 à 139.

Sur les serveurs, les paramètres de temps d'exécution des tâche doit être adapté: sched_min_granularity_ns à 10000000 et sched_wakeup_granularity_ns à 15000000 sont un bon point de départ.

 

Entrées/Sorties

La gestion des opérations d'écriture disque est une des données les plus importantes puisque la puissance cpu n'est rien si le système passe son temps à attendre les opérations de lecture/écritures. Je donne ici quelques conseils:

- Toujours utiliser les pilotes virtio, il n'y a aucune raison de s'en passer.
- Assigner un disque physique ou même une partition est toujours plus performant, mais complique la migration.
- Présenter des périphériques block réseaux est toujours préférable à un fichier dans un système de fichier monté localement.
- Un système de fichier journalisé dans un disque virtuel fichier, lui-même sur un système journalisé peut sembler un peu inutile.
- mtime et atime rendent les caches de lecture moins efficaces.
- Le format de disque raw est le plus performant (mais le moins fiable).
- Le cache d'écriture doit être géré par l'os qui dialogue directement avec les disques sachant que writethrough est très bon pour les lectures intensives, et none est meilleur pour les opérations d'écritures.
- Linux AIO et x2APIC améliorent sensiblement les performances
- Le scheduler d'E/S Deadline est le plus performant pour la virtualisation (hôte et invité)
- Désactiver le delay accounting et le random entropy contribution
- Éviter l'overcommit mémoire et cpu.
- Les invités avec 2 vcpu offrent de meilleurs performances d'écriture.
- Comme pour tout serveur, les dirty page sont à adapter pour un usage serveur: ratio à 40% et background_ratio à 15%.
- Supprimer ulatencyd, conçu pour un usage desktop.

 

Présentation des périphériques

La documentation Intel décrit les 5 modes de présentation des périphériques:

- Complète: consiste à émuler de manière entièrement logiciel le matériel.
- Émulation: le VMM présente à la vm un périphérique générique.
- Synthétique: Le VMM expose un périphérique spécifique, et l'invité doit posséder également le pilote dédié.
- Assignement. Le VMM expose un périphérique matériel.
- E/S partagé. Le VMM expose un périphérique matériel partagé.

Le modèle synthétique reste le meilleur choix dans la majorité des cas, mais cela dépend des périphériques et besoins. Par exemple, un lecteur cdrom est souvent de l'émulation complète, de même pour les clavier/souris/carte graphique. La présentation des périphériques physique offre les meilleurs performances, mais complique sérieusement les migrations.

D'une manière générale on évitera au maximum la présentation de périphérique générique, puisque l'on se retrouve avec 2 pilotes l'un sur l'autre. Le mode synthétique utilise par contre un pilote guest dédié, qui n'est finalement qu'une passerelle vers le pilote de l'hôte.

 

Les pilotes virtio

Linux est fournis avec plusieurs pilotes virtio. Ces pilotes sont des périphériques synthétiques haute performance. il existe des pilote virtio PCI, SCSI, balloon, rng, serie, console, réseaux, etc. Inutile de rappeler de les utiliser systèmatiquement.

 

Périphérique balloon

Balloon est un périphérique qui permet à l'hôte de réclamer de la mémoire vive à chaud auprès de l'invité, en clair, de gérer l'allocation dynamique à chaud de la mémoire vive des invités. Il est capable de réclamer de la mémoire inutilisée par un invité pour l'allouer à un autre invité qui en a besoin. Sous Linux, ce périphérique est fournis via un pilote virtio.

 

QXL/Spice

Conçus pour le VDI, QXL est une carte graphique paravirtualisée, et Spice un protocole de connexion à distance qui sépare le trafic en canaux pour un contrôle optimal, et permet de chiffrer le trafic et fournis un mécanisme d'authentification forte. Ce protocole donc bien plus interressant que vnc.

 

Horloges

Il existe plusieurs types d'horloges, et il est possible d'en fournir plusieurs à une machine virtuelle, ce qui est une bonne chose puisque l'invité choisira, en fonction de ce qu'il supporte, le timer qui lui convient le mieux.

RTC ( Real Time Clock) permet de créer des décomptes du temps, avec une précision le l'ordre du milliseconde, ce qui n'est pas suffisamment précis de nos jours.

PIT ( Programmable Interval Timer) est plus précis, mais il est peu utilisé à la faveur des timers présents dans les APIC, plus précis.

HPET ( High Precision Event Timer) est une horloge haute précision présente dans les architectures Intel récentes et tend à remplacer les 2 précédent, mais n'est supporté que les systèmes récents.

TSC ( Time Stamp Counter) est un registre CPU qui compte le nombre de cycle depuis le dernier reset. Il est très simple à utiliser mais ne peut pas fournir un résultat très précis vu que la fréquence d'horloge des CPU moderne est ajustée en permanence.

 

Les threads

Qemu offre la possibilité de créer des iothreads et de les assigner à des opérations d'E/S ou réseaux. Avec les cgroups, on peut aller plus loin, en dédiant des cpu physiques aux opérations d'E/S pour optimiser d'avantage les opérations disques.

Sous QEMU, on les crée un par un avec la commande object:
-object iothread,id=MyIoThread

Sous libvirt c'est plus simple
<iothreads>2</iothreads>

Une fois les threads crées, on peut assigner des opérations d'écritures avec:
iothread=MyIoThread

Sous libvirt, il ne reste plus qu'à mapper le thread à un cpuset et éventuellement de sélectionner le mode de CFS:
<iothreadpin iothread='1' cpuset='7'/>
<iothreadssched iothreads='1' scheduler='rr' priority='1'/>

On peut également faire de même pour le mappage des vcpus:
<vcpupin vcpu="0" cpuset="5"/>
<vcpusched vcpus='5,6' scheduler='batch'/>

 

swap

Quand un OS comme à swapper, les performances s'écroulent. C'est encore plus vrai pour un invité, qui peut monopoliser les accès disques au détriment des autres. Il est donc important de valider que le swap ne soit jamais utilisé. Cependant, il est déconseillé de ne pas avoir de swap, cela évite bien des problèmes. Un bonne pratique consiste à utiliser des disques ou des volumes réseaux dédiés au swap, afin de ne pas interférer avec les disques utilisés par le système et garantir un fonctionnement sans trop de dégradation.

 

Notes

De nombreuses options sont possibles mais non prises en charge actuellement. Par exemple il est techniquement possible d'assigner des threads pour les périphériques réseaux, mais cela n'est pas pris en compte. QEMU et Libvirt étant en constante évolution, les fonctionnalités évoluent rapidement et il est difficile de se tenir à jours.

Introduction à la virtualisation sous Linux

Petit tour rapide de qemu/KVM/libvirt

Le 03 mars 2015 , Aucun commentaire pour cet article


L'excellent émulateur QEMU offre la possibilité d'exploiter toutes les fonctionnalités des CPU modernes pour la virtualisation d'entreprise, grâce à KVM, fonctionnalité directement intégré dans le noyau. Il peut émuler tellement de périphériques qu'il est difficile d'en faire le tour. Sa syntaxe est assez intuitive, mais peu pratique au quotidien, nous allons également utiliser libvirt, plus souple, et qui amène une couche supplémentaire - les cgroups - afin de contrôler finement notre invité.

Avant de se lancer dans la virtualisation, voici un petit récapitulatif des paramètres de l'hôte:

Au démarrage de l'hôte, nous allons activer les transparent_hugepages pour des pages de 2Mo, désactiver le delay accounting et activer les cgroups pour la mémoire. Attention cependant, les bases de données déconseillent généralement transparent_hugepages, à n'utiliser que sur des hôte qui n'hébergerons pas ce type de service:

cgroup_enable=memory transparent_hugepage=madvice hugepagesz=2M hugepages=1 default_hugepagesz=2M nodelayacct

Pour optimiser les accès disques, on ajoute une règle udev pour désactiver le random entropy contribution sur nos disques et choisir le scheduler deadline:
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="deadline", ATTR{queue/add_random}="0"

deadline reste le plus performant dans la majorité des cas, mais pas toujours pour les disques ssd.

Dans sysctl.conf, on ajuste les paramètres de gestion de mémoire, comme pour tout serveur: CFS devrait avoir un min_granularity à 10000000ns et wakeup_granularity à 15000000ns, et les dirty_page à 40% et 15% pour le background. Ce sont des valeurs de base conseillé par RH qui conviennent parfaitement pour de la virtualisation. Ensuite, on désactive ulatencyd et ksm si vous n'en avez pas besoin.

Ajout d'un délai suffisant avant la réduction de fréquence du CPU:
SUBSYSTEM=="cpu", ATTR{cpufreq/scaling_governor}="ondemand"

et spécifier une valeure de 10 ou 100, via systemd-tmpfiles:
w /sys/devices/system/cpu/cpufreq/ondemand/sampling_down_factor - - - - 100

QEMU

La machine virtuelle utilisée ici a les caractéristiques suivantes: 2 cpus, 1 disque dur, 1Go de Ram, 2 interfaces réseaux, carte graphique et carte son, ainsi que quelques périphériques comme watchdog, panic, rng ou encore qemu-ga. Une première définition serait:

qemu-system-x86_64 -name Debian -no-user-config -nodefaults -k fr -msg timestamp=on -machine pc-i440fx-2.2,accel=kvm,usb=off,mem-merge=off -bios /usr/lib/roms/bios-256k.bin -L /usr/lib/roms/ -boot menu=off,strict=on -cpu kvm64,+kvm_pv_eoi -smp 2,maxcpus=8,sockets=1,cores=4,threads=2 -object iothread,id=iothread1 -object iothread,id=iothread2 -global PIIX4_PM.disable_s3=1 -global PIIX4_PM.disable_s4=1 -m 1024 -drive file=/srv/MyDisk,if=none,id=drive0,format=qcow2,cache=none,aio=threads -device virtio-blk-pci,iothread=iothread1,ioeventfd=on,event_idx=on,scsi=off,drive=drive0,bootindex=1 -drive if=none,id=drive1,readonly=on,format=raw -device ide-cd,bus=ide.0,unit=0,drive=drive1,bootindex=2 -netdev bridge,br=MyBridge0,helper=/sbin/qemu-bridge-helper,id=bridge0 -device virtio-net-pci,id=bridge0 -netdev bridge,br=MyBridge1,helper=/sbin/qemu-bridge-helper,id=bridge1 -device virtio-net-pci,id=bridge1 -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x6.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x6 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x6.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x6.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x7 -chardev spicevmc,id=charchannel0,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=com.redhat.spice.0 -chardev spicevmc,id=charredir0,name=usbredir -device usb-redir,chardev=charredir0,id=redir0 -chardev spicevmc,id=charredir1,name=usbredir -device usb-redir,chardev=charredir1,id=redir1 -chardev spicevmc,id=charredir2,name=usbredir -device usb-redir,chardev=charredir2,id=redir2 -chardev spicevmc,id=charredir3,name=usbredir -device usb-redir,chardev=charredir3,id=redir3 -spice port=19000,tls-port=20000,addr=192.168.2.1,agent-mouse=on,disable-ticketing,x509-dir=/etc/pki/spice,plaintext-channel=default,plaintext-channel=main,plaintext-channel=display,plaintext-channel=inputs,plaintext-channel=cursor,plaintext-channel=playback,plaintext-channel=record,plaintext-channel=usbredir,image-compression=lz,jpeg-wan-compression=always,zlib-glz-wan-compression=always,playback-compression=on,streaming-video=all,seamless-migration=on -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,bus=pci.0,addr=0x2 -chardev socket,id=charchannel1,path=/var/lib/libvirt/qemu/f16x86_64.agent,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=2,chardev=charchannel1,id=channel1,name=org.qemu.guest_agent.0 -device i6300esb,id=watchdog0 -watchdog-action reset -device virtio-balloon-pci,id=balloon0 -object rng-random,id=rng0,filename=/dev/random -device virtio-rng-pci,rng=rng0,max-bytes=1234,period=2000 -device pvpanic,ioport=1285 -realtime mlock=off -rtc base=utc,clock=vm,driftfix=slew -global kvm-pit.lost_tick_policy=discard -monitor stdio

 

Explications:

cpu: on active kvm avec accel=kvm, un -cpu kvm64 avec PV_EOI pour réduire les VM_EXISTS. Ici je spécifie 2 cpus, avec un max à 8 (j'utilise en fait un i7, et je laisse la possibilité d'ajouter autant de cpu à chaud que nécessaire). désactivation de S3 et S4.

disque: j'utilise des optimisation d'écriture: aio, pas de cache côté hôte/invité, un threads séparé et pilote virtio, qui m'assurent de bonnes performances au détriment de la fiabilité, et un lecteur cdrom ide pour l'installation de l'os.

réseau: 2 interfaces réseaux bridge (penser au setuid bit sur qemu-bridge-helper), pilote virtio.

vidéo: carte qxl et serveur spice offrent d'excellentes performances, et toutes les fonctionnalités nécessaire au vdi, pour un invité graphique, il est fortement conseillé. Un prochain article sera consacré à ce sujet.

le reste est globalement compréhensible, à noter l'utilisation des pilotes virtio partout où cela est possible, il n'y a aucune raison de s'en priver.

Pour lancer un invité Windows, il faut utiliser des disques raw, une horloge localtime au lieu de utc, et on a pas besoin du périphérique panic. Le reste des options sont les même. Penser à préparer l'iso des pilotes virtio et spice.

 

Libvirt

Libvirt est un toolbox pour la virtualisation. Sa configuration est simple, je donne les paramètres les plus utiles:

listen_tls = 1
tcp_port = "1601"
tls_port = "1602"
listen_addr = "192.168.123.2"
key_file = "/etc/pki/private/host/hypervisor08-key.pem"
cert_file = "/etc/pki/host/hypervisor08.pem"
ca_file = "/etc/pki/CA/uubu.fr.rca.pem"
crl_file = "/etc/pki/CA/uubu.fr.crl.pem"
tls_no_sanity_certificate = 0
tls_no_verify_certificate = 0
tls_allowed_dn_list = ["C=FR,ST=Rhone-Alpes,L=Grenoble,O=uubu.fr,CN=ws-admin02.uubu.fr"]

 

Sous libvirt, les définitions se font en XML:

Pool

On va créer un pool pour nos disques virtuels. Pour simplifier, je créé des pools de type répertoire:

<pool type='dir'>
 <name>MyPool</name>
 <target>
  <path>/srv/MyPool</path>
 </target>
</pool>

Volume

Dans le pool, on créer nos disques virtuels. Pour un disque qcow2 de 8Go:

<volume type='file'>
 <name>MyDisk</name>
 <capacity unit='GiB'>8</capacity>
 <target>
  <path>/srv/MyPool/MyDisk</path>
  <format type='qcow2'/>
  <permissions>
    <owner>1000</owner>
    <group>1000</group>
    <mode>0744</mode>
  </permissions>
 </target>
</volume>

Network

Les bridges doivent être créés avant la définition, nous utiliserons ces bridges pour les interfaces réseaux:

<network ipv6='yes' trustGuestRxFilters='no'>
 <name>MyBridge0</name>
 <forward mode='bridge'/>
 <bridge name='Bridge0' macTableManager="libvirt"/>
</network>

Domain

Pour définir nos invités, nous allons reprendre les options qemu, et nous allons créer une première définition migrable, c'est à dire en évitant les paramètres qui seraient trop spécifique à notre hôte:

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
 <name>MyVM</name>
 <os>
  <type arch='x86_64'>hvm</type>
  <loader readonly='yes' type='rom'>/usr/lib/roms/bios-256k.bin</loader>
  <bootmenu enable='no' timeout='0'/>
 </os>
 <vcpu current="2">8</vcpu>
 <cpu match='exact'>
  <model>kvm64</model>
  <vendor>Intel</vendor>
  <feature policy='optional' name='x2apic'/>
 </cpu>
 <cputune>
  <shares>1024</shares>
 </cputune>
 <memory unit="GiB">2</memory>
 <currentMemory unit="MiB">1024</currentMemory>
 <memoryBacking>
  <nosharepages/>
 </memoryBacking>
 <on_poweroff>destroy</on_poweroff>
 <on_reboot>restart</on_reboot>
 <on_crash>restart</on_crash>
 <on_lockfailure>poweroff</on_lockfailure>
 <pm>
  <suspend-to-disk enabled='no'/>
  <suspend-to-mem enabled='no'/>
 </pm>
 <features>
  <acpi/>
  <apic eoi='on'/>
  <hap/>
 </features>
 <clock offset='utc'>
  <timer name='kvmclock'/>
 </clock>
 <devices>
  <emulator>/local/qemu/qemu-system-x86_64</emulator>
  <disk type='volume' device='disk'>
   <driver name='qemu' type='raw' cache='none'/>
   <source pool='Disks' volume='MyDisk'/>
   <target dev='vda' bus='virtio'/>
   <boot order='1'/>
  </disk>
  <disk type='file' device='cdrom'>
   <source file=''/>
   <target dev='hda' bus='ide' try='closed'/>
   <boot order='2'/>
   <readonly/>
  </disk>
  <interface type='network'>
   <source network='MyBridge'/>
   <target dev='net-MyVM'/>
   <model type='virtio'/>
  </interface>
  <controller type='usb' index='0' model='ich9-ehci1'/>
  <controller type='usb' index='0' model='ich9-uhci1'>
   <master startport='0'/>
  </controller>
  <controller type='usb' index='0' model='ich9-uhci2'>
   <master startport='2'/>
  </controller>
  <controller type='usb' index='0' model='ich9-uhci3'>
   <master startport='4'/>
  </controller>
  <redirdev bus='usb' type='spicevmc'/>
  <redirdev bus='usb' type='spicevmc'/>
  <redirdev bus='usb' type='spicevmc'/>
  <redirdev bus='usb' type='spicevmc'/>
  <graphics type='spice' port='19007' keymap='bepo' defaultMode='insecure' passwd='MyPasswd'>
   <listen type='network' network='spice'/>
   <channel name='main' mode='insecure'/>
   <channel name='display' mode='insecure'/>
   <channel name='inputs' mode='insecure'/>
   <channel name='cursor' mode='insecure'/>
   <channel name='playback' mode='insecure'/>
   <channel name='record' mode='insecure'/>
   <channel name='usbredir' mode='insecure'/>
   <image compression='lz'/>
   <zlib compression='always'/>
   <jpeg compression='always'/>
   <playback compression='on'/>
   <streaming mode='all'/>
   <clipboard copypaste='yes'/>
   <mouse mode='client'/>
   <filetransfer enable='yes'/>
   <migration seamless='on'/>
  </graphics>
  <video>
   <model type='qxl' ram='65536' vram='65536' heads='1'>
    <acceleration accel3d='yes' accel2d='yes'/>
   </model>
  </video>
  <controller type='virtio-serial' index='0'/>
  <channel type='spicevmc'>
    <target type='virtio' name='com.redhat.spice.0'/>
  </channel>
  <watchdog model='i6300esb' action='reset'/>
  <memballoon model='virtio'/>
  <input type='mouse' bus='ps2'/>
  <input type='keyboard' bus='ps2'/>
  <channel type='unix'>
   <source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
   <target type='virtio' name='org.qemu.guest_agent.0'/>
  </channel>
  <rng model='virtio'>
   <rate period="2000" bytes="1234"/>
   <backend model='random'>/dev/random</backend>
  </rng>
  <panic>
   <address type='isa' iobase='0x505'/>
  </panic>
 </devices>
 <qemu:commandline>
  <qemu:arg value='-L'/>
  <qemu:arg value='/usr/lib/roms/'/>
 </qemu:commandline>
</domain>

 

et une définition plus difficile à migrer, mais qui nous assure un contrôle totale des threads, cpus, etc. grâce aux cgroups:

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
 <name>MyVM</name>
 <os>
  <type arch='x86_64'>hvm</type>
  <loader readonly='yes' type='rom'>/usr/lib/roms/bios-256k.bin</loader>
  <bootmenu enable='no' timeout='0'/>
 </os>
 <iothreads>2</iothreads>
 <vcpu current="2">8</vcpu>
 <cpu match='exact'>
  <model>kvm64</model>
  <vendor>Intel</vendor>
  <feature policy='optional' name='x2apic'/>
 </cpu>
 <cputune>
  <vcpupin vcpu="0" cpuset="5"/>
  <vcpupin vcpu="1" cpuset="4"/>
  <vcpupin vcpu="2" cpuset="3"/>
  <vcpupin vcpu="3" cpuset="2"/>
  <vcpupin vcpu="4" cpuset="1"/>
  <vcpupin vcpu="5" cpuset="7"/>
  <vcpupin vcpu="6" cpuset="6"/>
  <vcpupin vcpu="7" cpuset="0"/>
  <shares>1024</shares>
  <iothreadpin iothread="1" cpuset="7"/>
  <iothreadsched iothreads='1' scheduler='rr'/>
  <iothreadpin iothread="2" cpuset="6"/>
  <iothreadsched iothreads='2' scheduler='rr'/>
  <period>1000000</period>
  <quota>-1</quota>
  <emulator_period>1000000</emulator_period>
  <emulator_quota>-1</emulator_quota>
  <vcpusched vcpus='0-4' scheduler='rr' priority='1'/>
  <vcpusched vcpus='5,6' scheduler='batch'/>
  <vcpusched vcpus='7' scheduler='idle'/>
 </cputune>
 <memory unit="GiB">2</memory>
 <currentMemory unit="MiB">1024</currentMemory>
 <memoryBacking>
  <nosharepages/>
 </memoryBacking>
 <on_poweroff>destroy</on_poweroff>
 <on_reboot>restart</on_reboot>
 <on_crash>restart</on_crash>
 <on_lockfailure>poweroff</on_lockfailure>
 <pm>
  <suspend-to-disk enabled='no'/>
  <suspend-to-mem enabled='no'/>
 </pm>
 <features>
  <acpi/>
  <apic eoi='on'/>
  <hap/>
 </features>
 <clock offset='utc'>
  <timer name='kvmclock'/>
 </clock>
 <devices>
  <emulator>/local/qemu/qemu-system-x86_64</emulator>
  <disk type='volume' device='disk'>
   <driver name='qemu' type='raw' cache='none' aio='native' ioeventfd='on' event_idx='on' iothread='1'/>
   <source pool='Disks' volume='MyDisk'/>
   <target dev='vda' bus='virtio'/>
   <boot order='1'/>
  </disk>
  <disk type='file' device='cdrom'>
   <source file=''/>
   <target dev='hda' bus='ide' try='closed'/>
   <boot order='2'/>
   <readonly/>
  </disk>
  <interface type='network'>
   <source network='MyBridge'/>
   <target dev='net-MyVM'/>
   <model type='virtio'/>
   <driver name='vhost' txmode='iothread' ioeventfd='on' event_idx='on' queues='2' iothread='2'>
    <host csum='on' gso='on' tso4='on' tso6='on' ecn='on' ufo='on' mrg_rxbuf='on'/>
    <guest csum='on' tso4='on' tso6='on' ecn='on' ufo='on'/>
   </driver>
  </interface>
  <controller type='usb' index='0' model='ich9-ehci1'/>
  <controller type='usb' index='0' model='ich9-uhci1'>
   <master startport='0'/>
  </controller>
  <controller type='usb' index='0' model='ich9-uhci2'>
   <master startport='2'/>
  </controller>
  <controller type='usb' index='0' model='ich9-uhci3'>
   <master startport='4'/>
  </controller>
  <redirdev bus='usb' type='spicevmc'/>
  <redirdev bus='usb' type='spicevmc'/>
  <redirdev bus='usb' type='spicevmc'/>
  <redirdev bus='usb' type='spicevmc'/>
  <graphics type='spice' port='19007' keymap='bepo' defaultMode='insecure' passwd='MyPasswd'>
   <listen type='network' network='spice'/>
   <channel name='main' mode='insecure'/>
   <channel name='display' mode='insecure'/>
   <channel name='inputs' mode='insecure'/>
   <channel name='cursor' mode='insecure'/>
   <channel name='playback' mode='insecure'/>
   <channel name='record' mode='insecure'/>
   <channel name='usbredir' mode='insecure'/>
   <image compression='lz'/>
   <zlib compression='always'/>
   <jpeg compression='always'/>
   <playback compression='on'/>
   <streaming mode='all'/>
   <clipboard copypaste='yes'/>
   <mouse mode='client'/>
   <filetransfer enable='yes'/>
   <migration seamless='on'/>
  </graphics>
  <video>
   <model type='qxl' ram='65536' vram='65536' heads='1'>
    <acceleration accel3d='yes' accel2d='yes'/>
   </model>
  </video>
  <controller type='virtio-serial' index='0'/>
  <channel type='spicevmc'>
    <target type='virtio' name='com.redhat.spice.0'/>
  </channel>
  <watchdog model='i6300esb' action='reset'/>
  <memballoon model='virtio'/>
  <input type='mouse' bus='ps2'/>
  <input type='keyboard' bus='ps2'/>
  <channel type='unix'>
   <source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
   <target type='virtio' name='org.qemu.guest_agent.0'/>
  </channel>
  <rng model='virtio'>
   <rate period="2000" bytes="1234"/>
   <backend model='random'>/dev/random</backend>
  </rng>
  <panic>
   <address type='isa' iobase='0x505'/>
  </panic>
 </devices>
 <qemu:commandline>
  <qemu:arg value='-L'/>
  <qemu:arg value='/usr/lib/roms/'/>
 </qemu:commandline>
</domain>

Opérations

Je donne ici quelques opérations courantes:

Déclarer la vm: define --pool MyPool /xml/domains/MyVM

Attacher un iso dans le lecteur cdrom: change-media MyVM hda /iso/debian.iso --insert

Démarrer l'invité: start MyVM

Effectuer des requêtes dans le monitor: qemu-monitor-command --domain MyVM --hmp --cmd info spice

Connaître l'url de connection pour spice: domdisplay MyVM

Dialoguer avec qemu-ga: qemu-agent-command --domain srvpl-WS01 --cmd '{"execute": "guest-info"}'

ajouter de la mémoire/cpu: setmem MyVM 2G / setvcpus MyVM --count 3

Conclusion

L'avantage de libvirt, c'est que toutes les définitions et opérations courantes peuvent être scriptées pour se simplifier la vie.

Une fois la machine virtuelle installée, ne pas oublier d'optimiser l'invité. On peut reprendre les paramètres de l'hôte: scheduler deadline, transparent_hugepages, random entropy contribution, et le paramètres du CFS. Ne pas oublier également le driver qxl et qemu-ga si vous souhaiter les utiliser.

OpenLDAP - Contrôle d'accès

Implémentation du contrôle d'accès dans OpenLDAP

Le 25 septembre 2014 , Aucun commentaire pour cet article


Le contrôle d'accès d'OpenLDAP est très riche et très complexe. Je vais donner ici quelques conseils afin de réussir au mieux le design.


1. Documenter: Il est absolument impératif de documenter le contrôle d'accès mis en place, et doit être fait en 2 parties, une fonctionnelle, qui est un résumé de ce que l'on a définis, et une partie plus technique.
2. Validation: Une simple modification peut influencer tout le contrôle d'accès. En conséquence, une série de tests doivent être mis en place et jouées de manière régulière afin de s'assurer que les règles définies jouent bien leur rôle.
3. ACL vs ACI: Les ACI sont à première vu plus intéressantes à utiliser. Il faut cependant garder en tête que les ACI sont encore considérés comme expérimentales et sont moins évoluées que les acl, qu'on ne peut pas les utiliser pour le contrôle d'accès à la configuration du serveur, et que peu de backends les supportent. Une autre différence notable: les aci sont lues et traitées ensemble, les acl s'arrêtent au premier match.
4. Gestion de la structure: N'oublions pas que les règles de structures ne sont pas prises en charge par OpenLDAP, et par conséquent doivent être définies par acl.

aci et acl partagent les même syntaxes de permissions, donc attention, la permission "d" ne signifie pas delete! L'approche entre aci et acl est différente. Là ou une acl équivaux à une liste par cible, l'aci traite une liste pas source. On notera qu'OpenLDAP ne gére pas les droits tout à fait conformément au draft acl-model, mais ils sont suffisants pour définir des contrôles d'accès efficaces, et cela les simplifie!

Délégations

La balance entre simplicité et souplesse est difficile à trouver, la catégorisation des éléments est donc importante. Je vais décrire un modèle RBAC de base, et nous allons nous intéresser à cn=config.

Cet objet contient de nombreux attributs liés au serveur. Nous pouvons catégoriser la configuration comme ceci:
- Gestion du cache
- Gestion des connexions concurrentes
- Les requis/restrictions
- Gestion des threads
- Gestion des limites
- Comportement de l'indexation
- Gestion des logs
- L'id de réplication
- Gestion des certificats
- Gestion SASL
- Traitements authz

Plutôt que de créer autant de rôles, nous allons nous limiter à 3:
  - La Sécurité gérera les certificats et tout ce qui est en lien avec l'authentification
  - L'indexation gérera les paramètres d'indexation, et plus généralement l'optimisation des performances.
  - Les administrateurs géreront tous les autres attributs en lien avec le service.

On rajoutera un rôle d'audit, qui autorisera à lire tous les attributs.

Nous pouvons commencer par créer nos objets:

dc=uubu,dc=fr
\_ cn=Delegations
  \_Administration
    \_Admin-Groups
      \_ADM-ldap
  \_Applications
    \_ldap
      \_ldap-cfg-Auditors
      \_ldap-cfg-Security-Mgr
      \_ldap-cfg-Index-Mgr
      \_ldap-cfg-Service-Mgr

Maintenant que nos rôles sont définis, ajoutons nos acl. Notre groupe Admin-Managers ne gère pas directement les rôles, mais délègue la gestion aux membres de ADM-ldap, dont ils peuvent gérer les membres:
olcAccess: {1000}to dn.baseobject="cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" attrs="children" by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by * none
olcAccess: {1050}to dn.onelevel="cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="entry,objectClass,@groupOfMembers,cn" by set="[cn=
Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by * none

On en profite pour inclure notre règle de structure. Ici j'utilise les groupOfMembers, c'est un choix personnel et il est plutôt conseillé d'utiliser groupOfNames pour rester en phase avec les rfc. Ensuite, je gère les droits sur l'application "ldap":
olcAccess: {2000}to dn.baseobject="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="children" by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write stop by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" read by * none
olcAccess: {2050}to dn.onelevel="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="entry,objectClass,cn" by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" read by * none

Ici ce sont les Delegations-Managers qui sont habilités à modifier les rôles, nous bloquons donc la possibilité à un ADM-ldap de modifier la structure des rôles, même s'il peuvent gérer les membres:
olcAccess: {2100}to dn.onelevel="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="member" value.regex="^cn=ldap-[^,]+,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr$" by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" read by * none
olcAccess: {2150}to dn.onelevel="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="member" value.regex="^cn=[^,]+,ou=Peoples,(.+),dc=uubu,dc=fr$" by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" write by * none

cn=config

Les accès sur cet objets sont à gérer dans olcDatabase={0}config,cn=config. Un petit aperçus (je ne met pas tous les attributs):

olcAccess: {1000}to dn.base="cn=config" attrs="entry,cn,objectClass" by users =rscd by * none
olcAccess: {1050}to dn.base="cn=config" attrs="olcRootDSE,olcReverseLookup,olcReferral,olcRestrict,olcRequires,olcReadOnly,olcDisallows,olcAttributeOptions,olcAllows" by set="[cn=ldap-cfg-Service-Mgr,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscdw by set="[cn=ldap-cfg-Auditors,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd
olcAccess: {1200}to dn.base="cn=config" attrs="olcTLSCACertificateFile,olcTLSCACertificatePath,olcTLSCertificateFile,olcTLSCertificateKeyFile,olcTLSCipherSuite,olcTLSCRLCheck,olcTLSRandFile,olcTLSVerifyClient,olcTLSDHParamFile,olcTLSCRLFile,olcTLSProtocolMin,olcLocalSSF" by set="[cn=ldap-cfg-Security-Mgr,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscdw by set="[cn=ldap-cfg-Auditors,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd
olcAccess: {1250}to dn.base="cn=config" attrs="olcIndexIntLen,olcIndexSubstrAnyStep,olcIndexSubstrAnyLen,olcIndexSubstrIfMinLen,olcIndexSubstrIfMaxLen" by set="[cn=ldap-cfg-Index-Mgr,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscdw by set="[cn=TR-DSA-Auditors,cn=DSA,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd
olcAccess: {1300}to dn.base="cn=config" attrs="entry,@olcGlobal,@olcConfig" by set="[cn=ldap-cfg-Auditors,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd by * none

Voilà, il ne nous reste plus qu'à répéter l'opération sur chaque élément qui constitue la configuration du serveur, frontend, olcDatabases, Monitor et AccessLog si vous utilisez ces bases, Modules, Backends, et Overlays. Ensuite, il ne reste plus qu'à définir des rôles fonctionnels pour une gestion plus souple.

o=Enterprises

À partir de ce nœud, j'utilise les aci. Ceci est à titre d'exemple puisque les aci sont expérimentales, et un certain nombre de choses ne fonctionnent pas ou ne sont pas possibles. Pour déléguer la gestion du contrôle d'accès, il faut le déclarer:

olcAccess: to dn.subtree="o=Corporates,dc=uubu,dc=fr" by dynacl/aci write

Commençons par donner l'accès read aux utilisateurs sur la branche:
1#subtree#grant;r,s;entry,objectClass,description#users#

Note: Il est prévu qu'il soit X-ORDERED à l'avenir.

Ensuite, un rôle Group-Mgr pourra gérer cette branche: il peut lire l'objet et en modifier la description. Il aura également le droit de gérer la structure des entreprises. Un rôle Group-Acl-Mgr sera lui, en charge de la gestion des aci au niveau de l'objet et des entreprises:
2#entry#grant;r,s;[all];w;description#set#[cn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
3#entry#grant;w,r,s;OpenLDAPaci#set#[cn=Group-Acl-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
4#entry#grant;w,r,s;children#set#[cn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
5#entry#grant;w,r,s;children#set#[cn=Group-Acl-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
6#subtree#grant;w,r,s;entry,children,o,ou,description,objectClass#set#[cn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
7#subtree#grant;w,r,s;entry,OpenLDAPaci#set#[cn=Group-Acl-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

On remarque tout de suite les faiblesses des contraintes possibles des aci. En revanche, le design est quand même plus naturel.

Au niveau des aci des entreprises, on définis également des rôles pour chacune, et on peut, dans un souci d'autonomie, leur donner les même droits dans leur périmètre:

1#entry#grant;r,s;[all];w;description#set#[cn=jouets-fr-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
2#entry#grant;w,r,s;OpenLDAPaci#set#[cn=jouets-fr-Acl-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
3#entry#grant;w,r,s;children#set#[cn=jouets-fr-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
4#entry#grant;w,r,s;children#set#[cn=jouets-fr-Acl-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
5#subtree#grant;w,r,s;entry,ou,description,objectClass#set#[cn=jouets-fr-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
6#subtree#grant;w,r,s;entry,OpenLDAPaci#set#[cn=jouets-fr-Acl-Mgr,cn=Jouets-
fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

ou=Peoples

Sous ce nœud, nous avons des besoins plus spécifiques:

Les utilisateurs doivent pouvoir s'authentifier:
1#entry#grant;r,s,c,d;children#users#
2#subtree#grant;x;userPassword#public#

Ils peuvent également lire certains attributs de tous les utilisateurs:
3#subtree#grant;r,s;entry,cn,uid,sn#users#

Ils peuvent changer leur propre mot de passe:
4#subtree#grant;w;userPassword#self#

Le rôle PwdReset contient les personnes habilitées à réinitialiser les mots de passe des utilisateurs
5#subtree#grant;w;userPassword#set#[cn=Pwd-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

Le rôle Users-Adm-Mgr contient les personnes habilitées à gérer les comptes utilisateurs, y compris les créer/supprimer
6#entry#grant;w;children#set#[cn=Users-Adm-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
7#subtree#grant;w,r,s;[all]#set#[cn=Users-Adm-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

Le rôle User-Mgr contient les personnes habilitées à gérer des attributs des comptes utilisateurs (mais pas de créer/supprimer de compte):
8#subtree#grant;w,r,s;sn,uid,description#set#[cn=Users-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

Tester les acl:

Point essentiel pour s'assurer que nos acl fonctionnent tel que souhaité, et pour s'assurer qu'une modification n'impacte pas les autres acl. Il existe 2 manières de tester les acls, soit en utilisant slapacl, soit valider en effectuant des opérations directement dans la base. slapacl nous indique les droits mais il ne gère pas les aci.

Utilisation de slapacl: on test si un utilisateur peut gérer les membres (plus précisemment ici s'il peut se rajouter membre d'un groupe:

slapd -T acl -bdc=uubu,dc=fr -F bin/var/slapd.d/ -D"cn=Sylvain,ou=Peoples,o=uubu.org,o=Enterprise,dc=uubu,dc=fr" "member/write" -bcn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr "member/read:cn=Sylvain,ou=Peoples,o=uubu.org,o=Enterprise,dc=uubu,dc=fr"

on peut rajouter -d aci pour voir ce qui ce passe sous le capot.

La deuxième approche consiste à créer un script qui effectue des actions sur l'annuaire:

Par exemple je veut tester si un utilisateur lambda peut créer une entreprise:
echo -e "dn: o=toto,o=Enterprises,dc=uubu,dc=fr\nobjectClass: organization\no: toto\n" | ldapadd -x -D"cn=Sylvain,ou=Peoples,o=uubu.org,o=Corporates,dc=uubu,dc=fr" -wpassword -e'!noop'
puis on test le résultat:
[ $? -eq 50 ] && echo "OK"
ou alors:
[ $? -eq 14 ] && echo "ERROR!!!!!!"

N'oubliez pas l'extention noop, surtout si vous voulez tester la suppression d'une entreprise par exemple!

 

Conclusion: Les aci ne sont pas encore prêtes pour de la production, mais espérons que cela évoluera rapidement, ce modèle est quand même plus logique et intuitif que les acl. Noter que slapacl ne fonctionne pas avec les aci.

Note importante: Utilisez les outils d'openldap, tel que ldapmodify pour tester vos acl, et évitez d'utiliser ApacheDirectoryStudio. En effet, les acls avec évaluation de valeurs nécessitent un delete explicite d'une entrée, ce que ne fait pas ce dernier (entre autre).

OpenLDAP - Design d'annuaire

Mise en œuvre d'un annuaire LDAP

Le 09 septembre 2014 , Aucun commentaire pour cet article


Le design proposé ici servira de base pour les prochains articles. Une base de données LDAP a la particularité d'être organisée en une structure d'objets hiérarchiques. Ce modèle permet d'organiser ses données de manière à correspondre au plus proche de l'organisation. Il y a cependant un certains nombre de points important à respecter:


- Déport des délégations: Les délégations doivent être placées dans une branche extérieure à l'organisation, de manière à éviter q'un utilisateur puisse s'octroyer des privilèges.
- Délégation hors scope: Toute personne ayant des délégations sur une partie de l'organisation ne doit pas être en mesure de d'octroyer des droits en dehors du scope qui lui a été attribué.
- Mécanismes d'approbation: Si une délégation est sensible, ce qui est le cas au niveau de la gestion du haut du DSE, des mécanismes d'approbation ou techniques similaires doivent pouvoir être mis en œuvre.
- Hiérarchisation des rôles: les rôles doivent être hiérarchisés et séparés (par exemple les rôles techniques, et fonctionnels).
- Design basé sur les délégations et les stratégies: La structure du DIT doit avant tout correspondre à la gestion de la base (délégations) et non se focaliser exclusivement sur l'organisation fonctionnelle de l'entreprise.
- Scope limité: Les équipes en charge de la gestion des annuaires ne devraient pas être en mesure de gérer plus de 4 niveaux hiérarchiques. Cela permet d'une part de limiter les comptes à fort privilèges, de séparer les pouvoirs, et de simplifier la gestion.
- Sécurité: La sécurité d'un annuaire est cruciale, surtout si les accréditifs des utilisateurs s'y trouvent. l'authentification des comptes à privilèges doit être forte, et idéalement via des comptes dédiés.

 

Nous allons repartir sur l'exemple de l'article précédent, une fabrique de jouets. elle possède 3 sites de production et 1 site où se situe le siège. Nous appellerons cette entreprise jouets-fr. Rajoutons un peu de contenus, cette entreprise a racheté une autre entreprise de vente de jouets qui s'appelle openjouets, il n'y a pour le moment qu'un seul magasin, mais cela pourrait évoluer dans le future.

 

Le DN root, tout comme le desing du DIT, est libre. Cependant, il est intéressant d'harmoniser les contextes de nommage, et l'utilisation du composant de domaine (dc) est une bonne idée pour traduire le contexte DNS en LDAP. Prenons notre annuaire dont le DN root sera dc=uubu,dc=fr:

 

dc=uubu,dc=fr
\_ cn=Delegations
\_ o=Enterprises
   \_o=jouets-fr
     \_l=Grenoble
     \_l=Paris
     \_l=Toulouse
     \_l=Londre
   \_o=openjouets
     \_l=Paris

 

On commence simplement par déporter les délégations. Ensuite, un nœud Enterprises contient les entreprises. Chaque entreprise possède un nœud organisation portant son nom. Sous chaque entreprise, nous créons un nœud par site et sous chacun d'eux, nous créons des nœuds correspondant aux divers types d'objets de l'entreprise:

l=<site>
\_ou=Peoples
\_ou=groups
\_ou=Computers
\_ou=Printers
\_ou=Servers

Nous resterons sur ce modèle basique pour le moment. Il ne dépasse pas 4 nœuds par entreprise, et va nous permettre de définir un niveau de délégation assez simple.

 

Décortiquons un peu plus le nœud Délégations. Celui-ci va contenir les rôles de gestion de notre annuaire:

cn=Délégations
\_Administration
  \_Admin-Approvers
  \_Admin-Managers
  \_Admin-Groups
    \_ADM-<app1>
\_Applications
  \_<app1>
    \_<roleX>

La branche Administration concerne tous les groupes à fort privilèges, chapotés par les 2 groupes "top-level": Admin-Approvers et Admin-Managers. Ils permettent de se gérer mutuellement afin que personne ne soit en situation de pleins privilèges et/ou la possibilité de s'octroyer ou d'octroyer à une autre personne les pleins pouvoirs.

Sous Admin-groups, nous retrouverons tous les responsables applicatifs (c.a.d: les personnes habilitées à gérer les droits des autres personnes. Sous Applications, nous allons trouver tous les rôles de chaque application communes à toutes les entreprises, principalement le serveur LDAP lui-même. Les applications de base seront discutées dans l'article consacré aux contrôle d'accès.

Vie et évolution de l'annuaire

Un design d'annuaire doit prendre en compte l'exploitation au quotidien, et l'évolution dans le temps. La complexité de l'organisation de l'annuaire peut dépendre également de la solution utilisée pour gérer cet annuaire. Si l'annuaire est géré à l'ancienne, par exemple via phpldapadmin, la simplicité sera de mise. Par contre, si l'annuaire est gérée par une solution de gestion des identités, le design peut être plus élaboré, puisque l'annuaire devient donc une base applicative.

Prenons le cas d'une entreprise qui embauche fortement. À l'origine, les comptes utilisateurs étaient tous sous le nœud ou=Peoples. Avec le temps, on souhaite séparer la production de l'administration, gérées par 2 équipes différentes, ou par besoin de définir des stratégies différentes. Nous avons 2 cas possibles:

- On renomme ou=Peoples en ou=Production, et on créé sous le même parent ou=Administration.
- Sous ou=Peoples, on créé ou=Production et ou=Administration.

Dans le premier cas, celà nous implique de modifier fortement les acl, mais nous conservons le même niveau dans la hiérarchie. Dans l'autre cas, la modification des acl ne s'opèrent qu'au niveau du nœud ou=Peoples, et l'ajout ultérieurs d'autres nœuds ne necéssitera pas de définir de nouvelles acl ou d'en modifier d'autres. En revanche, nous ajoutons un niveau hiérarchique.

Autre cas, une entreprise en rachète une autre, et nous devons l'intégrer dans l'infrascture existante. Ici, le fait d'avoir un nœud Enterprise, nous permet de rajouter d'autres entreprises, ou possiblement d'autre sites sous un entreprise. Il suffira de se conformer et de calquer le modèle d'acl en place et de l'adapter à la nouvelle branche.

Dernier point important: il existe 3 façons de gérer l'appartenance d'un objet: Sa place dans la hiérarchie (est unique), le "membership" (n'est pas unique), et le DN (unique ou non en fonction du design). Cet aspect est à prendre en compte avant de se lancer dans un design d'arborescence afin d'éviter de se couper l'herbe sous les pieds!

OpenLDAP - Personalisation du schéma

Création d'un schéma personnalisé

Le 16 juin 2014 , Aucun commentaire pour cet article


Dans cet article, nous allons créer un schéma personnalisé. Nous utiliserons l'héritage pour simplifier le schéma, via le biais de définitions génériques. Le scénario utilisé permet de comprendre les différents composants et leurs intéractions, tout en se conformant aux best practices avec cependant quelques entorses, à des fins pédagogiques.

Nous allons partir d'un exemple simple: Une entreprise de fabrique de jouets possède 3 ateliers de fabrication, chacun fabricant des jouets différents (peluches, jouets en bois, etc.). Nous souhaitons référencer les produits fabriqués, les matières premières utilisées et les fournisseurs. Ce que nous souhaitons définir, ce sont les ateliers avec le(s) responsable(s) de production, les produits, avec pour chacun, les quantités en stock, les seuil minimum et maximum de stock, la/les couleurs du jouet, le prix unitaire. Les matières première permettent de référencer les matériaux, avec les stock disponible et les founisseurs, incluant les délai de livraison et les contacts.

Nous utiliserons l'héritage pour simplifier le schéma, via le biais de définitions génériques. Nous ajouterons également un peu de contraintes à ce schéma. Commençons par notre en-tête:

dn: cn=custom,cn=schema,cn=config
ObjectClass: top
ObjectClass: olcConfig
ObjectClass: olcSchemaConfig
cn: custom

Nous utiliserons la branche expérimentale pour nos OID. Définissons quelques alias pour rendre la suite plus compréhensible:

olcObjectIdentifier: CustomRoot 1.3.6.1.3
olcObjectIdentifier: Custom CustomRoot:1
olcObjectIdentifier: CustomAttributeType CustomRoot:1
olcObjectIdentifier: CustomObjectClass CustomRoot:2
olcObjectIdentifier: CustomAttributeTypeGen CustomAttributeType:1
olcObjectIdentifier: CustomAttributeTypeDef CustomAttributeType:2
olcObjectIdentifier: CustomObjectClassGen CustomObjectClass:1
olcObjectIdentifier: CustomObjectClassDef CustomObjectClass:2

On créé des définitions génériques:

olcAttributeTypes: ( CustomAttributeTypeGen:1 NAME 'custom-DNGeneric' DESC 'Generic DN based attributes' EQUALITY distinguishedNameMatch SYNTAX OMsDN)
olcAttributeTypes: ( CustomAttributeTypeGen:2 NAME 'custom-ia5Generic' DESC 'Generic IA5 String' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX OMsIA5String)
olcAttributeTypes: ( CustomAttributeTypeGen:3 NAME 'custom-utf8Generic' DESC 'Generic Directory String' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch ORDERING caseIgnoreOrderingMatch SYNTAX OMsDirectoryString)
olcAttributeTypes: ( CustomAttributeTypeGen:4 NAME 'custom-BoolGeneric' DESC 'Generic Boolean attribute' EQUALITY booleanMatch SYNTAX OMsBoolean SINGLE-VALUE)
olcAttributeTypes: ( CustomAttributeTypeGen:5 NAME 'custom-numGeneric' DESC 'Generic numeric attribute' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX OMsInteger{16} )
olcObjectClasses: ( CustomObjectClassGen:1 NAME 'custom-StructuralGeneric' SUP custom-top DESC 'Generic structural object' STRUCTURAL MAY ( displayName $ description )

et enfin nos définitions, on commence par définir notre classe abstraite:

olcObjectClasses: ( CustomObjectClassDef:1 NAME 'custom-top' DESC 'top of our custom superclass chain' ABSTRACT MUST objectClass MAY ( description $ custom-comment ) )

Les définitions d'attributs:

olcAttributeTypes: ( CustomAttributeTypeDef:1 NAME 'custom-RefId' DESC 'Reference Identifier' SUP custom-numGeneric SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:2 NAME 'custom-comment' DESC 'Any comment about this object' SUP custom-utf8Generic )
olcAttributeTypes: ( CustomAttributeTypeDef:3 NAME 'custom-color' DESC 'toy s color' SUP custom-numGeneric )
olcAttributeTypes: ( CustomAttributeTypeDef:4 NAME 'custom-stock' DESC 'number in stock' SUP custom-numGeneric SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:5 NAME 'custom-stockMax' DESC 'Unit max in stock' SUP custom-numGeneric SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:6 NAME 'custom-stockMin' DESC 'Unit min in stock' SUP custom-numGeneric SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:7 NAME 'custom-age' DESC 'age of use' SUP custom-ia5Generic )
olcAttributeTypes: ( CustomAttributeTypeDef:8 NAME 'custom-Unit' DESC 'Unit basis' SUP custom-utf8Generic SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:9 NAME 'custom-Delay' DESC 'procurement lead time' SUP custom-utf8Generic SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:10 NAME 'custom-ppu' DESC 'Price per Unit' SUP custom-numGeneric SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:11 NAME 'custom-availability' DESC 'Availability of the reference' SUP custom-BoolGeneric )
olcAttributeTypes: ( CustomAttributeTypeDef:12 NAME 'custom-foreman' DESC 'chief workshop' SUP custom-DNGeneric SINGLE-VALUE )
olcAttributeTypes: ( CustomAttributeTypeDef:13 NAME 'custom-contact' DESC 'Supplier contact' SUP custom-utf8Generic )

 

les définitions de classes d'objet:

olcObjectClasses: ( CustomObjectClassDef:1 NAME 'custom-top' DESC 'top of our custom superclass chain' ABSTRACT MUST objectClass MAY ( description $ custom-comment ) )
olcObjectClasses: ( CustomObjectClassGen:1 NAME 'custom-StructuralGeneric' SUP custom-top DESC 'Generic structural object' STRUCTURAL MAY ( displayName $ description ) )
olcObjectClasses: ( CustomObjectClassDef:2 NAME 'custom-RefObj' DESC 'Enforce a reference' SUP custom-StructuralGeneric MUST custom-RefId )
olcObjectClasses: ( CustomObjectClassDef:3 NAME 'custom-Product' DESC 'Product définition' SUP custom-RefObj MUST custom-availability )
olcObjectClasses: ( CustomObjectClassDef:4 NAME 'custom-RawMaterials' SUP custom-RefObj )
olcObjectClasses: ( CustomObjectClassDef:5 NAME 'custom-Workshop' SUP custom-StructuralGeneric MUST custom-foreman )
olcObjectClasses: ( CustomObjectClassDef:6 NAME 'custom-Supplier' SUP custom-StructuralGeneric MUST custom-contact)
olcObjectClasses: ( CustomObjectClassDef:7 NAME 'custom-SecondaryData' SUP custom-top AUXILIARY MAY ( custom-color $ custom-stock $ custom-stockMax $ custom-stockMin $ custom-age $ custom-Unit $ custom-Delay $ custom-ppu ) )

 

Voilà pour la définition des objects. Maintenant il nous faut un peu de contraintes. Ajoutons quelques règles de contenu:

olcDitContentRules: ( CustomObjectClassDef:3 NAME 'dcr-Product' DESC 'custom-Product may only be members of the custom-SecondaryData aux class' AUX 1.3.6.1.3.1.2.2.7 NOT ( 1.3.6.1.3.1.1.2.9 $ 1.3.6.1.3.1.1.2.10) )
olcDitContentRules: ( CustomObjectClassDef:4 NAME 'dcr-RawMaterials' DESC 'custom-RawMaterials may only be members of the custom-SecondaryData aux class' AUX 1.3.6.1.3.1.2.2.7 MUST 1.3.6.1.3.1.1.2.4 MAY ( 1.3.6.1.3.1.1.2.9 $ 1.3.6.1.3.1.1.2.5 $ 1.3.6.1.3.1.1.2.6 ) NOT 1.3.6.1.3.1.1.2.8 )
olcDitContentRules: ( CustomObjectClassDef:5 NAME 'dcr-Workshop' DESC 'custom-Workshop may only be members of the custom-SecondaryData aux class' AUX 1.3.6.1.3.1.2.2.7 MUST 1.3.6.1.3.1.1.2.9 NOT ( 1.3.6.1.3.1.1.2.3 $ 1.3.6.1.3.1.1.2.4 $ 1.3.6.1.3.1.1.2.5 $ 1.3.6.1.3.1.1.2.6 $ 1.3.6.1.3.1.1.2.7 $ 1.3.6.1.3.1.1.2.8 ) )
olcDitContentRules: ( CustomObjectClassDef:6 NAME 'dcr-Supplier' DESC 'custom-Supplier may only be members of the custom-SecondaryData aux class' AUX 1.3.6.1.3.1.2.2.7 MAY 1.3.6.1.3.1.1.2.9 NOT ( 1.3.6.1.3.1.1.2.3 $ 1.3.6.1.3.1.1.2.4 $ 1.3.6.1.3.1.1.2.5 $ 1.3.6.1.3.1.1.2.6 $ 1.3.6.1.3.1.1.2.7 $ 1.3.6.1.3.1.1.2.8 ) )

 

OpenLdap ne gère pas les règles de structure, mais permet de les gérer similairement via les ACL étendues (pensez à ajouter olcAddContentAcl à votre base):

Pour cela, définissons l'arborescence suivance:

dc=uuub,dc=fr

  \_ o=Enterprise

      \_ cn=<workshop>

          \_ cn=<product>

Ce que nous souhaitons, c'est que chaque atelier soit géré par son responsable, et que ce dernier puisse créer et gérer les jouets qui y sont fabriqués, sous cet atelier.

olcAccess: to dn.baseobject="o=Enterprise,dc=uubu,dc=fr" attrs="children" by group/groupOfMembers/member="cn=Admins,dc=uubu,dc=fr" write by users read by * none break
olcAccess: to dn.onelevel="o=Enterprise,dc=uubu,dc=fr" filter="(objectClass=custom-Workshop)" attrs="entry,@custom-Workshop,@custom-SecondaryData" by group/groupOfMembers/member="cn=Admins,dc=uubu,dc=fr" write by users read by * none break

olcAccess: to dn.regex="^(cn=[^,]+),o=Enterprise,dc=uubu,dc=fr$" filter="(objectClass=custom-Workshop)" attrs=entry,@custom-Workshop,@custom-SecondaryData by set="this/custom-foreman & user" write by users read by * none break
olcAccess: to dn.regex="^(cn=[^,]+),o=Enterprise,dc=uubu,dc=fr$" filter="(objectClass=custom-Workshop)" attrs="children" by set="this/custom-foreman & user" write by * read break
olcAccess: to dn.regex="^cn=([^,]+),(cn=[^,]+,o=Enterprise,dc=uubu,dc=fr)$" filter="(objectClass=custom-Product)" attrs="entry,@custom-Product,@custom-SecondaryData" by group/custom-Workshop/custom-foreman.expand="$2" write by users read by * none

 

Attention à l'utilisation des expressions régulières gourmandes en ressources, et les sets encore considérés comme expérimentaux. Je vais m'arrêter là, les autres règles de structure sont calqués sur celles-ci et ne devraient pas poser de problème.

Comme vous pouvez le remarquer, les règles de structure sont beaucoup plus souple que les acl, mais sont moins précises. Ainsi, là où une règle de structure est définis pour tout le DIT, des acls différentes peuvent être définis pour différentes sous-arborescence. Autre avantage des acls: on gère également les droits dans la foulée, mais ce n'est pas sans une complexité accrue.

Voilà, pour cette article. Je vais consacrer un article dédié aux ACLs et à la délégation.

 


précédents