Set-OPS-Public/roles/client_sante/templates/sonde-plancher.sh.j2
Daniel Allaire eee6851c3b sonde plancher ; une exposition sans port n est plus attribuee a l edge
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>
2026-09-29 12:34:47 -04:00

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