64138831a8
Neue Skripte dnssec_check (Bash, dig) und dnssec_check.ps1 (PowerShell, Resolve-DnsName): pruefen fuer hinterlegbare Domains den DNSSEC-Status (aktiv/inaktiv/fail) inkl. Resolver-Selbsttest. README erweitert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
229 lines
7.8 KiB
Bash
Executable File
229 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# dnssec_check - prüft für hinterlegte Domains, ob DNSSEC aktiviert und
|
||
# gültig signiert ist.
|
||
#
|
||
# Zielsystem : Ubuntu 22.04
|
||
# Benötigt : dig (Paket: bind9-dnsutils bzw. dnsutils)
|
||
# Installation: sudo apt install bind9-dnsutils
|
||
#
|
||
# Status-Bedeutung:
|
||
# aktiv - Domain ist DNSSEC-signiert UND wurde erfolgreich validiert
|
||
# (Antwort NOERROR mit gesetztem AD-Flag = Authenticated Data)
|
||
# inaktiv - Domain ist NICHT signiert (kein DNSSEC vorhanden)
|
||
# (Antwort NOERROR, aber KEIN AD-Flag)
|
||
# fail - DNSSEC vorhanden, aber Validierung schlägt fehl / Fehler
|
||
# (SERVFAIL vom validierenden Resolver o. ä.)
|
||
#
|
||
# Wichtig: Das Skript fragt einen *validierenden* Resolver ab (Default der
|
||
# lokale BIND auf 127.0.0.1). Ohne validierenden Resolver ist die Prüfung
|
||
# nicht aussagekräftig.
|
||
#
|
||
# Aufruf:
|
||
# ./dnssec_check # nutzt die unten hinterlegte Liste
|
||
# ./dnssec_check -r 192.168.1.1 # anderer Resolver
|
||
# ./dnssec_check example.com # nur diese Domain(s) prüfen
|
||
#
|
||
|
||
set -u
|
||
|
||
# ------------------------------------------------------------------
|
||
# Konfiguration
|
||
# ------------------------------------------------------------------
|
||
|
||
# Validierender Resolver (z. B. der lokale BIND). Per -r überschreibbar.
|
||
RESOLVER="127.0.0.1"
|
||
|
||
# >>> Hier die zu prüfenden Domains eintragen <<<
|
||
DOMAINS=(
|
||
"isc.org" # signiert -> erwartet: aktiv
|
||
"dnssec-failed.org" # kaputt -> erwartet: fail
|
||
"maier3000.de" # signiert -> erwartet: aktiv
|
||
"maierch.de" # signiert -> erwartet: aktiv
|
||
"maierchr.de" # signiert -> erwartet: aktiv
|
||
"citeq.de" # unsigniert-> erwartet: inaktiv
|
||
)
|
||
|
||
# Abfragetyp und dig-Optionen (Timeout/Versuche)
|
||
QTYPE="A"
|
||
DIG_OPTS=(+time=3 +tries=2)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Ab hier nichts mehr ändern
|
||
# ------------------------------------------------------------------
|
||
|
||
usage() {
|
||
cat >&2 <<EOF
|
||
Aufruf: $0 [-r resolver] [-t qtype] [-s|--self-check] [domain ...]
|
||
-r resolver DNS-Resolver, der geprüft wird (Default: $RESOLVER)
|
||
-t qtype Abfragetyp (Default: $QTYPE)
|
||
-s, --self-check nur prüfen, ob der Resolver DNSSEC validiert (keine Domainliste)
|
||
EOF
|
||
exit 1
|
||
}
|
||
|
||
SELF_ONLY=0
|
||
|
||
# Lange Option --self-check auf -s abbilden
|
||
# (Guard fuer leere Argumentliste wegen bash 3.2 unter macOS + 'set -u')
|
||
if [[ $# -gt 0 ]]; then
|
||
args=()
|
||
for a in "$@"; do
|
||
case "$a" in
|
||
--self-check) args+=("-s") ;;
|
||
*) args+=("$a") ;;
|
||
esac
|
||
done
|
||
set -- "${args[@]}"
|
||
fi
|
||
|
||
while getopts ":r:t:sh" opt; do
|
||
case "$opt" in
|
||
r) RESOLVER="$OPTARG" ;;
|
||
t) QTYPE="$OPTARG" ;;
|
||
s) SELF_ONLY=1 ;;
|
||
h) usage ;;
|
||
*) usage ;;
|
||
esac
|
||
done
|
||
shift $((OPTIND - 1))
|
||
|
||
# Domains von der Kommandozeile überschreiben die Liste
|
||
if [[ $# -gt 0 ]]; then
|
||
DOMAINS=("$@")
|
||
fi
|
||
|
||
# dig vorhanden?
|
||
if ! command -v dig >/dev/null 2>&1; then
|
||
echo "Fehler: 'dig' nicht gefunden. Installieren mit: sudo apt install bind9-dnsutils" >&2
|
||
exit 2
|
||
fi
|
||
|
||
# Farben nur, wenn Ausgabe ins Terminal geht
|
||
if [[ -t 1 ]]; then
|
||
C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_RST=$'\033[0m'
|
||
else
|
||
C_GREEN=""; C_YELLOW=""; C_RED=""; C_RST=""
|
||
fi
|
||
|
||
# Feld aus einer dig-Ausgabe ziehen ------------------------------------
|
||
get_status() { sed -n 's/.*status: \([A-Z]*\).*/\1/p' | head -n1; }
|
||
get_flags() { sed -n 's/.*flags: \([a-z ]*\);.*/\1/p' | head -n1; }
|
||
|
||
# Kernprüfung: liefert aktiv | inaktiv | fail -------------------------
|
||
check_domain() {
|
||
local domain="$1" out status flags cdstatus
|
||
|
||
out=$(dig "@$RESOLVER" "${DIG_OPTS[@]}" +dnssec "$domain" "$QTYPE" 2>/dev/null)
|
||
if [[ -z "$out" ]]; then
|
||
echo "fail"; return
|
||
fi
|
||
|
||
status=$(printf '%s\n' "$out" | get_status)
|
||
flags=$(printf '%s\n' "$out" | get_flags)
|
||
|
||
case "$status" in
|
||
NOERROR|NXDOMAIN)
|
||
# AD-Flag = vom Resolver erfolgreich validiert (= signiert)
|
||
if [[ " $flags " == *" ad "* ]]; then
|
||
echo "aktiv"
|
||
else
|
||
echo "inaktiv"
|
||
fi
|
||
;;
|
||
SERVFAIL)
|
||
# Gegenprobe mit +cd (checking disabled): kommt dann eine
|
||
# gültige Antwort, lag der SERVFAIL an der DNSSEC-Validierung.
|
||
cdstatus=$(dig "@$RESOLVER" "${DIG_OPTS[@]}" +cd "$domain" "$QTYPE" 2>/dev/null | get_status)
|
||
if [[ "$cdstatus" == "NOERROR" || "$cdstatus" == "NXDOMAIN" ]]; then
|
||
echo "fail" # DNSSEC-Signatur/Kette ungültig
|
||
else
|
||
echo "fail" # sonstiger Auflösungsfehler
|
||
fi
|
||
;;
|
||
*)
|
||
echo "fail"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Selbsttest: validiert der Resolver überhaupt? ----------------------
|
||
# Nutzt zwei Referenzdomains:
|
||
# - eine absichtlich kaputt signierte (muss SERVFAIL liefern)
|
||
# - eine korrekt signierte (muss AD-Flag liefern)
|
||
# Rückgabe: 0 = validiert, 1 = validiert nicht, 2 = unklar/Netzfehler
|
||
SELF_BAD_DOMAIN="dnssec-failed.org" # bewusst falsch signiert (Verisign)
|
||
SELF_GOOD_DOMAIN="isc.org" # korrekt signiert
|
||
|
||
self_check() {
|
||
local bad_status cd_status good good_status good_flags
|
||
|
||
bad_status=$(dig "@$RESOLVER" "${DIG_OPTS[@]}" +dnssec "$SELF_BAD_DOMAIN" A 2>/dev/null | get_status)
|
||
cd_status=$( dig "@$RESOLVER" "${DIG_OPTS[@]}" +cd "$SELF_BAD_DOMAIN" A 2>/dev/null | get_status)
|
||
good=$( dig "@$RESOLVER" "${DIG_OPTS[@]}" +dnssec "$SELF_GOOD_DOMAIN" A 2>/dev/null)
|
||
good_status=$(printf '%s\n' "$good" | get_status)
|
||
good_flags=$( printf '%s\n' "$good" | get_flags)
|
||
|
||
# Netz-/Erreichbarkeitsproblem?
|
||
if [[ -z "$bad_status" || -z "$good_status" ]]; then
|
||
printf 'Resolver-Selbsttest: %sUNKLAR%s (Resolver %s nicht erreichbar?)\n' \
|
||
"$C_YELLOW" "$C_RST" "$RESOLVER"
|
||
return 2
|
||
fi
|
||
|
||
# Kriterien fuer "validiert":
|
||
# kaputte Domain -> SERVFAIL, mit +cd aber aufloesbar
|
||
# gute Domain -> NOERROR mit AD-Flag
|
||
if [[ "$bad_status" == "SERVFAIL" && ( "$cd_status" == "NOERROR" || "$cd_status" == "NXDOMAIN" ) \
|
||
&& "$good_status" == "NOERROR" && " $good_flags " == *" ad "* ]]; then
|
||
printf 'Resolver-Selbsttest: %sVALIDIERT DNSSEC%s (%s=SERVFAIL, %s=AD)\n' \
|
||
"$C_GREEN" "$C_RST" "$SELF_BAD_DOMAIN" "$SELF_GOOD_DOMAIN"
|
||
return 0
|
||
else
|
||
printf 'Resolver-Selbsttest: %sKEINE VALIDIERUNG%s (%s=%s, +cd=%s, %s=%s flags:%s)\n' \
|
||
"$C_RED" "$C_RST" \
|
||
"$SELF_BAD_DOMAIN" "$bad_status" "$cd_status" \
|
||
"$SELF_GOOD_DOMAIN" "$good_status" "$good_flags"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# ------------------------------------------------------------------
|
||
# Ausgabe
|
||
# ------------------------------------------------------------------
|
||
|
||
# Nur Selbsttest gewuenscht?
|
||
if [[ "$SELF_ONLY" -eq 1 ]]; then
|
||
printf 'Resolver: %s (%s)\n' "$RESOLVER" "$(date '+%Y-%m-%d %H:%M:%S')"
|
||
self_check
|
||
exit $?
|
||
fi
|
||
|
||
printf '%s\n' "DNSSEC-Prüfung über Resolver: $RESOLVER ($(date '+%Y-%m-%d %H:%M:%S'))"
|
||
|
||
# Immer zuerst prüfen, ob der Resolver validiert – sonst sind die
|
||
# Domain-Ergebnisse nicht aussagekräftig.
|
||
self_check
|
||
sc_rc=$?
|
||
if [[ "$sc_rc" -ne 0 ]]; then
|
||
printf '%sHinweis:%s Resolver validiert nicht/unklar – "aktiv/fail" unten sind nur eingeschränkt verlässlich.\n' \
|
||
"$C_YELLOW" "$C_RST"
|
||
fi
|
||
echo
|
||
printf '%-40s %s\n' "DOMAIN" "STATUS"
|
||
printf '%-40s %s\n' "----------------------------------------" "--------"
|
||
|
||
rc=0
|
||
for d in "${DOMAINS[@]}"; do
|
||
st=$(check_domain "$d")
|
||
case "$st" in
|
||
aktiv) col="$C_GREEN" ;;
|
||
inaktiv) col="$C_YELLOW" ;;
|
||
*) col="$C_RED"; rc=1 ;;
|
||
esac
|
||
printf '%-40s %s%s%s\n' "$d" "$col" "$st" "$C_RST"
|
||
done
|
||
|
||
# Exit-Code: 0 = kein fail, 1 = mindestens ein fail (nützlich für Monitoring/cron)
|
||
exit $rc
|