From 87c8aac5f5fe2d4bdf743b8f1336e5976d885d49 Mon Sep 17 00:00:00 2001 From: Adam Belkacemi Date: Wed, 7 Oct 2026 11:40:34 +0200 Subject: [PATCH] =?UTF-8?q?ajoute=20le=20r=C3=A9seau=20pour=20OpenStack=20?= =?UTF-8?q?:=20dummy0,=20paire=20veth,=20NAT=20restreint=20au=20r=C3=A9sea?= =?UTF-8?q?u=20externe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- ansible/reseau-openstack.yml | 21 +++++ ansible/roles/durcissement/tasks/main.yml | 8 ++ .../durcissement/templates/nftables.conf.j2 | 13 +++ .../roles/reseau-openstack/defaults/main.yml | 16 ++++ .../roles/reseau-openstack/handlers/main.yml | 6 ++ ansible/roles/reseau-openstack/tasks/main.yml | 88 +++++++++++++++++++ .../templates/10-dummy0.netdev.j2 | 3 + .../templates/10-dummy0.network.j2 | 8 ++ .../templates/10-nat-openstack.conf.j2 | 11 +++ .../templates/20-veth-ext.netdev.j2 | 6 ++ .../templates/21-veth-ext-ovs.network.j2 | 10 +++ .../templates/22-veth-ext-gw.network.j2 | 10 +++ .../templates/99-ip-forward.conf.j2 | 3 + docs/journal.md | 44 ++++++++++ 14 files changed, 247 insertions(+) create mode 100644 ansible/reseau-openstack.yml create mode 100644 ansible/roles/reseau-openstack/defaults/main.yml create mode 100644 ansible/roles/reseau-openstack/handlers/main.yml create mode 100644 ansible/roles/reseau-openstack/tasks/main.yml create mode 100644 ansible/roles/reseau-openstack/templates/10-dummy0.netdev.j2 create mode 100644 ansible/roles/reseau-openstack/templates/10-dummy0.network.j2 create mode 100644 ansible/roles/reseau-openstack/templates/10-nat-openstack.conf.j2 create mode 100644 ansible/roles/reseau-openstack/templates/20-veth-ext.netdev.j2 create mode 100644 ansible/roles/reseau-openstack/templates/21-veth-ext-ovs.network.j2 create mode 100644 ansible/roles/reseau-openstack/templates/22-veth-ext-gw.network.j2 create mode 100644 ansible/roles/reseau-openstack/templates/99-ip-forward.conf.j2 diff --git a/ansible/reseau-openstack.yml b/ansible/reseau-openstack.yml new file mode 100644 index 0000000..ba85da1 --- /dev/null +++ b/ansible/reseau-openstack.yml @@ -0,0 +1,21 @@ +--- +# Étape 4 du déploiement Syllabus : réseau pour OpenStack (dummy0, paire veth, +# NAT). Touche le réseau et le pare-feu — à exécuter uniquement après accord +# explicite d'Adam (règle 3). Se termine par un redémarrage, lui aussi +# soumis à son accord explicite. +# +# `reseau-openstack` tourne avant `durcissement` : ce dernier re-rend le +# pare-feu (nftables.conf.j2) en tenant compte du nouveau sous-réseau externe +# OpenStack (variables ci-dessous), et son filet de sécurité anti-coupure +# protège donc aussi ce changement-ci. +- name: Réseau pour OpenStack + hosts: syllabus + become: true + gather_facts: true + vars: + reseau_veth_gw: veth-ext-gw + reseau_veth_gw_subnet: 172.24.4.0/24 + reseau_interface_publique: eno1 + roles: + - reseau-openstack + - durcissement diff --git a/ansible/roles/durcissement/tasks/main.yml b/ansible/roles/durcissement/tasks/main.yml index 363ab14..0a31290 100644 --- a/ansible/roles/durcissement/tasks/main.yml +++ b/ansible/roles/durcissement/tasks/main.yml @@ -55,6 +55,14 @@ mode: "0644" notify: redémarrer fail2ban +- name: créer le répertoire des règles nftables additionnelles + ansible.builtin.file: + path: /etc/nftables.d + state: directory + owner: root + group: root + mode: "0750" + - name: déployer le script de retour à l'état d'avant durcissement ansible.builtin.template: src: durcissement-rollback.sh.j2 diff --git a/ansible/roles/durcissement/templates/nftables.conf.j2 b/ansible/roles/durcissement/templates/nftables.conf.j2 index c76bebb..221e624 100644 --- a/ansible/roles/durcissement/templates/nftables.conf.j2 +++ b/ansible/roles/durcissement/templates/nftables.conf.j2 @@ -21,9 +21,22 @@ table inet filter { chain forward { type filter hook forward priority 0; policy drop; + +{% if reseau_veth_gw_subnet is defined %} + # Un seul sens autorisé : réseau externe OpenStack -> Internet (NAT), + # plus les réponses à ce trafic. Rien n'entre depuis Internet vers les + # réseaux internes ; tout le reste est refusé par la politique drop. + iifname "{{ reseau_veth_gw }}" oifname "{{ reseau_interface_publique }}" ip saddr {{ reseau_veth_gw_subnet }} accept + iifname "{{ reseau_interface_publique }}" oifname "{{ reseau_veth_gw }}" ct state established,related accept +{% endif %} } chain output { type filter hook output priority 0; policy accept; } } + +# Autres règles (NAT, etc.), déposées par d'autres rôles. Un glob sans fichier +# correspondant n'est pas une erreur pour nft : cette ligne ne casse rien tant +# que /etc/nftables.d/ est vide. +include "/etc/nftables.d/*.conf" diff --git a/ansible/roles/reseau-openstack/defaults/main.yml b/ansible/roles/reseau-openstack/defaults/main.yml new file mode 100644 index 0000000..e5eec0a --- /dev/null +++ b/ansible/roles/reseau-openstack/defaults/main.yml @@ -0,0 +1,16 @@ +--- +# Réseau privé pour Kolla (interface de management / VIP interne, pas de HAProxy). +reseau_dummy_nom: dummy0 +reseau_dummy_ip: 10.10.0.1/24 + +# Paire veth : un bout ira à Neutron (neutron_external_interface, étape 5), +# l'autre reste dans le réseau racine et porte la passerelle du réseau +# "externe" OpenStack (celui des routeurs de VM, pas les VM elles-mêmes). +reseau_veth_ovs: veth-ext-ovs +reseau_veth_gw: veth-ext-gw +reseau_veth_gw_ip: 172.24.4.1/24 +reseau_veth_gw_subnet: 172.24.4.0/24 + +reseau_interface_publique: eno1 + +durcissement_delai_filet_secondes: 300 diff --git a/ansible/roles/reseau-openstack/handlers/main.yml b/ansible/roles/reseau-openstack/handlers/main.yml new file mode 100644 index 0000000..e2fe428 --- /dev/null +++ b/ansible/roles/reseau-openstack/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: redémarrer systemd-networkd + ansible.builtin.systemd: + name: systemd-networkd + state: restarted + enabled: true diff --git a/ansible/roles/reseau-openstack/tasks/main.yml b/ansible/roles/reseau-openstack/tasks/main.yml new file mode 100644 index 0000000..50483ba --- /dev/null +++ b/ansible/roles/reseau-openstack/tasks/main.yml @@ -0,0 +1,88 @@ +--- +# Étape 4 : réseau pour OpenStack. N'est jamais exécuté seul : ce rôle tourne +# dans le même play que `durcissement` (voir ansible/reseau-openstack.yml), +# pour que le pare-feu soit re-rendu avec la règle qui autorise ce nouveau +# sous-réseau — et pour réutiliser son filet de sécurité anti-coupure. + +- name: charger le module noyau dummy au démarrage + ansible.builtin.copy: + content: "dummy\n" + dest: /etc/modules-load.d/reseau-openstack.conf + mode: "0644" + +- name: charger le module dummy maintenant (sans attendre le prochain redémarrage) + community.general.modprobe: + name: dummy + state: present + +- name: déployer l'interface dummy0 (management interne Kolla) + ansible.builtin.template: + src: 10-dummy0.netdev.j2 + dest: /etc/systemd/network/10-dummy0.netdev + mode: "0644" + notify: redémarrer systemd-networkd + +- name: déployer le réseau de dummy0 + ansible.builtin.template: + src: 10-dummy0.network.j2 + dest: /etc/systemd/network/10-dummy0.network + mode: "0644" + notify: redémarrer systemd-networkd + +- name: déployer la paire veth (externe OpenStack) + ansible.builtin.template: + src: 20-veth-ext.netdev.j2 + dest: /etc/systemd/network/20-veth-ext.netdev + mode: "0644" + notify: redémarrer systemd-networkd + +- name: déployer le réseau du bout OVS de la paire veth (pas d'IP, pour Neutron) + ansible.builtin.template: + src: 21-veth-ext-ovs.network.j2 + dest: /etc/systemd/network/21-veth-ext-ovs.network + mode: "0644" + notify: redémarrer systemd-networkd + +- name: déployer le réseau du bout passerelle de la paire veth + ansible.builtin.template: + src: 22-veth-ext-gw.network.j2 + dest: /etc/systemd/network/22-veth-ext-gw.network + mode: "0644" + notify: redémarrer systemd-networkd + +- name: "activer systemd-networkd (ne gère que les interfaces déclarées, eno1 reste sous ifupdown)" + ansible.builtin.systemd: + name: systemd-networkd + enabled: true + # Pas de state ici : le handler s'occupe du (re)démarrage une fois tous les + # fichiers en place, pour n'appliquer qu'une seule fois. + +- name: appliquer les nouvelles interfaces réseau + ansible.builtin.meta: flush_handlers + +- name: activer le routage IPv4 (nécessaire au NAT) + ansible.posix.sysctl: + name: net.ipv4.ip_forward + value: "1" + sysctl_file: /etc/sysctl.d/99-reseau-openstack.conf + state: present + reload: true + +- name: créer le répertoire des règles nftables additionnelles + ansible.builtin.file: + path: /etc/nftables.d + state: directory + owner: root + group: root + mode: "0750" + +- name: déployer le NAT masquerade pour le réseau externe OpenStack + ansible.builtin.template: + src: 10-nat-openstack.conf.j2 + dest: /etc/nftables.d/10-nat-openstack.conf + mode: "0640" + owner: root + group: root + # Handler défini dans le rôle durcissement (même play) : nft relit le fichier + # principal ET ses inclusions en une seule recharge. + notify: recharger nftables diff --git a/ansible/roles/reseau-openstack/templates/10-dummy0.netdev.j2 b/ansible/roles/reseau-openstack/templates/10-dummy0.netdev.j2 new file mode 100644 index 0000000..5e4ecef --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/10-dummy0.netdev.j2 @@ -0,0 +1,3 @@ +[NetDev] +Name={{ reseau_dummy_nom }} +Kind=dummy diff --git a/ansible/roles/reseau-openstack/templates/10-dummy0.network.j2 b/ansible/roles/reseau-openstack/templates/10-dummy0.network.j2 new file mode 100644 index 0000000..35930f2 --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/10-dummy0.network.j2 @@ -0,0 +1,8 @@ +[Match] +Name={{ reseau_dummy_nom }} + +[Network] +Address={{ reseau_dummy_ip }} +# Pas de route par défaut ni de DNS ici : cette interface sert uniquement de +# point d'ancrage interne pour Kolla (network_interface, kolla_internal_vip_address). +DHCP=no diff --git a/ansible/roles/reseau-openstack/templates/10-nat-openstack.conf.j2 b/ansible/roles/reseau-openstack/templates/10-nat-openstack.conf.j2 new file mode 100644 index 0000000..270ae31 --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/10-nat-openstack.conf.j2 @@ -0,0 +1,11 @@ +# Géré par Ansible (rôle reseau-openstack) — ne pas modifier à la main. +# NAT masquerade : seul le sous-réseau "externe" OpenStack ({{ reseau_veth_gw_subnet }}, +# utilisé par les routeurs de VM qui en ont explicitement besoin, jamais les +# VM de lab elles-mêmes — voir étape 10 du prompt de déploiement) sort vers +# Internet via {{ reseau_interface_publique }}. +table ip nat { + chain postrouting { + type nat hook postrouting priority 100; + ip saddr {{ reseau_veth_gw_subnet }} oifname "{{ reseau_interface_publique }}" masquerade + } +} diff --git a/ansible/roles/reseau-openstack/templates/20-veth-ext.netdev.j2 b/ansible/roles/reseau-openstack/templates/20-veth-ext.netdev.j2 new file mode 100644 index 0000000..1cf11ae --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/20-veth-ext.netdev.j2 @@ -0,0 +1,6 @@ +[NetDev] +Name={{ reseau_veth_ovs }} +Kind=veth + +[Peer] +Name={{ reseau_veth_gw }} diff --git a/ansible/roles/reseau-openstack/templates/21-veth-ext-ovs.network.j2 b/ansible/roles/reseau-openstack/templates/21-veth-ext-ovs.network.j2 new file mode 100644 index 0000000..b9708c0 --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/21-veth-ext-ovs.network.j2 @@ -0,0 +1,10 @@ +[Match] +Name={{ reseau_veth_ovs }} + +[Network] +# Pas d'IP : ce bout sera donné à Neutron (neutron_external_interface) à +# l'étape 5, qui l'attachera lui-même à son bridge OVS/OVN. On se contente de +# le faire monter et de le laisser persister au redémarrage. +DHCP=no +LinkLocalAddressing=no +ConfigureWithoutCarrier=yes diff --git a/ansible/roles/reseau-openstack/templates/22-veth-ext-gw.network.j2 b/ansible/roles/reseau-openstack/templates/22-veth-ext-gw.network.j2 new file mode 100644 index 0000000..9cd6ab6 --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/22-veth-ext-gw.network.j2 @@ -0,0 +1,10 @@ +[Match] +Name={{ reseau_veth_gw }} + +[Network] +Address={{ reseau_veth_gw_ip }} +DHCP=no +# Passerelle du réseau "externe" OpenStack (celui des routeurs de VM). Pas de +# route par défaut ajoutée ici : c'est le NAT masquerade nftables qui fait +# sortir ce sous-réseau vers Internet via {{ reseau_interface_publique }}, +# pas une route système. diff --git a/ansible/roles/reseau-openstack/templates/99-ip-forward.conf.j2 b/ansible/roles/reseau-openstack/templates/99-ip-forward.conf.j2 new file mode 100644 index 0000000..3ac5fd1 --- /dev/null +++ b/ansible/roles/reseau-openstack/templates/99-ip-forward.conf.j2 @@ -0,0 +1,3 @@ +# Géré par Ansible (rôle reseau-openstack) — ne pas modifier à la main. +# Nécessaire pour que le NAT masquerade du réseau externe OpenStack fonctionne. +net.ipv4.ip_forward = 1 diff --git a/docs/journal.md b/docs/journal.md index 8b82e02..5bca60a 100644 --- a/docs/journal.md +++ b/docs/journal.md @@ -105,3 +105,47 @@ Chiffrer avant d'envoyer (plutôt que de compter sur un transport chiffré) a un **Suite** Étape 4 : réseau pour OpenStack (`dummy0`, paire veth, NAT). Touche le réseau → accord explicite d'Adam obligatoire avant d'appliquer, filet anti-coupure, et redémarrage du serveur seulement avec son accord. + +## 2026-10-07 — Étape 4 : réseau pour OpenStack + +**Fait** + +- Rôle Ansible `reseau-openstack` + playbook `ansible/reseau-openstack.yml` (qui rejoue aussi `durcissement` pour le pare-feu et son filet anti-coupure). Appliqué après accord explicite d'Adam, puis redémarrage avec son accord. +- `dummy0` : 10.10.0.1/24, futur `network_interface` / `kolla_internal_vip_address` (Kolla sans HAProxy). +- Paire veth : `veth-ext-ovs` (sans IP, futur `neutron_external_interface`) ↔ `veth-ext-gw` (172.24.4.1/24, passerelle du réseau externe OpenStack). +- Persistance par `systemd-networkd`, limité à ces trois interfaces : `eno1` reste sous `ifupdown`, inchangé. +- `net.ipv4.ip_forward=1` (`/etc/sysctl.d/99-reseau-openstack.conf`). +- nftables : NAT masquerade de 172.24.4.0/24 vers `eno1` (`/etc/nftables.d/10-nat-openstack.conf`, inclus depuis `/etc/nftables.conf`). Chaîne `forward` resserrée à la demande d'Adam : seulement `veth-ext-gw → eno1` depuis 172.24.4.0/24, et les réponses `eno1 → veth-ext-gw` (`established,related`). Tout le reste est refusé ; rien n'entre depuis Internet vers les réseaux internes. + +**Règles nftables finales (relues après redémarrage)** + +``` +chain forward { policy drop; + iifname "veth-ext-gw" oifname "eno1" ip saddr 172.24.4.0/24 accept + iifname "eno1" oifname "veth-ext-gw" ct state established,related accept +} +table ip nat { chain postrouting { + ip saddr 172.24.4.0/24 oifname "eno1" masquerade +} } +``` + +**Problèmes rencontrés et solution** + +- 1er lancement : `Destination directory /etc/nftables.d does not exist`. Le dossier était créé par `durcissement`, qui tourne après `reseau-openstack`. Échec survenu avant toute modification du pare-feu. Corrigé en créant aussi le dossier dans `reseau-openstack`. +- Un lancement a été bloqué par le garde-fou de Claude Code parce que la règle `forward` avait été modifiée après l'accord d'Adam sans lui être remontrée. On a fait une simulation `--check --diff`, Adam a validé le diff, puis on a appliqué. +- Table `f2b-table` de fail2ban absente après rechargement du pare-feu et après redémarrage : c'est normal, fail2ban 1.0 ne la crée qu'au premier bannissement. Point encore à vérifier : `flush ruleset` à chaque rechargement efface une table de bannissement déjà créée. fail2ban devrait la recréer au bannissement suivant (vérification automatique de ses actions), mais ce n'est pas encore testé. + +**Critère vérifié (après redémarrage à 11:37, SSH revenu à 11:39)** + +- SSH répond, `dummy0` et la paire veth sont présents avec leurs IP, `ip_forward=1`. +- Internet joignable depuis le serveur (ping 9.9.9.9, résolution de `deb.debian.org`), route par défaut inchangée. +- Services actifs : ssh, nftables, fail2ban, chrony, systemd-networkd, timer de sauvegarde. RAID `[UU]` sur `md0` et `md1`. +- `nmap` depuis le Mac : seul le port 22 est ouvert. + +**Comprendre** + +OpenStack a besoin de deux réseaux en plus de l'accès public : un réseau interne pour que ses services se parlent, et un réseau « externe » pour que les routeurs virtuels puissent sortir. Sur un serveur qui n'a qu'une seule IP publique, mettre Neutron directement sur `eno1` reviendrait à lui confier notre seule porte d'entrée. On a donc créé des interfaces virtuelles : `dummy0`, une carte réseau factice qui porte une adresse privée stable pour les services, et une paire veth, qui fonctionne comme un câble virtuel dont un bout ira à Neutron et l'autre sert de passerelle. Le NAT fait sortir ce réseau externe vers Internet en se faisant passer pour l'IP publique. Le pare-feu n'autorise que ce sens-là, plus les réponses, si bien que personne ne peut entrer depuis Internet vers les réseaux internes. Le redémarrage prouve que tout cela est persistant, et pas seulement appliqué en mémoire. + +**Suite** + +Étape 5 : Kolla-Ansible 2025.2 tout-en-un. `bootstrap-servers` peut toucher au pare-feu et à Docker : vérifier avant, rejouer `durcissement` après, et demander l'accord d'Adam pour ce qui touche au pare-feu.