La zone publie l empreinte de ce que le plancher doit porter ; chaque machine la compare a son /etc/hosts. Au site, elle a revele que sauvegarde, pki et dns.genese.internal pointaient vers l edge (regression de mon deploiement du matin) : la derivation ne donne plus a l edge ce qui n a pas de port. Zone du site corrigee, SFTP des locataires verifie. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.5 KiB
Django/Jinja
96 lines
4.5 KiB
Django/Jinja
#!/bin/bash
|
|
# GENERE par Set-OPS (role client_sante). Ne pas editer a la main.
|
|
#
|
|
# SONDE « plancher » — le plancher /etc/hosts de cette machine dit-il la meme chose que
|
|
# l'autoritatif du domaine ?
|
|
#
|
|
# POURQUOI (2026-09-29). Le plancher et la zone PowerDNS derivent du meme plan, mais
|
|
# chacun ne le suit que quand son role est rejoue. La console a ete ajoutee au plan : la
|
|
# zone l'a recue, les planchers non — sauf celui d'`ops-01`, redeploye depuis. Depuis
|
|
# l'edge, `console` ne se resolvait pas, et rien ne le signalait. Sur ces machines, le
|
|
# plancher est la SEULE resolution des noms internes : un nom qui y manque n'existe pas.
|
|
#
|
|
# COMMENT, SANS TRANSFERT DE ZONE. La zone publie `_plancher.<domaine> TXT "n=… sha256=…"`
|
|
# : le nombre et l'empreinte des paires « nom adresse » que le plancher doit porter pour ce
|
|
# domaine. On calcule la meme chose ici sur /etc/hosts. Si elles different, on nomme ce
|
|
# qu'on peut : chaque nom du plancher est demande a l'autoritatif (en trop, readresse), et
|
|
# l'ecart de nombre dit combien de noms publies manquent ici.
|
|
#
|
|
# L'autoritatif est interroge DIRECTEMENT : le resolveur qu'utilisent ces machines peut
|
|
# ignorer ce domaine (c'est le cas chez les locataires, qui passent par celui du site).
|
|
#
|
|
# Mise en defaut PAR PARAMETRE : `client_sante_plancher_fichier` (un plancher vide).
|
|
set -uo pipefail
|
|
|
|
DOMAINE='{{ domaine_interne | default('') }}'
|
|
AUTORITE='{{ hostvars[groups['serveur_powerdns'][0]].ansible_host | default('') if (groups.get('serveur_powerdns') or []) else '' }}'
|
|
FICHIER='{{ client_sante_plancher_fichier }}'
|
|
|
|
if [[ -z "${DOMAINE}" || -z "${AUTORITE}" ]]; then
|
|
echo "Pas d'autoritatif interne dans cet ecosysteme : rien a quoi comparer le plancher."
|
|
exit 0
|
|
fi
|
|
|
|
txt=$(dig +short +time=3 +tries=2 TXT "_plancher.${DOMAINE}" "@${AUTORITE}" 2>/dev/null | tr -d '"')
|
|
if [[ "${txt}" != n=*" sha256="* ]]; then
|
|
echo "L'autoritatif (${AUTORITE}) ne publie pas l'empreinte du plancher (_plancher.${DOMAINE} : ${txt:-rien}) — zone anterieure, ou injoignable : comparaison impossible."
|
|
exit 1
|
|
fi
|
|
|
|
# Les paires « nom adresse » du plancher pour CE domaine : toute ligne, tout nom qui finit
|
|
# par `.<domaine>` (le nom court des hotes, en alias, n'est pas compte ; la zone non plus).
|
|
local_=$(python3 - "${FICHIER}" "${DOMAINE}" <<'PY'
|
|
import hashlib, sys
|
|
fichier, domaine = sys.argv[1], sys.argv[2]
|
|
paires = set()
|
|
try:
|
|
lignes = open(fichier, encoding="utf-8").read().splitlines()
|
|
except OSError:
|
|
lignes = []
|
|
for l in lignes:
|
|
champs = l.split("#", 1)[0].split()
|
|
if len(champs) < 2:
|
|
continue
|
|
for nom in champs[1:]:
|
|
if nom.endswith("." + domaine):
|
|
paires.add(f"{nom} {champs[0]}")
|
|
tri = sorted(paires)
|
|
print(f"n={len(tri)} sha256={hashlib.sha256(chr(10).join(tri).encode()).hexdigest()}")
|
|
for p in tri:
|
|
print(p)
|
|
PY
|
|
)
|
|
empreinte=$(printf '%s\n' "${local_}" | head -1)
|
|
n_pub=${txt#n=}; n_pub=${n_pub%% *}
|
|
n_loc=${empreinte#n=}; n_loc=${n_loc%% *}
|
|
perf="| noms=${n_loc};;;0; publies=${n_pub};;;0;"
|
|
|
|
if [[ "${empreinte}" == "${txt}" ]]; then
|
|
echo "Plancher conforme a l'autoritatif : ${n_loc} nom(s) de ${DOMAINE}, memes adresses. ${perf}"
|
|
exit 0
|
|
fi
|
|
|
|
# ILS DIFFERENT : on nomme ce qu'on peut. Chaque nom du plancher est demande a l'autoritatif.
|
|
# Un nom qu'il ignore est en trop ici (ou une machine planifiee, que la zone ne publie pas
|
|
# encore) ; une autre adresse est un readressage que l'un des deux n'a pas suivi.
|
|
en_trop=(); readresses=(); connus=0; i=0
|
|
while read -r nom ip; do
|
|
(( i++ >= 40 )) && break
|
|
pub=$(dig +short +time=2 +tries=1 A "${nom}" "@${AUTORITE}" 2>/dev/null | tail -1)
|
|
if [[ -z "${pub}" ]]; then en_trop+=("${nom%%.*}")
|
|
elif [[ "${pub}" != "${ip}" ]]; then readresses+=("${nom%%.*}=${ip}/${pub}"); connus=$((connus + 1))
|
|
else connus=$((connus + 1)); fi
|
|
done < <(printf '%s\n' "${local_}" | tail -n +2)
|
|
manquants=$(( n_pub - connus ))
|
|
|
|
msg="Le plancher de cette machine et l'autoritatif divergent (${n_loc} nom(s) ici, ${n_pub} publie(s))"
|
|
[[ ${manquants} -gt 0 ]] && msg+=" ; ${manquants} nom(s) publie(s) absent(s) d'ici"
|
|
[[ -n "${readresses[*]}" ]] && msg+=" ; autre adresse : ${readresses[*]}"
|
|
[[ -n "${en_trop[*]}" ]] && msg+=" ; inconnu(s) de l'autoritatif : ${en_trop[*]}"
|
|
msg+=". Rejouer hosts_statiques (ou serveur_powerdns, si c'est la zone qui date)."
|
|
# Un nom qui MANQUE ou une adresse qui differe casse une resolution : critique. Des noms en
|
|
# trop seulement (une machine retiree du plan) ne cassent rien : avertissement.
|
|
if (( manquants > 0 )) || [[ -n "${readresses[*]}" ]]; then
|
|
echo "${msg} ${perf}"; exit 2
|
|
fi
|
|
echo "${msg} ${perf}"; exit 1
|