Comment implémenter un modèle de pare-feu de base avec Iptables sur Ubuntu 20.04
Introduction
La mise en place d'un pare-feu est une étape importante dans la sécurisation de votre serveur. Une grande partie de cela consiste à décider des règles et politiques individuelles qui appliqueront les restrictions de trafic sur votre réseau. Les pare-feu comme iptables
vous permettent également d'avoir votre mot à dire sur le cadre structurel dans lequel vos règles sont appliquées.
Dans ce guide, vous apprendrez à construire un pare-feu pouvant servir de base à des ensembles de règles plus complexes. Ce pare-feu se concentrera principalement sur la fourniture de valeurs par défaut raisonnables et sur l'établissement d'un cadre qui encourage l'extensibilité.
Conditions préalables
Pour terminer ce didacticiel, vous aurez besoin d'accéder à un serveur Ubuntu 20.04 avec un utilisateur non root configuré avec les privilèges sudo
. Vous pouvez le faire en suivant toutes les étapes décrites dans notre Guide de configuration initiale du serveur Ubuntu 20.04, à l'exception de Étape 4, car nous allons configurer le pare-feu dans ce didacticiel.
De plus, nous vous recommandons de revoir les stratégies de pare-feu que vous souhaitez mettre en œuvre. Vous pouvez suivre ce guide pour avoir une meilleure idée de ce qu'il faut prendre en compte.
Installation du service de pare-feu persistant
Commencez par mettre à jour le cache de packages local :
sudo apt update
Installez maintenant le package iptables-persistent
. Cela vous permet d'enregistrer vos ensembles de règles et de les appliquer automatiquement au démarrage :
sudo apt install iptables-persistent
Lors de l'installation, il vous sera demandé si vous souhaitez enregistrer vos règles actuelles, sélectionnez' . Veuillez noter que vous exécuterez la commande netfilter-persistent
pour exécuter le service de pare-feu persistant iptables
. Ensuite, vous allez modifier les fichiers de règles générés.
Remarque sur IPv6 dans ce guide
Avant de commencer, nous allons brièvement discuter d'IPv4 et d'IPv6. La commande iptables
gère uniquement le trafic IPv4. Pour le trafic IPv6, un outil complémentaire distinct appelé ip6tables
est utilisé. Les règles sont stockées dans des tables et des chaînes distinctes. Pour la commande netfilter-persistent
, les règles IPv4 sont écrites et lues à partir de /etc/iptables/rules.v4
, et les règles IPv6 sont stockées dans /etc/iptables/rules.v6
.
Ce guide suppose que vous pas utilisez activement IPv6 sur votre serveur. Si vos services ne tirent pas parti d'IPv6, il est plus sûr de bloquer complètement l'accès, comme cela sera démontré dans ce guide.
Implémentation de la stratégie de pare-feu de base (méthode rapide)
Afin d'être opérationnel le plus rapidement possible, nous vous montrerons comment modifier directement le fichier de règles et copier et coller la stratégie de pare-feu terminée. Ensuite, nous expliquerons la stratégie générale et comment ces règles pourraient être implémentées en utilisant la commande iptables
au lieu de modifier le fichier.
Pour mettre en œuvre la stratégie et le cadre de pare-feu, vous allez modifier les fichiers /etc/iptables/rules.v4
et /etc/iptables/rules.v6
. Ouvrez le fichier rules.v4
dans votre éditeur de texte préféré. Ici, nous utiliserons nano
:
sudo nano /etc/iptables/rules.v4
À l'intérieur, le fichier contiendra le contenu suivant :
/etc/iptables/rules.v4
# Generated by iptables-save v1.8.4 on Tue Mar 1 19:03:10 2022 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT # Completed on Tue Mar 1 19:03:10 2022
Supprimez ces contenus et remplacez-les par ce qui suit :
/etc/iptables/rules.v4
*filter # Allow all outgoing, but drop incoming and forwarding packets by default :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] # Custom per-protocol chains :UDP - [0:0] :TCP - [0:0] :ICMP - [0:0] # Acceptable UDP traffic # Acceptable TCP traffic -A TCP -p tcp --dport 22 -j ACCEPT # Acceptable ICMP traffic # Boilerplate acceptance policy -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -A INPUT -i lo -j ACCEPT # Drop invalid packets -A INPUT -m conntrack --ctstate INVALID -j DROP # Pass traffic to protocol-specific chains ## Only allow new connections (established and related should already be handled) ## For TCP, additionally only allow new SYN packets since that is the only valid ## method for establishing a new TCP connection -A INPUT -p udp -m conntrack --ctstate NEW -j UDP -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP # Reject anything that's fallen through to this point ## Try to be protocol-specific w/ rejection message -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable -A INPUT -p tcp -j REJECT --reject-with tcp-reset -A INPUT -j REJECT --reject-with icmp-proto-unreachable # Commit the changes COMMIT *raw :PREROUTING ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT *nat :PREROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] COMMIT *security :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT *mangle :PREROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] COMMIT
Enregistrez et fermez le fichier. Si vous utilisez nano
, vous pouvez le faire en appuyant sur CTRL + X
, puis sur Y
et ENTER
.
Vous pouvez tester le fichier pour les erreurs de syntaxe en exécutant la commande suivante. Assurez-vous de corriger les erreurs de syntaxe si vous en recevez :
sudo iptables-restore -t /etc/iptables/rules.v4
Ouvrez ensuite le fichier /etc/iptables/rules.v6
pour modifier les règles IPv6 :
sudo nano /etc/iptables/rules.v6
Ce fichier aura le contenu suivant :
/etc/iptables/rules.v6
# Generated by ip6tables-save v1.8.4 on Tue Mar 1 19:03:10 2022 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT # Completed on Tue Mar 1 19:03:10 2022
Vous pouvez bloquer tout le trafic IPv6 en remplaçant le contenu du fichier par la configuration suivante :
/etc/iptables/rules.v6
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] COMMIT *raw :PREROUTING DROP [0:0] :OUTPUT DROP [0:0] COMMIT *nat :PREROUTING DROP [0:0] :INPUT DROP [0:0] :OUTPUT DROP [0:0] :POSTROUTING DROP [0:0] COMMIT *security :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] COMMIT *mangle :PREROUTING DROP [0:0] :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT DROP [0:0] :POSTROUTING DROP [0:0] COMMIT
Enregistrez et fermez le fichier.
Pour tester ce fichier pour les erreurs de syntaxe, utilisez la commande ip6tables-restore
avec l'option -t
:
sudo ip6tables-restore -t /etc/iptables/rules.v6
Lorsque les deux fichiers de règles ne signalent aucune erreur de syntaxe, vous pouvez appliquer les règles que vous avez définies en exécutant :
sudo service netfilter-persistent reload
Output * Loading netfilter rules... run-parts: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables start run-parts: executing /usr/share/netfilter-persistent/plugins.d/25-ip6tables start [ OK ]
Cela mettra immédiatement en œuvre la politique décrite dans vos fichiers. Vous pouvez le vérifier en répertoriant les règles iptables
actuellement utilisées. Vérifiez d'abord IPv4 :
sudo iptables -S
Output-P INPUT DROP -P FORWARD DROP -P OUTPUT ACCEPT -N ICMP -N TCP -N UDP -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate INVALID -j DROP -A INPUT -p udp -m conntrack --ctstate NEW -j UDP -A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable -A INPUT -p tcp -j REJECT --reject-with tcp-reset -A INPUT -j REJECT --reject-with icmp-proto-unreachable -A TCP -p tcp -m tcp --dport 22 -j ACCEPT
Vérifiez ensuite les règles IPv6 actuelles :
sudo ip6tables -S
Output-P INPUT DROP -P FORWARD DROP -P OUTPUT DROP
Ces règles de pare-feu seront réappliquées à chaque démarrage. Testez-le pour vous assurer que vous pouvez toujours vous connecter et que tous les autres accès sont bloqués.
Une explication de notre stratégie générale de pare-feu
Dans le pare-feu de base construit avec les règles de la section précédente, nous avons créé un cadre extensible qui peut être ajusté pour ajouter ou supprimer des règles. Pour le trafic IPv4, nous nous intéressons principalement à la chaîne INPUT
dans la table filter
. Cette chaîne traitera tous les paquets destinés à notre serveur. Nous avons également autorisé tout le trafic sortant et refusé tout transfert de paquets, ce qui ne serait approprié que si ce serveur agissait comme un routeur pour d'autres hôtes. Nous acceptons les paquets dans toutes les autres tables car nous ne voulons filtrer que les paquets dans ce guide.
En général, nos règles configurent un pare-feu qui refusera le trafic entrant par défaut. Nous créons ensuite des exceptions pour les services et les types de trafic que nous souhaitons exclure de cette politique.
Dans la chaîne principale INPUT
, nous avons ajouté des règles génériques pour le trafic dont nous sommes convaincus qu'elles seront toujours traitées de la même manière. Par exemple, nous voulons toujours refuser les paquets jugés « invalides » et nous voulons toujours autoriser le trafic sur l'interface de bouclage locale et les données associées à une connexion établie.
Ensuite, nous faisons correspondre le trafic en fonction du protocole qu'il utilise et le mélangeons dans une chaîne spécifique au protocole. Ces chaînes spécifiques au protocole sont destinées à contenir des règles qui correspondent et autorisent le trafic pour des services spécifiques. Dans cet exemple, le seul service que nous autorisons est SSH dans notre chaîne TCP
. Si nous offrions un autre service, comme un serveur HTTP(S), nous pourrions également ajouter des exceptions ici. Ces chaînes seront au centre de la plupart de vos personnalisations.
Tout trafic qui ne correspond pas aux règles génériques ou aux règles de service dans les chaînes spécifiques au protocole est traité par les dernières règles de la chaîne INPUT
. Nous avons défini la politique par défaut sur DROP
pour notre pare-feu, qui refusera les paquets qui ne respectent pas nos règles. Cependant, les règles à la fin de la chaîne INPUT
rejettent les paquets et envoient un message au client qui imite la façon dont le serveur répondrait s'il n'y avait pas de service en cours d'exécution sur ce port.
Pour le trafic IPv6, nous supprimons tout le trafic. Notre serveur n'utilise pas ce protocole, il est donc plus sûr de ne pas interagir du tout avec le trafic.
Implémentation de vos pare-feu à l'aide de la commande iptables
Maintenant que vous comprenez l'idée générale derrière la stratégie que nous avons construite, nous allons discuter de la façon dont vous pourriez créer ces règles à l'aide des commandes iptables
. Nous nous retrouverons avec les mêmes règles que nous avons spécifiées ci-dessus mais nous créerons nos politiques en ajoutant des règles de manière itérative. Parce que iptables
applique chacune des règles immédiatement, l'ordre des règles est très important (par exemple, nous laissons les règles qui refusent les paquets jusqu'à la fin).
Réinitialiser votre pare-feu
Commencez par réinitialiser vos règles de pare-feu afin de pouvoir examiner comment les stratégies peuvent être créées à partir de la ligne de commande. Videz toutes vos règles en exécutant ce qui suit :
sudo service netfilter-persistent flush
Vérifiez maintenant que vos règles sont réinitialisées :
sudo iptables -S
Vous devriez avoir une sortie indiquant que les règles de la table filter
ont disparu et que la stratégie par défaut est définie sur ACCEPT
sur toutes les chaînes :
Output-P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPT
Création de chaînes spécifiques au protocole
Ensuite, vous allez créer toutes vos chaînes spécifiques au protocole. Ceux-ci seront utilisés pour contenir les règles qui créent des exceptions à votre politique de refus pour les services que vous souhaitez exposer. Vous allez en créer un pour le trafic UDP
:
sudo iptables -N UDP
Puis un autre pour TCP
:
sudo iptables -N TCP
Et un de plus pour ICMP
:
sudo iptables -N ICMP
Ensuite, ajoutez l'exception pour le trafic SSH. SSH utilise TCP, vous allez donc ajouter une règle pour accepter le trafic TCP
destiné au port 22
à la chaîne TCP :
sudo iptables -A TCP -p tcp --dport 22 -j ACCEPT
Si vous souhaitez ajouter des services TCP supplémentaires, vous pouvez le faire maintenant en répétant la commande avec le numéro de port remplacé.
Création de règles d'acceptation et de refus à usage général
Dans la chaîne INPUT
, où tout le trafic entrant commence à filtrer, nous devons ajouter nos règles à usage général. Ce sont quelques règles de bon sens qui définissent la base de notre pare-feu en acceptant le trafic à faible risque (trafic local et trafic associé aux connexions que nous avons déjà vérifiées) et en supprimant le trafic qui n'est clairement pas utile (paquets invalides).
Tout d'abord, créez une exception pour accepter tout le trafic faisant partie d'une connexion établie ou lié à une connexion établie :
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Cette règle utilise l'extension conntrack
, qui fournit un suivi interne afin que iptables
ait le contexte dont il a besoin pour évaluer les paquets dans le cadre de connexions plus importantes au lieu d'un flux de paquets discrets et non liés. TCP est un protocole basé sur la connexion, donc une connexion établie est assez bien définie. Pour UDP et d'autres protocoles sans connexion, les connexions établies font référence au trafic qui a vu une réponse (la source du paquet d'origine sera la destination du paquet de réponse, et vice versa). Une connexion associée fait référence à une nouvelle connexion qui a été initiée en association avec une connexion existante. L'exemple classique ici est une connexion de transfert de données FTP, qui serait liée à la connexion de contrôle FTP qui a déjà été établie.
Vous voudrez également autoriser tout le trafic provenant de l'interface de bouclage local. Il s'agit du trafic généré par le serveur et destiné au serveur. Il est utilisé par les services sur l'hôte pour communiquer entre eux :
sudo iptables -A INPUT -i lo -j ACCEPT
Enfin, refusez tous les paquets invalides. Les paquets peuvent être invalides pour un certain nombre de raisons. Ils peuvent faire référence à des connexions qui n'existent pas, ils peuvent être destinés à des interfaces, des adresses ou des ports qui n'existent pas, ou ils peuvent être malformés. Dans tous les cas, vous supprimerez tous les paquets non valides car il n'existe aucun moyen approprié de les gérer et car ils pourraient représenter une activité malveillante :
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
Création des règles de saut vers les chaînes spécifiques au protocole
Jusqu'à présent, nous avons créé des règles générales dans la chaîne INPUT
et des règles pour des services acceptables spécifiques au sein de nos chaînes spécifiques au protocole. Cependant, à l'heure actuelle, le trafic entre dans la chaîne INPUT
et n'a aucun moyen d'atteindre nos chaînes spécifiques au protocole.
Vous devez maintenant diriger le trafic de la chaîne INPUT
vers les chaînes spécifiques au protocole appropriées. Vous pouvez faire correspondre le type de protocole pour l'envoyer à la bonne chaîne. Assurez-vous également que le paquet représente une nouvelle connexion (toutes les connexions établies ou associées doivent déjà être traitées plus tôt). Commencez avec le trafic UDP
:
sudo iptables -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
Ensuite, exécutez la commande suivante pour le trafic TCP
. Veuillez noter qu'avec les paquets TCP, vous ajouterez l'exigence supplémentaire que le paquet soit un paquet SYN, qui est le seul type valide pour démarrer une connexion TCP :
sudo iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
Exécutez ensuite ce qui suit pour le trafic ICMP
:
sudo iptables -A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
Rejeter tout le trafic restant
Si un paquet qui a été transmis à une chaîne spécifique au protocole ne correspondait à aucune des règles qu'il contient, le contrôle sera renvoyé à la chaîne INPUT
. Tout ce qui atteint ce point ne devrait pas être autorisé par votre pare-feu.
Vous refuserez le trafic à l'aide de la cible REJECT
, qui envoie un message de réponse au client. Cela vous permet de spécifier la messagerie sortante afin que vous puissiez imiter la réponse qui serait donnée si le client essayait d'envoyer des paquets à un port fermé normal. La réponse dépend du protocole utilisé par le client.
Tenter d'atteindre un port UDP
fermé entraînera un message ICMP
indiquant « port inaccessible ». Vous pouvez imiter cela en exécutant ce qui suit :
sudo iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
Tenter d'établir une connexion TCP
sur un port fermé entraîne une réponse TCP RST :
sudo iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
Pour tous les autres paquets, vous pouvez envoyer un message ICMP
« protocole inaccessible » pour indiquer que le serveur ne répond pas aux paquets de ce type :
sudo iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable
Ajustement des stratégies par défaut
Les trois dernières règles que vous avez ajoutées doivent gérer tout le trafic restant dans la chaîne INPUT
. Cependant, vous devez définir la stratégie par défaut sur DROP
par précaution, comme dans ce qui suit :
sudo iptables -P INPUT DROP
Vous devez également définir cette politique dans la chaîne FORWARD
si ce serveur n'est pas configuré en tant que routeur vers d'autres machines :
sudo iptables -P FORWARD DROP
Avertissement : Avec votre politique définie sur DROP
, si vous effacez votre iptables
avec sudo iptables -F
, votre connexion SSH actuelle sera abandonnée ! Rincer avec sudo netfilter-persistent flush
est un meilleur moyen d'effacer les règles car il réinitialisera également la politique par défaut.
Pour respecter votre politique IPv6 de suppression de tout le trafic, vous pouvez utiliser les commandes ip6tables
suivantes, en commençant par INPUT
:
sudo ip6tables -P INPUT DROP
Exécutez ensuite ce qui suit pour FORWARD
:
sudo ip6tables -P FORWARD DROP
Terminez en définissant la stratégie pour OUTPUT
:
sudo ip6tables -P OUTPUT DROP
Cela devrait reproduire assez fidèlement vos règles.
Enregistrement des règles iptables
À ce stade, vous devez tester vos règles de pare-feu et vous assurer qu'elles bloquent le trafic que vous souhaitez empêcher sans entraver votre accès normal. Une fois que vous êtes convaincu que vos règles se comportent correctement, vous pouvez les enregistrer afin qu'elles soient automatiquement appliquées à votre système au démarrage.
Enregistrez vos règles actuelles (à la fois IPv4 et IPv6) en exécutant ce qui suit :
sudo service netfilter-persistent save
Cela écrasera vos fichiers /etc/iptables/rules.v4
et /etc/iptables/rules.v6
avec les politiques que vous avez créées sur la ligne de commande.
Conclusion
En suivant ce guide, soit en collant vos règles de pare-feu directement dans les fichiers de configuration, soit en les appliquant manuellement et en les enregistrant sur la ligne de commande, vous avez créé une bonne configuration de pare-feu de départ. Vous devrez ajouter les règles individuelles pour autoriser l'accès aux services que vous souhaitez mettre à disposition.
Le cadre établi dans ce guide devrait vous permettre de faire des ajustements et peut aider à clarifier vos politiques existantes. Consultez certains de nos autres guides sur la façon de créer votre politique de pare-feu avec certains services populaires :