Skip to content

Latest commit

 

History

History
125 lines (103 loc) · 7.18 KB

sujet.md

File metadata and controls

125 lines (103 loc) · 7.18 KB

Objectif : Créer un système de lancement de services « containérisés » en limitant au maximum l’utilisation des permissions root.

Jusqu’ici, on utilisait l’IP forwarding associé à une règle iptables NAT pour, donner l’accès au réseau au applications « containérisées ». Cette méthode présente comme limite principale, l’obligation de créer toutes les paires d’interfaces veth dans le namespace « racine » puis de migrer une interface liée dans le nouveau namespace network. Cette opération nécessite obligatoirement les droits root puisque toutes les opérations doivent être effectuées au niveau du namespace « racine ». On se propose ici de n’utiliser qu’une seule interface veth dont une des extrémités sera placée dans le namespace réseau d’un processus utilisateur non privilégié qui utilise également le namespace user avec le mapping root ainsi que le namespace UTS. Ainsi, si l’utilisateur ne dispose pas de permission dans le namespace racine, il peut tout à fait créer autant d’interfaces virtuelles qu’il souhaite dans le namespace network+user. Pour simplifier la configuration des interfaces, on utilisera un serveur DHCP qui écoutera sur l’interface virtuelle au niveau du namespace racine et des interfaces macvlan créées dans le namespace network+user. Depuis ce dernier, il est alors possible de créer de nouveaux namespaces qui isoleront plus drastiquement les applications (chroot + namespaces network, mount, ipc, time, uts et pid). Toutes les opérations permettant de donner accès au réseau se passeront désormais dans le namespace user+network et donc sans nécessité des privilèges root.

1ère étape

À cette étape, il est inutile (et contreproductif) d'utiliser l'isolation chroot : ne le faites pas !

  • Côté namespace racine (privilèges root)
    • Créer et configurer une interface veth
    • Faites le nécessaire pour lancer dhcpd sur l’interface veth
    • Placer l’interface appairée dans le namespace user
  • Coté namespace (user non privilégié) :
    • Attendre la disponibilité de l’interface veth
    • Configurer l’interface avec dhclient

Vérifier que tout fonctionne convenablement.

2ème étape

Faites le nécessaire pour lancer des applications « containérisées » (chroot+namespaces) : un serveur web, un serveur tftp, etc (cf TP 1). Chacun des namespaces utilisera une interface de type macvlan associée à l’interface veth en mode bridge :

  • On crée l’interface et on la déplace dans le namespace du "container"
  • Dans l'espace "container", on confie à dhclient le soin de récupérer les informations réseau (ce qui permet de récupérer aussi les informations DNS automatiquement).
  • Vérifier que les règles de routage permettent bien d'accéder au container depuis le namespace racine.

Quelques indications utiles :

  • Le service apparmor peut empêcher le démarrage du serveur DHCP, si nécessaire arrêtez le service.
  • dhclient utilise un script nommé dhclient-script (dans /usr/sbin) qui effectue une part importante de la configuration. Celui-ci doit être adapté à l’environnement chroot+namespace de votre « container ». Une option de dhclient permet de lui indiquer où trouver le script.

Le schéma suivant illustre le fonctionnement attendu.

Schéma 1

3ème étape (scripts d’automatisation)

Créez deux scripts qui automatisent le processus de lancement des « containers ».

4ème étape

Adaptez vos scripts pour qu’ils fonctionnent sous forme de « daemon » :

  • Le script « côté root » détecte ou est informé du lancement d’un script « côté utilisateur » pour créer automatiquement une interface veth sur laquelle écoute un serveur DHCP qui distribuera les adresses aux « containers » utilisateurs (un sous-réseau différent par script utilisateur). Vous pouvez utiliser un seul serveur DHCP ou un par script utilisateur détecté.
  • Le script « côté utilisateur » attend activement qu’on lui passe un « container » à lancer pour créer à la demande l’interface macvlan, lancer le « container » et lui passer l’interface réseau

Pour communiquer avec les scripts, vous pouvez utiliser au choix :

  • La commande trap du shell (avec un signal SIGUSR1 par exemple)
  • Un tube nommé (voir mkfifo)
  • La commande netcat

Attention aux problématiques de sécurité en fonction de la méthode choisie. On veut aboutir au fonctionnement décrit dans le schéma suivant.

Schéma 2

Indications

  1. Attendre "l'apparition" d'une interface dont le nom commence par le paramètre passé à la fonction
wait_iface() {
    local IFACE="$1"
    local iface
    echo -n "Waiting for interface $IFACE" >&2
    while sleep 1; do
        for iface in $(ip l | grep '^[0-9][0-9]*:' | \
                           cut -f 2 -d : | \
                           cut -f 1 -d '@')
        do
            grep -q "^$1$" <<< "$iface" && echo $iface && return 0;
        done
    done
}
  1. Script dhclient simplifié : dhclient-script-simplifie.sh
#!/bin/bash
case "$reason" in
     PREINIT)
	ip link set dev ${interface} up
     	;;
     BOUND|REBOOT)
	[ -n "$old_ip_address" ] && [ "$old_ip_address" != "$new_ip_address" ] \
	  && ip addr flush dev ${interface} label ${interface}
	[ -z "$old_ip_address" ] || [ "$old_ip_address" != "$new_ip_address" ] \
	  && ip addr add ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
                ${new_broadcast_address:+broadcast $new_broadcast_address} \
                ${new_dhcp_lease_time:+valid_lft $new_dhcp_lease_time} \
                ${new_dhcp_lease_time:+preferred_lft $new_dhcp_lease_time} \
                dev ${interface} label ${interface}
	[ -n "$new_interface_mtu" ] && ip link set dev ${interface} mtu /
	[ -n "$new_routers" ] && ! [ "$reason" == RENEW ] && ! [ "$reason" == REBIND ] \
	  && for router in $new_routers; do
	     	 ip route add default via ${router} dev ${interface} \
		    ${if_metric:+metric $if_metric}
	     done
	[ -n "$new_host_name" ] && hostname $new_host_name
	[ -n "$new_resolv_conf" ] && [ ! -e /etc/resolv.conf ] && \
	  [ -n "$new_domain_name" ] && echo domain ${new_domain_name%% *} > /etc/resolv.conf && \
	    [ -n "$new_domain_name_servers" ] && for dns in $new_domain_name_servers; do \
	        echo nameserver $dns >> /etc/resolv.conf; \
	      done
	;;
      RENEW|REBIND)
	[ -n "$old_ip_address" ] && [ "$old_ip_address" != "$new_ip_address" ] \
          && ip addr flush dev ${interface} label ${interface}
	ip addr change ${new_ip_address}${new_subnet_mask:+/$new_subnet_mask} \
           ${new_broadcast_address:+broadcast $new_broadcast_address} \
           ${new_dhcp_lease_time:+valid_lft $new_dhcp_lease_time} \
           ${new_dhcp_lease_time:+preferred_lft $new_dhcp_lease_time} \
           dev ${interface} label ${interface}
        [ -n "$new_host_name" ] && hostname $new_host_name
	[ ! -e /etc/resolv.conf ] && [ -n "$new_domain_name_servers" ] \
	  && for dns in $new_domain_name_servers; do \
                echo nameserver $dns >> /etc/resolv.conf; \
             done
	;;
      EXPIRE|FAIL|RELEASE|STOP)
         [ -n "$old_ip_address" ] && ip addr flush dev ${interface} label ${interface}
	;;
      *)
        ;;
esac
exit 0