Jails FreeBSD (ici 6.2) ---------------------- I) Introduction --------------- Le modèle Unix des droits est simple mais rapidement insuffisant pour faire face à des situations complexes où l'on doit déléguer certains droits d'administrations. L'ajout d'un contrôle d'accès fin améliore les choses, mais au prix de la complexité de l'administration et de l'implémentation. Les jails permettent de partitionner l'environnement tout en gardant le modèle simple des droits Unix. Dans une jail, les utilisateurs sont confinés dans la prison. root ---- Déléguer les droits root via un chroot est clairement insuffisant. root garde tous ses droits, seule la vue de l'arborescence est changée. La solution des jails --------------------- Les jails apportent une solution en partitionnant l'environnement au niveau des processus, des systèmes de fichiers et des ressources réseaux. - processus emprisonnés. Un processus et ses descendant ne peuvent pas sortir de leur prison. Un processus ne peut pas agir sur un processus hors de sa prison. Root reste un utilisateur privilégié mais ses droits sont très fortements limités : - interdiction de créer des n½uds de périphériques. - pas de possibilité de monter / démonter un système de fichier. - pas de possibilité de modifier les routes réseaux. - etc... En résumé, seules les actions impactants la jail sont autorisées. - système de fichiers. Seule l'arborescence de la prison est visible (comme chroot). - réseau. Chaque jail est attachée à une adresse IP(v4), les processus emprisonnés ne peuvent que s'attacher à cette adresse. En cas d'utilisation d'une raw socket (interdites par défaut), les adresses IP sont forcées. Ces limitations ne sont implémentées que pour les sockets IPv4 (ou local), les autres domaines sont interdits. [source: ] II) En pratique --------------- Le lancement d'une jail s'effectue par la commande jail(8). voir man jail. Exemple ------- Lancement de sh dans une jail sur /rescue ( les binaires dans /rescue sont statiques ) Pour d'autres programmes, la manip est la même, il faut en plus copier les bibliothèques. On a besoin d'une adresse IP pour la jail : # ifconfig lo0 alias 192.168.2.1 netmask 0xffffffff Et des n½uds de périphériques (devfs) # mkdir /rescue/dev # mount -t devfs dev /rescue/dev on lance sh dans la prison avec le nom d'hôte roxette-1 : # jail /rescue roxette-1 192.168.2.1 sh # ./ls ... # ./ps PID TT STAT TIME COMMAND 53606 p4 SJ 0:00.00 sh 53610 p4 R+J 0:00.00 ./ps Le 'J' dans la colonne STAT indique que le processus est emprisonné. Comment on s'appelle ? # ./hostname roxette-1 Testons le confinement de root : # ./mount /dev/ad0s3a on / (ufs, local, noatime) # ./umount /dev/ad0s3a umount: unmount of / failed: Operation not permitted La prison vue de l'hôte ----------------------- Ici nous somme root mais sur l'hôte (ie pas dans une jail). jls(8) permet de lister les jails actives et leur 'JID' : # jls JID IP Address Hostname Path 9 192.168.2.1 roxette-1 /rescue Les processus jailés sont également marqués 'J' avec ps : # ps PID TT STAT TIME COMMAND 1467 v0 Is+ 0:00,00 /usr/libexec/getty Pc ttyv0 [...] 53629 p4 I+J 0:00,00 /sh jexec(8) permet de lancer un processus dans une jail existante à partir de l'hôte : # jexec 9 ./ps PID TT STAT TIME COMMAND 53629 p4 I+J 0:00.00 /sh 53705 p7 R+J 0:00.00 ./ps Maintenant dans la jail, quittont le sh emprisonné. # exit (là on retourne à l'hôte) # jls (la jail a été détruite automatiquement, parler du refcounting ici et du problème sur les sockets). Résumé: jail(8), jls(8), jexec(8), confinement, séparation jail/hôte. III) Une machine virtuelle FreeBSD dans le FreeBSD -------------------------------------------------- Il n'y a pas de différences fondamentales avec l'exemple précédent : - on copie un système complet sous la racine de la jail (avec quelques petits ajustements). - on configure la machine virtuelle (rc.conf, etc...). - on monte un devfs sous cette racine (mais un devfs propre, pas comme l'exemple précédent !). - on lance /etc/rc dans la prison, qui va démarrer la machine virtuelle exactement comme l'hôte principal. - Suivant la configuration des adresses IP entre l'hôte principal et les jails, il peut-être nécessaire de NATer sur l'hôte principal. Construction de la prison ------------------------- Il suffit d'installer un "monde" déjà compilé (avec make buildworld) dans la racine de la jail. La page de man de jail(8) décrit la procédure et l'intégration de la jail dans les scripts /etc/rc.d : La jail aura pour IP 192.168.2.1, la racine sera /usr/jails/192.168.2.1 # mkdir /usr/jails/192.168.2.1 # cd /usr/src # make installworld DESTDIR=/usr/jails/192.168.2.1 # make distribution DESTDIR=/usr/jails/192.168.2.1 L'arborescence créée : # cd /usr/jails/192.168.2.1 # ls .cshrc bin/ etc/ media/ rescue/ sys@ var/ .profile boot/ lib/ mnt/ root/ tmp/ COPYRIGHT dev/ libexec/ proc/ sbin/ usr/ Et ça ne prend que 130 Mo d'espace disque (il n'y a que la base ici). # du -hd 1 858K ./bin 610K ./boot 2,0K ./dev 1,3M ./etc 3,5M ./lib 158K ./libexec 2,0K ./media 2,0K ./mnt 2,0K ./proc 3,2M ./rescue 10K ./root 3,5M ./sbin 2,0K ./tmp 117M ./usr 138K ./var 130M . On songe à garder une note dans la jail de la version du monde utilisé pour la construire. C'est utile lors des mises à jours. Par convention perso : # echo `uname -a` > /usr/jails/192.168.2.1/etc/VERSION Et on fait une configuration grossière de la jail comme l'indique "man 8 jail": /usr/jails/192.168.2.1/etc/rc.conf sendmail_enable="NONE" rpcbind_enable="NO" network_interfaces="" un fstab vide : # touch /usr/jails/192.168.2.1/etc/fstab fuseau horaire # echo "Europe/Paris" > /usr/jails/192.168.2.1/etc/timezone crontab système, adjkerntz a enlever /usr/jails/192.168.2.1/etc/crontab (optionnel) recopie des configs perso : /etc/csh.cshrc /root/.cshrc /etc/make.conf /etc/login.conf ... ***** NOTE : LA CONFIGURATION N'EST PAS TERMINEE ! ******************** Il faut aussi configurer les mots de passes utilisateurs (en particulier celui de root), les aliases mails et toutes ces sortes de choses. La méthode est la même que pour l'hôte principal. S'il y a plusieurs jails, modifier les heures des crons "periodic" peut aussi être utile (ils démarrent tous à 3h00 par défaut). *********************************************************************** Lancement de la jail à partir des scripts rc -------------------------------------------- Les scripts de démarrage "rc" permettent d'automatiser le lancement des jails, y compris le montage des systèmes de fichiers spéciaux comme devfs. Il suffit de le paramétrer dans /etc/rc.conf : /etc/rc.conf # alias IP sur lo0 pour la jail "test1" ifconfig_lo0_alias0="inet 192.168.2.1 netmask 255.255.255.255" ############################################################## ### Jail Configuration ####################################### ############################################################## jail_enable="YES" # Set to NO to disable starting of any jails jail_list="test1" # Space separated list of names of jails jail_set_hostname_allow="NO" # Allow root user in a jail to change its hostname jail_socket_unixiproute_only="YES" # Route only TCP/IP within a jail jail_sysvipc_allow="NO" # Allow SystemV IPC use from within a jail jail_stop_jailer="NO" # Only stop jailer. Requires jail_*_exec be set # to use sysutils/jailer port to start the jail. # To use rc's built-in jail infrastructure create entries for # each jail, specified in jail_list, with the following variables. # ---------------------------------------------------------------------- # test jail_test1_rootdir="/usr/jails/192.168.2.1" # Jail's root directory jail_test1_hostname="roxette-21.lamaiziere.net" # Jail's hostname jail_test1_ip="192.168.2.1" # Jail's IP number jail_test1_exec="/bin/sh /etc/rc" # commands to execute in jail jail_test1_devfs_enable="YES" # mount devfs in the jail jail_test1_fdescfs_enable="NO" # mount fdescfs in the jail jail_test1_procfs_enable="NO" # mount procfs in jail jail_test1_devfs_ruleset="devfsrules_jail" # devfs ruleset to apply to jail La jail est lancée ou arrêtée par le script /etc/rc.d/jail # /etc/rc.d/jail start test1 (si aucun nom de jail n'est spécifié, le script agit sur toutes les jails) # jls JID IP Address Hostname Path 3 192.168.2.1 roxette-21.lamaiziere.net /usr/jails/192.168.2.1 # /etc/rc.d/jail stop test1 # jls on la relance # /etc/rc.d/jail start test1 # jls JID IP Address Hostname Path 4 192.168.2.1 roxette-21.lamaiziere.net /usr/jails/192.168.2.1 Et on rentre dans la jail (id = 4) # jexec 4 csh [ ici on est dans la prison ] Jetons un ½il : # ls /dev fd ptyp0 ptyp3 ptyp6 ptyp9 ptypc stderr ttyp0 ttyp3 ttyp6 ttyp9 ttypc zero log ptyp1 ptyp4 ptyp7 ptypa ptypd stdin ttyp1 ttyp4 ttyp7 ttypa ttypd null ptyp2 ptyp5 ptyp8 ptypb random stdout ttyp2 ttyp5 ttyp8 ttypb urandom Grâce au jeu de règles spécifié dans /etc/rc.conf ("devfsrules_jail)", il n'y a que le strict nécessaire. En particulier aucun périphérique matériel n'est accessible, kmem est absent, etc. # mount /dev/ad0s3f on / (ufs, local, noatime, soft-updates) Configurons rapidement un accès ssh pour se loguer à travers le réseau et non pas à partir de l'hôte principal. # echo sshd_enable=\"YES\" >> /etc/rc.conf # /etc/rc.d/sshd start Generating public/private rsa1 key pair. Your identification has been saved in /etc/ssh/ssh_host_key. Your public key has been saved in /etc/ssh/ssh_host_key.pub. [...] Starting sshd. Les sockets: roxette-21# netstat -a netstat: kvm not available Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address (state) tcp4 0 0 192.168.2.1.ssh *.* LISTEN udp4 0 0 192.168.2.1.syslog *.* netstat: kvm not available Active UNIX domain sockets Address Type Recv-Q Send-Q Inode Conn Refs Nextref Addr c6604578 dgram 0 0 c7323440 0 0 0 /var/run/logpriv c741fe38 dgram 0 0 c81d0550 0 0 0 /var/run/log kvm (kernel memory interface) n'est pas accessible puisque /dev/kmem est absent, d'où l'avertissement de netstat. - Ajout d'un utilisateur 'patrick' par sysinstall - accès ssh cp /home/patrick/authorized_keys2 /usr/jails/192.168.2.1/usr/home/patrick/.ssh/ $ ssh 192.168.2.1 Il est possible d'utiliser un autre moyen pour se loguer : telnet, X, ssh est le plus sécurisé. Mise à jour de la prison. ------------------------- Le « monde » utilisé par une jail doit être maintenu synchro par rapport au monde de l'hôte principal et du noyau. La mise à jour s'effectue exactement comme la mise à jour de l'hôte. # cd /usr/src # mergemaster -p -D /usr/jails/192.168.2.1 # make installworld DESTDIR=/usr/jails/192.168.2.1 # mergemaster -D /usr/jails/192.168.2.1 Il est conseillé d'arrêter la jail durant cette procédure. Il n'est pas possible d'effectuer la mise à jour depuis la jail, make installworld doit changer des ACL et cela n'est pas autoriser par défaut. Installation de ports. ---------------------- Là aussi il faut voir la jail comme une machine virtuelle et la méthode ne diffère pas de la méthode habituelle : cvsup / portsnap de l'arbre des ports, make install, pkg_add, portupgrade, etc... Certains logiciels peuvent ne pas fonctionner dans une jail, parce qu'ils nécessitent l'accès à un périphérique (ex: cdrecord) ou à une autre ressource inaccessible (raw socket par exemple). Dans ce cas il faut voir au cas par cas l'ajout des n½uds de périphériques dans le /dev de la jail ou s'il est possible de modifier les limitations imposées aux jails : il y a une option pour les raw sockets par exemple. Dans tous les cas c'est à l'administrateur de l'hôte principal de décider. Résumé: Installation, configuration, login via le réseau, mise à jour, installation de ports. Options sur les jails --------------------- Certaines caractéristiques des jails sont configurables par des sysctl à partir de l'hôte principal pour toutes les jails. Voir man jail. - security.jail.chflags_allowed Permission de modifier les ACL par l'utilisateur root des jails. La permission dépend aussi du "niveau de sécurité" courant kern.securelevel - security.jail.allow_raw_sockets Permission d'utiliser les raw sockets. - security.jail.enforce_statfs Visibilité des points de montages : # sysctl security.jail.enforce_statfs=0 security.jail.enforce_statfs: 1 -> 0 roxette-21:/# mount /dev/ad0s3a on / (ufs, local, noatime) devfs on /dev (devfs, local) /dev/ad0s3e on /tmp (ufs, local, noatime, soft-updates) /dev/ad0s3f on /usr (ufs, local, noatime, soft-updates) /dev/ad0s3d on /var (ufs, local, soft-updates) /dev/ad0s2 on /mnt/win (ntfs, local, noexec, nosuid, read-only) linprocfs on /usr/compat/linux/proc (linprocfs, local) devfs on /usr/jails/192.168.2.1/dev (devfs, local) # sysctl security.jail.enforce_statfs=1 security.jail.enforce_statfs: 0 -> 1 roxette-21:/# mount /dev/ad0s3f on / (ufs, local, noatime, soft-updates) devfs on /dev (devfs, local) # sysctl security.jail.enforce_statfs=2 security.jail.enforce_statfs: 1 -> 2 roxette-21:/# mount /dev/ad0s3f on / (ufs, local, noatime, soft-updates) - security.jail.sysvipc_allowed Permission d'utiliser les IPC system V (inter processus communications). - security.jail.socket_unixiproute_only Utilisation seule des domaines PF_LOCAL, PF_INET, PF_ROUTE à l'intérieur d'une jail. - security.jail.set_hostname_allowed Permission de changer le nom d'hôte. Deux autres sysctl ont une valeur par jail (ie chaque jail peut posseder sa propre valeur) : kern.securelevel et kern.hostname. - kern.securelevel « Niveau de sécurité », le niveau de sécurité ne peut être qu'augmenté mais jamais diminué. Le niveau de sécurité limite certaines opérations : man init : -1 Permanently insecure mode - always run the system in level 0 mode. This is the default initial value. 0 Insecure mode - immutable and append-only flags may be turned off. All devices may be read or written subject to their permissions. 1 Secure mode - the system immutable and system append-only flags may not be turned off; disks for mounted file systems, /dev/mem, /dev/kmem and /dev/io (if your platform has it) may not be opened for writing; kernel modules (see kld(4)) may not be loaded or unloaded. 2 Highly secure mode - same as secure mode, plus disks may not be opened for writing (except by mount(2)) whether mounted or not. This level precludes tampering with file systems by unmounting them, but also inhibits running newfs(8) while the system is multi- user. In addition, kernel time changes are restricted to less than or equal to one second. Attempts to change the time by more than this will log the message ``Time adjustment clamped to +1 second''. 3 Network secure mode - same as highly secure mode, plus IP packet filter rules (see ipfw(8), ipfirewall(4) and pfctl(8)) cannot be changed and dummynet(4) or pf(4) configuration cannot be adjusted. voir aussi security(7), chflags(1). L'aspect pratique concernant les jails est qu'il est possible d'utiliser un niveau plus élévé dans la jail que dans l'hôte : par exemple avec une jail tournant avec un securelevel >= 1 et un hôte tournant en <= 0, il sera possible de supprimer les ACL à partir de l'hôte en l'interdisant à partir de la jail. Note que cela n'empêche pas *d'ajouter* des ACL. exemple : roxette:~# sysctl security.jail.chflags_allowed=1 security.jail.chflags_allowed: 0 -> 1 roxette-21:/bin# sysctl kern.securelevel kern.securelevel: -1 /bin/rcp porte le flag schg, on peut le changer : roxette-21:/bin# ls -lo rcp -r-sr-xr-x 1 root wheel schg 18332 29 mar 21:57 rcp* roxette-21:/bin# chflags noschg rcp roxette-21:/bin# ls -lo rcp -r-sr-xr-x 1 root wheel - 18332 29 mar 21:57 rcp* roxette-21:/bin# sysctl kern.securelevel=2 kern.securelevel: -1 -> 2 roxette-21:/bin# chflags schg rcp roxette-21:/bin# ls -lo rcp -r-sr-xr-x 1 root wheel schg 18332 29 mar 21:57 rcp* roxette-21:/bin# chflags noschg rcp chflags: rcp: Operation not permitted À partir du passage en securelevel=2, on a pu ajouter le flag schg à /bin/rcp mais on ne peut plus l'enlever. Note : Avec security.jail.chflags_allowed=0, chflags est tout simplement interdit, il n'est pas possible de modifier les ACL quelque soit le securelevel en cours des jails. Résumé: sysctls "security.jail.*", securelevel, ACL. Limitations. ------------ Les jails ont quelques limitations : - Seul IPv4 est supporté, IPv6 est à l'étude ou à divers stades de développement (je crois). - Il serait souhaitable de pouvoir limiter les IPC à une jail (avoir un security.jail.sysvipc_allowed par jail). - Il n'est pas possible d'allouer des ressources UC par jail. Les processus sont schédulés normalement. Une jail qui exécute un programme de type "bombe fork" impactera l'ensemble (il est possible de jouer sur les limites voir limits(1) ). IV) Mise en ½uvre. ------------------ Montages multiples. ------------------- Plutôt que de recopier n fois les binaires ou l'arbre des ports, on peut utiliser des montages multiples en read-only. L'utilisation des montages unionfs est aussi possible mais unionfs est actuellement instable sous FreeBSD. - Montage en nullfs nullfs permet de monter une arborescence existante sous une autre arborescence. Par exemple : # mount_nullfs -o ro /usr/ports /usr/jails/192.168.2.1/usr/ports # mount /usr/ports on /usr/jails/192.168.2.1/usr/ports (nullfs, local, read-only) /etc/fstab peut-être utilisé pour monter le fs au démarrage : /usr/ports /usr/jails/192.168.2.1/usr/ports nullfs ro 0 0 Jailer. ------ sysutils/jailer est un utilitaire qui permet de lancer / arrêter une jail depuis celle-ci ou de l'extérieur. Il permet aussi d'éviter le problème de refcounting puisque la jail n'est pas terminée proprement dit, jailer arrête les processus mais reste actif. Utilitaires. ------------ Il y a quelques utilitaires concernant les jails dans ports/sysutils. V) Conclusion. -------------- Les jails FreeBSD sont simples à mettre en ½uvre, sans remise en question du shéma Unix traditionnel. Elles sont en outre proprement intégrées au système et disponibles "out of the box". Elles apportent : - Du buziness : Certains hébergeurs proposent des serveurs semi-dédiés basés sur jail(). - De la sécurité : Si un « méchant » parvient à compromettre un service jailé il aura du mal à aller plus loin. Mais ce n'est pas impossible, jail() a déjà eu des problèmes de sécurité (assez peu). Comme chaque jail a son IP, il est facile de filtrer au niveau du parefeu. On peut aussi faire des stats jails par jails, par exemple le trafic réseau par IP. - De la facilité : Qui a déjà eu à mettre à jour Perl (ou pire libtool ou gettext) sera bien content de pouvoir faire la mise à jour jail par jail, en n'interrompant que le strict minimum à chaque fois. Mieux il est facile de copier une jail, de la lancer et de faire des tests pour vérifier si cela fonctionne sans risquer de casser quelque chose. - Du fun : Des tas de machines virtuelles à dispo pour des expérimentations de toutes sortes. Les jails c'est bon, mangez-en. Utilisation personnelle (pentium 3/733 MHz/512 Mo RAM): ------------------------------------------------------ 7 jails 1: Apache + SquirellMail // web + webmail 2: Postfix + CourierImap + SA + Clamav // serveur de courrier (principal) 3: Postfix + PostGrey // serveur de courrier (passerelle et préfiltrage) 4: Postfix // serveur de courrier (backup pour d'autres domaines) 5: Inn // serveur de news 6: tnftpd // serveur FTP 7: divers // services divers et variés en vrac Les services ne sont pas très chargés (serveur perso). Charge moyenne sur une semaine = 0.30 Top: last pid: 15615; load averages: 0.10, 0.10, 0.14 up 81+10:20:44 23:54:03 146 processes: 2 running, 144 sleeping CPU states: 4.0% user, 0.2% nice, 2.0% system, 0.5% interrupt, 93.3% idle Mem: 188M Active, 165M Inact, 101M Wired, 12M Cache, 60M Buf, 27M Free Swap: 2048M Total, 57M Used, 1990M Free, 2% Inuse J'utilise deux scripts pour l'installation et la mise à jour d'une jail que j'ai pompé chez www.the-labs.com et adapté à mon usage : http://www.lamaiziere.net/private/jail_install http://www.lamaiziere.net/private/jail_update Références : ------------ page de man de jail. http://docs.freebsd.org/44doc/papers/jail/jail.html (par les auteurs) http://www.the-labs.com/FreeBSD/JailTools/ (plutôt pour FreeBSD 4, mais infos utiles) http://www.freebsddiary.org/jail-6.php ------------------------------------------------------------------------------------ "La licence BEER-WARE" (Révision 42) : Patrick Lamaizière