Comment lier un conteneur rootless à un port privilégié sous Linux
L’une des principales innovations introduites par Podman est la capacité de faire fonctionner des contenants sans racines. En termes de sécurité, il s’agissait d’une grande amélioration, car un conteneur potentiellement compromis fonctionnant en tant que root représente une menace pour la sécurité du système hôte. Afin d’obtenir un comportement similaire, les versions récentes de Docker prennent en charge l’exécution du démon docker dans le contexte de l’utilisateur. L’exécution de conteneurs non privilégiés, bien que plus sécurisés, a aussi ses inconvénients, comme l’incapacité de se lier à des ports hôtes privilégiés.
Dans ce tutoriel, nous apprenons comment permettre à un conteneur Docker/Podman sans racine de se lier à un port hôte privilégié sous Linux.
Dans ce tutoriel, vous apprendrez :
- Comment rediriger un port privilégié vers un port non privilégié en créant une règle de pare-feu ou en utilisant redir
- Comment permettre à un conteneur non privilégié de se lier à un port privilégié en définissant la capacité CAP_NET_BIND_SERVICE
- Comment modifier la plage de ports privilégiés
Introduction
docker run -p 80:80 httpd:latesterror while calling PortManager.AddPort(): cannot expose privileged port 80Rediriger un port privilégié vers un port non privilégié à l’aide d’une règle de pare-feu
La première solution possible consiste à rediriger le trafic du port privilégié que nous voulons utiliser, vers un port non privilégié de notre choix, que nous procéderons ensuite à mapper vers le port exposé par le conteneur.
Pour rediriger un port vers un autre, nous pouvons créer une règle de pare-feu. La commande exacte que nous devons exécuter dépend du gestionnaire de pare-feu que nous utilisons. Le composant du noyau Linux qui fournit la fonctionnalité de manipulation des paquets est « netfilter ». Dans les distributions Linux modernes, pour « parler » avec ce composant depuis l’espace utilisateur, nous utilisons le framework « nftables ». Cependant, comme la création directe de règles nftables peut être une tâche fastidieuse, la plupart du temps, nous interagissons avec des gestionnaires de configuration de pare-feu de niveau supérieur. Un exemple de cet utilitaire est le pare-feu.
Création de la règle à l’aide de firewalld
Si vous avez lu notre article d’introduction sur firewalld, vous savez déjà qu’il est géré via l’utilitaire firewall-cmd qui n’a pas de moyen natif de spécifier une règle de redirection de port. Pour ce faire, nous devons nous replier sur une règle « directe », qui nous permet d’interagir avec le pare-feu à un niveau inférieur. Pour définir une règle « directe », nous lançons l’utilitaire avec l’option --direct. En supposant que nous voulions rediriger le trafic du port 80 vers le port 8080 sur localhost, nous exécuterions :
sudo firewall-cmd --direct --add-rule ipv4 nat OUTPUT 0 -p tcp --dport=80 -o lo -j REDIRECT --to-port=8080Deux choses à noter : la première est que la commande ci-dessus ajoute la règle à la zone pare-feu « par défaut », puisque nous n’en avons pas explicitement spécifié une (les zones sont des abstractions liées au pare-feu. Encore une fois, jetez un coup d’œil à notre tutoriel pare-feu si vous voulez en savoir plus à leur sujet). La deuxième, c’est que la règle n’est pas persistante et ne survivrait pas à un redémarrage ou à un redémarrage du service pare-feu. Pour le rendre permanent, nous devons utiliser l’option --permanent et redémarrer le pare-feu en exécutant :
sudo firewall-cmd --reloadCréation de la règle à l’aide des utilitaires iptables ou nft
La syntaxe utilisée pour spécifier les règles directes firewalld, comme vous pouvez le voir ci-dessus, ressemble à celle utilisée par iptables. Si vous (comme moi), préférez, il est possible de définir une règle en utilisant directement les utilitaires iptables/ip6tables. Puisque, dans les distributions Linux modernes, nftables a remplacé le framework iptables, ces utilitaires ne sont plus que des liens vers iptables-nft, un wrapper qui nous permet de continuer à utiliser l’ancienne syntaxe :
sudo iptables -t nat -A OUTPUT -o lo -p tcp --dport=80 -j REDIRECT --to-port=8080Enfin, pour définir la même redirection en utilisant la syntaxe native nftables, nous pouvons utiliser l’utilitaire nft, et exécuter la commande suivante :
sudo nft 'add rule ip nat OUTPUT oifname "lo" tcp dport 80 counter redirect to :8080'Les règles établies avec les utilitaires iptables ou nft de niveau inférieur ne sont pas non plus persistantes. Pour les renforcer à chaque démarrage, nous devons vider l’ensemble de règles actuel et utiliser un outil qui les recharge automatiquement. La stratégie précise varie selon la distribution que nous exploitons, et nous ne l’explorerons pas ici.
Rediriger un portage à l’aide de redir
Une alternative à la création d’une règle de pare-feu consiste à utiliser un outil dédié comme redir, qui est open source, et disponible dans les dépôts par défaut de toutes les principales distributions Linux. Sur Fedora, nous pouvons l’installer avec la commande suivante :
sudo dnf install redirSur Debian et les distributions basées sur Debian, nous pouvons utiliser :
sudo apt install redirRedir est capable de rediriger les connexions TCP venant sur un port local vers une combinaison <adresse>:p autre spécifique. La partie <adresse> est facultative : si elle est omise, l’adresse 0.0.0.0 (méta) est utilisée (elle représente toutes les adresses IPv4 sur la machine). Pour rediriger le port 80 vers le port 8080 en utilisant redir, nous exécuterions :
sudo redir :80 127.0.0.1:8080Les redirections définies avec redir ne sont pas persistantes; Pour qu’ils soient appliqués à chaque démarrage du système, nous devons créer un service qui lance la commande avec les options -n et -s. Nous utilisons le premier pour exécuter l’application au premier plan, et le second pour rediriger les journaux vers syslog. Presque toutes les distributions Linux, de nos jours, utilisent Systemd comme système d’initialisation et gestionnaire de services. Voici un exemple très minimal de ce à quoi pourrait ressembler une unité de service systemd qui lance redir au démarrage :
[Unit]
Description=Redirect tcp port 80 to 8080 with redir
[Service]
ExecStart=/bin/redir -sn :80 127.0.0.1:8080
[Install]
WantedBy=multi-user.target
Nous pouvons enregistrer l’unité sous le nom /etc/systemd/system/redir.service. Pour démarrer et activer le service au démarrage, nous exécutons :
sudo systemctl enable --now redir.serviceMapper le port non privilégié au conteneur
Une fois que nous avons redirigé le trafic portuaire, nous pouvons enfin exécuter le conteneur et le lier au port non privilégié :
docker run --name test_httpd -p 8080:80 httpd:latestDéfinir la capacité CAP_NET_BIND_SERVICE
Une solution de rechange uniquement pour rediriger le trafic de port consiste à définir la capacité CAP_NET_BIND_SERVICE sur le binaire /usr/bin/rootlesskit. Les capacités sont des autorisations spéciales qui peuvent être attribuées aux processus sous Linux. CAP_NET_BIND_SERVICE’est celle qui nous intéresse dans ce cas, puisqu’elle permet de lier un socket à un port privilégié. Pour attribuer la capacité CAP_NET_BIND_SERVICE au binaire rootlesskit, nous exécutons la commande suivante :
sudo setcap cap_net_bind_service=ep /usr/bin/rootlesskitPour supprimer la capacité, :
sudo setcap -r /usr/bin/rootlesskitModification de la portée des ports non privilégiés
La dernière stratégie que nous examinerons ici consiste à modifier l’éventail des ports privilégiés. Pour vérifier quels ports sont considérés comme privilégiés par le système, il suffit de lire le fichier /proc/sys/net/ipv4/ip_unprivileged_port_start : il contient le premier port non privilégié du système (par défaut 1024). Pour s’en tenir à l’exemple précédent, pour que le système considère le port 80 comme non privilégié, nous écririons la valeur appropriée dans le fichier :
echo 80 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_startPour que ce changement soit persistant, nous devons ajouter la ligne suivante à un fichier de configuration dans le répertoire /etc/sysctl.d/ :
echo net.ipv4.ip_unprivileged_port_start = 80 | sudo tee /etc/sysctl.d/90-unprivileged_port_start.confNotez qu’en adoptant cette stratégie, le système considérera tous les ports >= 80 comme non privilégiés!
Conclusions
Dans ce tutoriel, nous avons vu quelques méthodes que nous pouvons utiliser pour laisser un conteneur rootless se lier à un port privilégié. Nous avons vu comment rediriger le trafic d’un port privilégié vers un port non privilégié, comment définir la capacité de CAP_NET_BIND_SERVICE et, enfin, comment modifier la plage de ports privilégiés.
