Cle CSK ECDSA P-256 tiree dans la voute du locataire, importee par le role, DS calcule sans la machine. Refus de changer la cle ou de retirer la signature tant qu'un DS est publie. SOA-EDIT EPOCH : INCEPTION-EPOCH aurait laisse expirer les signatures du site. CAA sur chezlepro.ca, sonde des signatures au site, P18 deplie vault_dnssec_<zone>. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
109 lines
4.9 KiB
Django/Jinja
109 lines
4.9 KiB
Django/Jinja
#!/bin/bash
|
|
# GENERE par Set-OPS (role serveur_powerdns). Ne pas editer a la main.
|
|
#
|
|
# setops-dnssec-zone ZONE signer|nu — met une zone publique dans l'etat DNSSEC du plan.
|
|
#
|
|
# signer : la cle arrive sur l'ENTREE STANDARD (la cle CSK de la voute du locataire, en
|
|
# base64). La zone doit porter CETTE cle, et elle seule.
|
|
# nu : la zone ne doit porter aucune cle.
|
|
#
|
|
# Codes : 0 deja conforme, 10 change, 3 refus (la raison sur la sortie), autre = erreur.
|
|
# LA CLE N'EST JAMAIS ECRITE SUR LA SORTIE : Ansible la journaliserait.
|
|
#
|
|
# LE REFUS QUI COMPTE. Changer la cle — ou retirer la signature — pendant que le registre
|
|
# publie un DS rend le domaine BOGUS pour tout resolveur validant : site et courriel
|
|
# injoignables, et aucune machine en panne. On mesure donc le DS publie avant, et une mesure
|
|
# qui echoue vaut refus : l'absence de preuve n'est pas une preuve d'absence.
|
|
set -euo pipefail
|
|
|
|
ZONE="${1:?zone}"
|
|
MODE="${2:?signer|nu}"
|
|
P=(runuser -u pdns -- pdnsutil --config-name=public)
|
|
|
|
ids() {
|
|
"${P[@]}" list-keys "$ZONE" 2>/dev/null | awk -v z="$ZONE" '$1 == z { print $7 }'
|
|
}
|
|
|
|
ds_publie() {
|
|
# Rend 0 si le registre ne publie AUCUN DS, 1 s'il en publie, 2 si on ne sait pas.
|
|
local sortie statut
|
|
sortie=$(dig +time=4 +tries=2 +noall +comments +answer "$ZONE" DS 2>/dev/null) || return 2
|
|
statut=$(sed -n 's/.*status: \([A-Z]*\),.*/\1/p' <<<"$sortie" | head -1)
|
|
[[ "$statut" == "NOERROR" || "$statut" == "NXDOMAIN" ]] || return 2
|
|
grep -qE "^[^;].*[[:space:]]DS[[:space:]]" <<<"$sortie" && return 1
|
|
return 0
|
|
}
|
|
|
|
refuser_si_ds() {
|
|
local code=0
|
|
ds_publie || code=$?
|
|
case "$code" in
|
|
0) return 0 ;;
|
|
1) echo "REFUS : le registre publie un DS pour $ZONE. $1 rendrait le domaine BOGUS. Retirer d'abord le DS chez le registraire, attendre son TTL, puis relancer." ;;
|
|
*) echo "REFUS : impossible de lire le DS publie pour $ZONE (resolveur injoignable ?). $1 sans cette mesure pourrait rendre le domaine BOGUS." ;;
|
|
esac
|
|
exit 3
|
|
}
|
|
|
|
actuelles=$(ids)
|
|
|
|
if [[ "$MODE" == "nu" ]]; then
|
|
[[ -z "$actuelles" ]] && exit 0
|
|
refuser_si_ds "Retirer la signature"
|
|
"${P[@]}" disable-dnssec "$ZONE" >/dev/null
|
|
for id in $(ids); do "${P[@]}" remove-zone-key "$ZONE" "$id" >/dev/null; done
|
|
# LIMITE CONNUE. Sans SOA-EDIT, le serial servi redescend a celui du contenu — plus petit
|
|
# que l'heure que le site a vue en dernier. Le site garde donc la version signee jusqu'au
|
|
# prochain changement de contenu ; sans DS publie (verifie plus haut), c'est sans effet
|
|
# pour les resolveurs. Pour l'aligner tout de suite : `pdns_control retrieve ZONE` au site.
|
|
"${P[@]}" set-meta "$ZONE" SOA-EDIT >/dev/null
|
|
echo "$ZONE : signature retiree."
|
|
exit 10
|
|
fi
|
|
|
|
[[ "$MODE" == "signer" ]] || { echo "mode inconnu : $MODE"; exit 2; }
|
|
IFS= read -r CLE || true
|
|
[[ "$CLE" =~ ^[A-Za-z0-9+/]{43}=$ ]] || { echo "REFUS : cle absente ou malformee sur l'entree standard."; exit 3; }
|
|
|
|
# SOA-EDIT EPOCH : LE SERIAL SERVI EST L'HEURE (calcule le 2026-09-16, apres une premiere
|
|
# version fausse).
|
|
#
|
|
# PowerDNS signe avec une inception au debut de la semaine PRECEDENTE et une expiration deux
|
|
# semaines apres le debut de la semaine COURANTE (mesure : inception 2026-09-10, expiration
|
|
# 2026-10-01, un 2026-09-17). Le secondaire du site garde les signatures qu'il a tirees : il
|
|
# faut qu'il retire la zone au moins chaque semaine.
|
|
#
|
|
# La premiere version posait INCEPTION-EPOCH : serial = max(serial, inception). Or notre
|
|
# serial suit deja l'heure du dernier changement — il DEPASSE l'inception pendant deux
|
|
# semaines. Le serial servi ne bougeait donc qu'au moment ou les signatures du site
|
|
# expiraient : une heure de BOGUS, au mieux, a chaque cycle.
|
|
#
|
|
# EPOCH rend l'heure : le site voit un serial neuf a chaque rafraichissement (une heure) et
|
|
# retire la zone. Deux zones de quelques Ko par heure — le prix d'une signature jamais
|
|
# perimee de plus d'une heure. Les signatures servies gardent entre 7 et 14 jours.
|
|
soa_edit=$("${P[@]}" get-meta "$ZONE" SOA-EDIT 2>/dev/null | grep -cx "SOA-EDIT = EPOCH" || true)
|
|
|
|
if [[ $(wc -w <<<"$actuelles") -eq 1 ]] \
|
|
&& "${P[@]}" export-zone-key "$ZONE" "$actuelles" 2>/dev/null | grep -qxF "PrivateKey: $CLE" \
|
|
&& [[ "$soa_edit" -ge 1 ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
# UNE AUTRE CLE EST EN PLACE : la remplacer n'est sur que si aucun DS n'est publie.
|
|
if [[ -n "$actuelles" ]] && ! { [[ $(wc -w <<<"$actuelles") -eq 1 ]] \
|
|
&& "${P[@]}" export-zone-key "$ZONE" "$actuelles" 2>/dev/null | grep -qxF "PrivateKey: $CLE"; }; then
|
|
refuser_si_ds "Remplacer la cle"
|
|
for id in $actuelles; do "${P[@]}" remove-zone-key "$ZONE" "$id" >/dev/null; done
|
|
fi
|
|
|
|
if [[ -z "$(ids)" ]]; then
|
|
f=$(mktemp /run/setops-dnssec.XXXXXX)
|
|
trap 'shred -u -z "$f" 2>/dev/null || rm -f "$f"' EXIT
|
|
chown pdns "$f"
|
|
chmod 0600 "$f"
|
|
printf 'Private-key-format: v1.2\nAlgorithm: 13 (ECDSAP256SHA256)\nPrivateKey: %s\n' "$CLE" > "$f"
|
|
"${P[@]}" import-zone-key "$ZONE" "$f" ksk active published >/dev/null
|
|
fi
|
|
"${P[@]}" set-meta "$ZONE" SOA-EDIT EPOCH >/dev/null
|
|
echo "$ZONE : signee."
|
|
exit 10
|