diff --git a/flux-genere/forge-01.nft b/flux-genere/forge-01.nft new file mode 100644 index 0000000..aec18fd --- /dev/null +++ b/flux-genere/forge-01.nft @@ -0,0 +1,46 @@ +#!/usr/sbin/nft -f +# GENERE par scripts/resoudre_flux.py depuis les meta/flux.yml — hote forge-01. +# Deploye par le role `nftables_baseline` en tant que /etc/nftables.conf. + +# Remplace UNIQUEMENT les tables setops (pas de `flush ruleset` : préserve les +# tables etrangeres — DNAT/forward de conteneurs, par exemple). +# +# `setops_filter` est le gabarit de REPLI, pose quand aucun ruleset resolu +# n'existe encore. Les deux fichiers sont des ALTERNATIVES, jamais des couches — +# mais `nft -f` AJOUTE sans purger, et le repli survivait donc a la bascule. +# Deux chaines `input` sur le meme hook, toutes deux en `policy drop` : le +# paquet traverse les DEUX, et seule l'intersection de leurs `accept` passait. +# Mesure du 2026-08-10 sur Technolibre : l'AC etait debout, sa regle 8443 posee +# et acceptante, l'ICMP passait en 0,15 ms — et le TCP expirait, arrete par le +# `policy drop` du repli. Chaque fichier retire donc desormais la table de +# l'autre, et la bascule converge dans les deux sens. +table inet setops_flux {} +delete table inet setops_flux +table inet setops_filter {} +delete table inet setops_filter + +table inet setops_flux { + chain input { + type filter hook input priority 0; policy drop; + + iif "lo" accept + ct state established,related accept + ct state invalid drop + ip protocol icmp accept + ip6 nexthdr icmpv6 accept + + ip saddr { 10.17.0.0/24 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 192.168.255.2/32 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 22 accept # serveur_debian: Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). + icmp type destination-unreachable icmp code frag-needed accept # serveur_debian: ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. + ip saddr { 10.29.16.11 } tcp dport 3000 accept # serveur_forgejo: Interface web + Git HTTP servis via l'edge (TLS terminé à l'edge). + } + + chain forward { + type filter hook forward priority 0; policy drop; + ct state established,related accept + iifname "docker0" accept + oifname "docker0" accept + } + chain output { type filter hook output priority 0; policy accept; } +} diff --git a/flux-genere/infra-dns-01.nft b/flux-genere/infra-dns-01.nft new file mode 100644 index 0000000..d312c3d --- /dev/null +++ b/flux-genere/infra-dns-01.nft @@ -0,0 +1,47 @@ +#!/usr/sbin/nft -f +# GENERE par scripts/resoudre_flux.py depuis les meta/flux.yml — hote infra-dns-01. +# Deploye par le role `nftables_baseline` en tant que /etc/nftables.conf. + +# Remplace UNIQUEMENT les tables setops (pas de `flush ruleset` : préserve les +# tables etrangeres — DNAT/forward de conteneurs, par exemple). +# +# `setops_filter` est le gabarit de REPLI, pose quand aucun ruleset resolu +# n'existe encore. Les deux fichiers sont des ALTERNATIVES, jamais des couches — +# mais `nft -f` AJOUTE sans purger, et le repli survivait donc a la bascule. +# Deux chaines `input` sur le meme hook, toutes deux en `policy drop` : le +# paquet traverse les DEUX, et seule l'intersection de leurs `accept` passait. +# Mesure du 2026-08-10 sur Technolibre : l'AC etait debout, sa regle 8443 posee +# et acceptante, l'ICMP passait en 0,15 ms — et le TCP expirait, arrete par le +# `policy drop` du repli. Chaque fichier retire donc desormais la table de +# l'autre, et la bascule converge dans les deux sens. +table inet setops_flux {} +delete table inet setops_flux +table inet setops_filter {} +delete table inet setops_filter + +table inet setops_flux { + chain input { + type filter hook input priority 0; policy drop; + + iif "lo" accept + ct state established,related accept + ct state invalid drop + ip protocol icmp accept + ip6 nexthdr icmpv6 accept + + ip saddr { 10.17.0.0/24 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 192.168.255.2/32 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 22 accept # serveur_debian: Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). + icmp type destination-unreachable icmp code frag-needed accept # serveur_debian: ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } udp dport 53 accept # serveur_powerdns: Résolution DNS interne (zone souveraine). DoT/DoH = feuille de route (chiffrement DNS). + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 53 accept # serveur_powerdns: Résolution DNS interne en TCP (réponses volumineuses, AXFR restreint par allow_axfr_ips). + } + + chain forward { + type filter hook forward priority 0; policy drop; + ct state established,related accept + iifname "docker0" accept + oifname "docker0" accept + } + chain output { type filter hook output priority 0; policy accept; } +} diff --git a/flux-genere/infra-edge-01.nft b/flux-genere/infra-edge-01.nft new file mode 100644 index 0000000..356da28 --- /dev/null +++ b/flux-genere/infra-edge-01.nft @@ -0,0 +1,49 @@ +#!/usr/sbin/nft -f +# GENERE par scripts/resoudre_flux.py depuis les meta/flux.yml — hote infra-edge-01. +# Deploye par le role `nftables_baseline` en tant que /etc/nftables.conf. + +# Remplace UNIQUEMENT les tables setops (pas de `flush ruleset` : préserve les +# tables etrangeres — DNAT/forward de conteneurs, par exemple). +# +# `setops_filter` est le gabarit de REPLI, pose quand aucun ruleset resolu +# n'existe encore. Les deux fichiers sont des ALTERNATIVES, jamais des couches — +# mais `nft -f` AJOUTE sans purger, et le repli survivait donc a la bascule. +# Deux chaines `input` sur le meme hook, toutes deux en `policy drop` : le +# paquet traverse les DEUX, et seule l'intersection de leurs `accept` passait. +# Mesure du 2026-08-10 sur Technolibre : l'AC etait debout, sa regle 8443 posee +# et acceptante, l'ICMP passait en 0,15 ms — et le TCP expirait, arrete par le +# `policy drop` du repli. Chaque fichier retire donc desormais la table de +# l'autre, et la bascule converge dans les deux sens. +table inet setops_flux {} +delete table inet setops_flux +table inet setops_filter {} +delete table inet setops_filter + +table inet setops_flux { + chain input { + type filter hook input priority 0; policy drop; + + iif "lo" accept + ct state established,related accept + ct state invalid drop + ip protocol icmp accept + ip6 nexthdr icmpv6 accept + + ip saddr { 10.17.0.0/24 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 192.168.255.2/32 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 22 accept # serveur_debian: Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). + icmp type destination-unreachable icmp code frag-needed accept # serveur_debian: ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. + tcp dport 80 accept # serveur_nginx: HTTP entrant — redirection permanente vers HTTPS. + tcp dport 443 accept # serveur_nginx: HTTPS entrant — services exposés (terminaison TLS à l'edge). + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 443 accept # serveur_nginx: HTTPS depuis le tenant : les FQDN publiés vivent à l'edge (découverte OIDC, appels inter-services par nom). + ip saddr { 10.17.0.0/24, 192.168.255.2/32 } tcp dport 443 accept # serveur_nginx: HTTPS depuis le reseau d'administration : l'exploitant administre les services par leur interface web, servie par l'edge. + } + + chain forward { + type filter hook forward priority 0; policy drop; + ct state established,related accept + iifname "docker0" accept + oifname "docker0" accept + } + chain output { type filter hook output priority 0; policy accept; } +} diff --git a/flux-genere/infra-pki-01.nft b/flux-genere/infra-pki-01.nft new file mode 100644 index 0000000..ef4796b --- /dev/null +++ b/flux-genere/infra-pki-01.nft @@ -0,0 +1,46 @@ +#!/usr/sbin/nft -f +# GENERE par scripts/resoudre_flux.py depuis les meta/flux.yml — hote infra-pki-01. +# Deploye par le role `nftables_baseline` en tant que /etc/nftables.conf. + +# Remplace UNIQUEMENT les tables setops (pas de `flush ruleset` : préserve les +# tables etrangeres — DNAT/forward de conteneurs, par exemple). +# +# `setops_filter` est le gabarit de REPLI, pose quand aucun ruleset resolu +# n'existe encore. Les deux fichiers sont des ALTERNATIVES, jamais des couches — +# mais `nft -f` AJOUTE sans purger, et le repli survivait donc a la bascule. +# Deux chaines `input` sur le meme hook, toutes deux en `policy drop` : le +# paquet traverse les DEUX, et seule l'intersection de leurs `accept` passait. +# Mesure du 2026-08-10 sur Technolibre : l'AC etait debout, sa regle 8443 posee +# et acceptante, l'ICMP passait en 0,15 ms — et le TCP expirait, arrete par le +# `policy drop` du repli. Chaque fichier retire donc desormais la table de +# l'autre, et la bascule converge dans les deux sens. +table inet setops_flux {} +delete table inet setops_flux +table inet setops_filter {} +delete table inet setops_filter + +table inet setops_flux { + chain input { + type filter hook input priority 0; policy drop; + + iif "lo" accept + ct state established,related accept + ct state invalid drop + ip protocol icmp accept + ip6 nexthdr icmpv6 accept + + ip saddr { 10.17.0.0/24 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 192.168.255.2/32 } tcp dport 22 accept # administration (garde anti-lockout, intrant nftables_admin_ssh) + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 22 accept # serveur_debian: Plan de gestion : administration et déploiement Ansible par SSH (inter-nœud ; l'accès depuis l'extérieur est filtré à l'OPNsense). + icmp type destination-unreachable icmp code frag-needed accept # serveur_debian: ICMP « fragmentation nécessaire » entrant : sans lui, un distant ne peut pas nous demander de réduire nos paquets — les transferts se figent. + ip saddr { 10.29.16.11, 10.29.19.11, 10.29.19.21, 10.29.21.11 } tcp dport 8443 accept # serveur_step_ca: ACME + API step-ca : chaque nœud (client_pki) émet/renouvelle ses certificats et récupère la racine. + } + + chain forward { + type filter hook forward priority 0; policy drop; + ct state established,related accept + iifname "docker0" accept + oifname "docker0" accept + } + chain output { type filter hook output priority 0; policy accept; } +} diff --git a/inventories/production/group_vars/all/30-amont.yml b/inventories/production/group_vars/all/30-amont.yml new file mode 100644 index 0000000..1a5f463 --- /dev/null +++ b/inventories/production/group_vars/all/30-amont.yml @@ -0,0 +1,48 @@ +--- +# L'AMONT DE LA LIGNEE — comment patient 0 atteint la forge dont il descend. +# +# La forge amont (Alliance Boreale) porte les cinq depots du genome. La forge de +# patient 0 les MIROITE toutes les huit heures, pour que la copie cesse d'etre figee au +# jour du poussage. Ce fichier declare par quelle adresse elle l'atteint. +# +# CE QUI A ETE MESURE LE 2026-08-23, depuis forge-01, dans l'overlay de patient 0 : +# +# ping 100 octets vers 192.168.14.66 -> BLOQUE +# ping 100 octets vers 192.168.12.201 -> BLOQUE +# ping / https vers l'internet -> passe, toutes tailles +# +# Le tenant de patient 0 a le droit de SORTIR sur l'internet et PAS d'entrer dans le +# LAN 192.168.x de l'hebergeur. C'est le default-deny entre les deux mondes, et il est +# correct : un ecosysteme enfant n'a rien a faire dans le reseau physique de son hote. +# +# L'ADRESSE PUBLIQUE, ELLE, REPOND — ET C'EST BIEN LA BONNE FORGE : +# +# curl https://69.70.26.51/api/v1/version -> {"version":"8.0.3+gitea-1.22.0"} +# la forge locale de patient 0 -> {"version":"16.0.2+gitea-1.22.0"} +# 8 essais sur 8 : HTTP 200 en ~6,7 ms +# +# Sept millisecondes vers une adresse publique : le trafic ne sort pas vraiment, il +# revient en epingle au bord du reseau. C'est stable et c'est le chemin qui marche. +# +# POURQUOI EPINGLER PLUTOT QUE LAISSER LE DNS FAIRE. Le DNS public ne rend aujourd'hui +# qu'un seul enregistrement A (69.70.26.51), et les hotes de patient 0 interrogent Quad9 +# — la resolution est donc juste par construction. Mais le jour ou un enregistrement a +# horizon partage rendrait l'adresse INTERNE, le miroir s'arreterait SANS BRUIT : la +# poignee TCP vers 192.168.14.66 « reussit » (mesure du meme jour), seule la livraison +# manque. Une copie du genome qui vieillit en silence est exactement le defaut que ce +# depot combat. On epingle donc l'adresse dont on a prouve qu'elle livre. +# +# SI CETTE ADRESSE CHANGE, les miroirs s'arretent — mais bruyamment, et tout le reste de +# l'acces a la forge amont s'arrete en meme temps. C'est un echec visible, pas un silence. +# +# Le plancher /etc/hosts est REGENERE integralement a chaque passage du role +# `hosts_statiques` : une entree posee a la main y serait effacee. Elle doit vivre ici. +hosts_statiques_externes: + - ip: "69.70.26.51" + noms: + - "forge.alliance-boreale.ca" + pourquoi: "forge amont du genome — l'adresse interne est bloquee par la frontiere" + +# A SUIVRE, constate en passant le 2026-08-23 : les hotes de patient 0 resolvent par +# 9.9.9.9 (Quad9) et n'interrogent PAS leur propre serveur `infra-dns-01`. L'ecosysteme +# fait donc tourner un resolveur que personne n'utilise. A traiter separement.