portabilite : le repli nftables survivait a la bascule et annulait tout
Le socle pose l'un de DEUX fichiers dans /etc/nftables.conf : le ruleset derive (table setops_flux) s'il existe, sinon un gabarit de repli plat (table setops_filter) qui n'ouvre que le 22. Ce sont des ALTERNATIVES, jamais des couches. Mais le rechargement est `nft -f`, qui AJOUTE sans purger, et le fichier derive retirait setops_flux — jamais setops_filter. Un hote passe du repli au derive gardait donc DEUX chaines input sur le meme hook, toutes deux en policy drop. Le paquet traverse les deux : seule l'intersection de leurs accept passait. Symptome parfaitement trompeur : AC debout, 8443 en ecoute, regle posee et acceptante, ICMP entre les deux VM a 0,15 ms — et step ca bootstrap qui expire. Il a fallu lire le ruleset entier pour voir la seconde table. Le fichier derive retire desormais les DEUX tables (le repli portait deja flush ruleset). Pas de flush ruleset cote derive : choix du depot pour ne pas detruire de tables etrangeres, respecte. Ce qui l'avait rendu possible : `make reconstruire` ne generait JAMAIS les flux. `make flux` en est maintenant la premiere etape. Et `no_log` a masque la cause trois fois dans la journee — dont au terme d'un deploiement de 157 taches. Les taches concernees extraient desormais le verdict a part (ce qui a echoue, quel code, quel message) sans toucher aux en-tetes. Une garde qui protege un secret ne doit pas emporter le diagnostic. Verifie : TLS OK depuis infra-dns-01 vers l'AC, une seule table nftables, 13/14 hotes deployes sans echec, prouver.py 34 OK, ansible-lint production. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3b16b8845d
commit
9c1c790f9d
4 changed files with 85 additions and 4 deletions
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -56,6 +56,44 @@ rien à faire ». Le verrou de la seule commande destructive du moteur passait d
|
|||
sans rien éprouver. Le faux cluster **fabrique désormais la collision sur le plan courant**,
|
||||
quel qu'il soit.
|
||||
|
||||
### 4. Le repli nftables survivait à la bascule — et annulait tout
|
||||
|
||||
Le socle pose l'un de **deux** fichiers dans `/etc/nftables.conf` : le ruleset **dérivé**
|
||||
(`make flux`, table `setops_flux`) s'il existe, sinon un **gabarit de repli** plat (table
|
||||
`setops_filter`) qui n'ouvre que le `22`. Ce sont des **alternatives**, jamais des couches.
|
||||
|
||||
Mais le rechargement est `nft -f`, qui **ajoute sans purger**, et le fichier dérivé retirait
|
||||
soigneusement `setops_flux`… jamais `setops_filter`. Un hôte passé du repli au dérivé se
|
||||
retrouvait donc avec **deux chaînes `input` sur le même hook, toutes deux en `policy drop`**.
|
||||
Le paquet traverse les deux : seule l'**intersection** de leurs `accept` passait — le `22` et
|
||||
l'ICMP, rien d'autre.
|
||||
|
||||
Le symptôme était parfaitement trompeur : l'AC **debout**, son port 8443 **en écoute**, sa
|
||||
règle `ip saddr { … } tcp dport 8443 accept` **posée et acceptante**, l'ICMP entre les deux
|
||||
VM à **0,15 ms** — et `step ca bootstrap` qui expire. Il a fallu lire le ruleset entier pour
|
||||
voir la seconde table.
|
||||
|
||||
Le fichier dérivé retire désormais **les deux** tables (le repli, lui, portait déjà
|
||||
`flush ruleset`). Pas de `flush ruleset` côté dérivé : c'est un choix du dépôt pour ne pas
|
||||
détruire de tables étrangères, et il est respecté.
|
||||
|
||||
**Ce qui l'avait rendu possible** : `make reconstruire` ne générait **jamais** les flux.
|
||||
Sans eux, `flux-genere/` est vide, le repli est posé, et l'hôte passe ensuite au dérivé —
|
||||
exactement la bascule qui casse. `make flux` est maintenant la première étape de
|
||||
`reconstruire`. Invisible sur un écosystème déjà construit, dont les `.nft` traînent d'une
|
||||
exécution précédente.
|
||||
|
||||
### `no_log` a masqué la cause trois fois dans la même journée
|
||||
|
||||
Le clonage, l'attente de configuration, et la déclaration des URI de déconnexion Keycloak :
|
||||
trois échecs lus « the output has been hidden », dont un au terme d'un déploiement de 157
|
||||
tâches. Le mot-clé protège de vrais secrets — un jeton d'API porté par un en-tête — et on ne
|
||||
peut pas simplement l'enlever.
|
||||
|
||||
Les trois tâches **extraient donc désormais le verdict à part** : ce qui a échoué, avec quel
|
||||
code et quel message, sans jamais toucher aux en-têtes. Une garde qui protège un secret ne
|
||||
doit pas emporter le diagnostic avec lui.
|
||||
|
||||
### Ce qui relevait des données du tenant, pas du moteur
|
||||
|
||||
L'instance datait d'avant plusieurs évolutions, et les preuves statiques les ont toutes
|
||||
|
|
|
|||
9
Makefile
9
Makefile
|
|
@ -594,6 +594,14 @@ _attendre-flotte: ansible-runtime
|
|||
raser: ansible-runtime _instance-requise ## DESTRUCTIF : detruit les VM derivees du plan — exige CONFIRMER=true ET INSTANCE=<nom>
|
||||
@python3 scripts/raser.py $(if $(INSTANCE),--instance $(INSTANCE)) $(if $(HOTE),--hote $(HOTE)) $(if $(filter true,$(CONFIRMER)),--confirmer)
|
||||
|
||||
# `make flux` D'ABORD, et ce n'est pas une precaution : sans lui, `instance/flux-genere/`
|
||||
# est VIDE et le socle deploie nftables en `policy drop` SANS AUCUNE REGLE DERIVEE. La
|
||||
# flotte se monte, SSH repond depuis l'administration — et tout le reste est mur.
|
||||
#
|
||||
# Invisible sur un ecosysteme deja construit, dont les .nft trainaient d'une execution
|
||||
# precedente. Trouve le 2026-08-10 en montant Technolibre depuis zero : l'AC etait
|
||||
# debout, son port 8443 en ecoute, et `step ca bootstrap` expirait depuis une VM du
|
||||
# meme sous-reseau. Zero regle pour 8443 dans le ruleset, `flux-genere/` vide.
|
||||
reconstruire: _instance-requise ## Reconstruit un ecosysteme depuis zero : VM puis deploiement complet
|
||||
@set -e; \
|
||||
if [[ "$(CONFIRMER)" != "true" ]]; then \
|
||||
|
|
@ -602,6 +610,7 @@ reconstruire: _instance-requise ## Reconstruit un ecosysteme depuis zero : VM pu
|
|||
printf '%s\n' 'Relancer avec CONFIRMER=true.'; \
|
||||
exit 2; \
|
||||
fi; \
|
||||
$(MAKE) flux; \
|
||||
$(MAKE) flotte-creer CONFIRMER=true; \
|
||||
$(MAKE) _attendre-flotte; \
|
||||
$(MAKE) _amorcer-socle; \
|
||||
|
|
|
|||
|
|
@ -48,9 +48,31 @@
|
|||
loop: "{{ serveur_keycloak_clients_actuels.json }}"
|
||||
loop_control:
|
||||
label: "{{ item.clientId }}"
|
||||
# `no_log` protege le JETON porte par l'en-tete — on ne peut pas simplement l'enlever.
|
||||
# Mais il masquait aussi la CAUSE : le 2026-08-10, l'echec s'est lu « the output has
|
||||
# been hidden » sur le tout dernier appel d'un deploiement de 157 taches. On journalise
|
||||
# donc le verdict a part, en n'extrayant que ce qui ne revele rien.
|
||||
no_log: true
|
||||
register: serveur_keycloak_deconnexion_res
|
||||
failed_when: false
|
||||
changed_when: true
|
||||
when:
|
||||
- item.clientId in serveur_keycloak_deconnexion
|
||||
- (item.attributes | default({})).get('post.logout.redirect.uris', '')
|
||||
!= serveur_keycloak_deconnexion[item.clientId]
|
||||
|
||||
- name: Dire quels clients Keycloak a refusés, et pourquoi
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Keycloak a refuse la mise a jour des URI de deconnexion pour :
|
||||
{{ refuses | map(attribute='item.clientId') | list | join(', ') }}.
|
||||
Codes rendus : {{ refuses | map(attribute='status') | list }}.
|
||||
Messages : {{ refuses | map(attribute='msg') | map('string')
|
||||
| map('truncate', 160) | list }}.
|
||||
vars:
|
||||
refuses: >-
|
||||
{{ (serveur_keycloak_deconnexion_res.results | default([]))
|
||||
| rejectattr('skipped', 'defined')
|
||||
| selectattr('status', 'defined')
|
||||
| rejectattr('status', 'equalto', 204) | list }}
|
||||
when: refuses | length > 0
|
||||
|
|
|
|||
|
|
@ -290,11 +290,23 @@ def _rendre_nft(hote: str, regles: list[str]) -> str:
|
|||
return (
|
||||
f"#!/usr/sbin/nft -f\n"
|
||||
f"# GENERE par scripts/resoudre_flux.py depuis les meta/flux.yml — hote {hote}.\n"
|
||||
f"# APERCU inspectable — NON active. L'activation reste un geste dedie, teste par noeud.\n\n"
|
||||
f"# Remplace UNIQUEMENT la table setops (pas de `flush ruleset` : préserve les\n"
|
||||
f"# tables de Docker — DNAT/forward des conteneurs, ex. Collabora).\n"
|
||||
f"# Deploye par le role `nftables_baseline` en tant que /etc/nftables.conf.\n\n"
|
||||
f"# Remplace UNIQUEMENT les tables setops (pas de `flush ruleset` : préserve les\n"
|
||||
f"# tables etrangeres — DNAT/forward de conteneurs, par exemple).\n"
|
||||
f"#\n"
|
||||
f"# `setops_filter` est le gabarit de REPLI, pose quand aucun ruleset resolu\n"
|
||||
f"# n'existe encore. Les deux fichiers sont des ALTERNATIVES, jamais des couches —\n"
|
||||
f"# mais `nft -f` AJOUTE sans purger, et le repli survivait donc a la bascule.\n"
|
||||
f"# Deux chaines `input` sur le meme hook, toutes deux en `policy drop` : le\n"
|
||||
f"# paquet traverse les DEUX, et seule l'intersection de leurs `accept` passait.\n"
|
||||
f"# Mesure du 2026-08-10 sur Technolibre : l'AC etait debout, sa regle 8443 posee\n"
|
||||
f"# et acceptante, l'ICMP passait en 0,15 ms — et le TCP expirait, arrete par le\n"
|
||||
f"# `policy drop` du repli. Chaque fichier retire donc desormais la table de\n"
|
||||
f"# l'autre, et la bascule converge dans les deux sens.\n"
|
||||
f"table inet setops_flux {{}}\n"
|
||||
f"delete table inet setops_flux\n\n"
|
||||
f"delete table inet setops_flux\n"
|
||||
f"table inet setops_filter {{}}\n"
|
||||
f"delete table inet setops_filter\n\n"
|
||||
f"table inet setops_flux {{\n"
|
||||
f" chain input {{\n"
|
||||
f" type filter hook input priority 0; policy drop;\n\n"
|
||||
|
|
|
|||
Loading…
Reference in a new issue