dnssec_check: DNSSEC-Status-Pruefung fuer Bash und PowerShell

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>
This commit is contained in:
maierch
2026-07-10 19:13:36 +02:00
parent a2482df6af
commit 64138831a8
3 changed files with 499 additions and 0 deletions
+61
View File
@@ -121,3 +121,64 @@ powershell -ExecutionPolicy Bypass -File C:\Tools\dig.ps1 example.com MX
- Das Skript benötigt das Modul `DnsClient` (`Resolve-DnsName`), das unter Windows standardmäßig vorhanden ist. - Das Skript benötigt das Modul `DnsClient` (`Resolve-DnsName`), das unter Windows standardmäßig vorhanden ist.
- Die Standardausgabe orientiert sich am `dig`-Format mit `QUESTION`- und `ANSWER SECTION`. - Die Standardausgabe orientiert sich am `dig`-Format mit `QUESTION`- und `ANSWER SECTION`.
- Mit `+short` werden nur die reinen Antwortwerte ausgegeben (eine pro Zeile). - Mit `+short` werden nur die reinen Antwortwerte ausgegeben (eine pro Zeile).
---
# dnssec_check — DNSSEC-Status prüfen
Prüft für eine hinterlegbare Liste von Domains, ob **DNSSEC aktiviert und gültig
signiert** ist, und testet vorab, ob der abgefragte Resolver überhaupt validiert.
Es gibt zwei funktionsgleiche Varianten:
| Datei | Plattform | Werkzeug |
|-------|-----------|----------|
| `dnssec_check` | Linux/macOS (Bash) | `dig` (Paket `bind9-dnsutils`) |
| `dnssec_check.ps1` | Windows (PowerShell 5.1+) | `Resolve-DnsName` (Modul `DnsClient`) |
## Status-Werte
| Status | Bedeutung |
|--------|-----------|
| `aktiv` | Domain ist DNSSEC-signiert und wird vom validierenden Resolver korrekt ausgeliefert |
| `inaktiv` | Domain ist **nicht** signiert (kein DNSSEC) |
| `fail` | DNSSEC vorhanden, aber Validierung schlägt fehl (SERVFAIL) |
Der Test ist nur aussagekräftig gegen einen **validierenden** Resolver (z. B. der
interne BIND mit `dnssec-validation auto;`). Beide Skripte führen daher zuerst
einen **Selbsttest** aus: `dnssec-failed.org` muss scheitern, `isc.org` muss
signiert auflösen. Schlägt der Selbsttest fehl, wird gewarnt.
## Bash (Linux/macOS)
```bash
sudo apt install bind9-dnsutils # falls dig fehlt
chmod +x dnssec_check
./dnssec_check # hinterlegte Liste, Resolver 127.0.0.1
./dnssec_check -r 192.168.1.10 # anderer Resolver
./dnssec_check --self-check # nur Resolver-Validierung prüfen
./dnssec_check isc.org citeq.de # nur diese Domains
```
## PowerShell (Windows)
```powershell
.\dnssec_check.ps1 # hinterlegte Liste, Resolver 127.0.0.1
.\dnssec_check.ps1 -Resolver 192.168.1.10
.\dnssec_check.ps1 -SelfCheck # nur Resolver-Validierung prüfen
.\dnssec_check.ps1 isc.org citeq.de # nur diese Domains
```
> Hinweis: `Resolve-DnsName` liefert das AD-Header-Flag nicht zurück. Die
> PowerShell-Variante wertet daher das Vorhandensein von **RRSIG-Records** aus.
> Ob der Resolver wirklich validiert, klärt der Selbsttest.
## Domains hinterlegen
Die zu prüfenden Domains stehen direkt im Skript:
- Bash: Array `DOMAINS=( … )` im Kopf
- PowerShell: `$DefaultDomains = @( … )` im Kopf
## Exit-Code (für Monitoring / cron / Scheduled Task)
`0` = kein `fail`, `1` = mindestens eine Domain `fail`. Der reine Selbsttest gibt
`0` (validiert), `1` (validiert nicht) bzw. `2` (Resolver nicht erreichbar) zurück.
Executable
+228
View File
@@ -0,0 +1,228 @@
#!/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
+210
View File
@@ -0,0 +1,210 @@
#Requires -Version 5.1
<#
.SYNOPSIS
dnssec_check.ps1 - prueft fuer hinterlegte Domains, ob DNSSEC aktiviert
und gueltig signiert ist. (Windows-Pendant zum
Bash-Skript dnssec_check.)
.DESCRIPTION
Zielsystem : Windows 10/11 / Windows Server (Windows PowerShell 5.1 oder
PowerShell 7+). Nutzt das native Cmdlet Resolve-DnsName
(Modul DnsClient) - kein dig noetig.
Status-Bedeutung:
aktiv - Domain ist DNSSEC-signiert UND wurde vom Resolver ausgeliefert
(Antwort erhalten + RRSIG-Records vorhanden)
inaktiv - Domain ist NICHT signiert (Antwort erhalten, keine RRSIG)
fail - DNSSEC vorhanden, aber Validierung schlaegt fehl / Fehler
(Abfrage scheitert; Gegenprobe mit -DnssecCd bestaetigt DNSSEC)
Hinweis: Resolve-DnsName liefert das AD-Header-Flag nicht zurueck. Statt
des AD-Flags wird das Vorhandensein von RRSIG-Records ausgewertet. Ob der
Resolver *wirklich* validiert, klaert der Selbsttest (kaputte Domain muss
scheitern). Ohne validierenden Resolver ist die Pruefung nicht aussagekraeftig.
.PARAMETER Resolver
DNS-Resolver, der geprueft wird (Default: 127.0.0.1 = lokaler DNS).
.PARAMETER QType
Abfragetyp (Default: A).
.PARAMETER SelfCheck
Nur pruefen, ob der Resolver DNSSEC validiert (keine Domainliste).
.PARAMETER Domains
Optional: Domains, die statt der hinterlegten Liste geprueft werden.
.EXAMPLE
.\dnssec_check.ps1
.EXAMPLE
.\dnssec_check.ps1 -Resolver 192.168.1.10
.EXAMPLE
.\dnssec_check.ps1 -SelfCheck
.EXAMPLE
.\dnssec_check.ps1 isc.org dnssec-failed.org
#>
[CmdletBinding()]
param(
[string] $Resolver = "127.0.0.1",
[string] $QType = "A",
[switch] $SelfCheck,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]] $Domains
)
# ------------------------------------------------------------------
# Konfiguration
# ------------------------------------------------------------------
# >>> Hier die zu pruefenden Domains eintragen <<<
$DefaultDomains = @(
"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
)
# Referenzdomains fuer den Selbsttest
$SelfBadDomain = "dnssec-failed.org" # bewusst falsch signiert (Verisign)
$SelfGoodDomain = "isc.org" # korrekt signiert
# ------------------------------------------------------------------
# Ab hier nichts mehr aendern
# ------------------------------------------------------------------
if (-not $Domains -or $Domains.Count -eq 0) {
$Domains = $DefaultDomains
}
# Cmdlet vorhanden?
if (-not (Get-Command Resolve-DnsName -ErrorAction SilentlyContinue)) {
Write-Error "Resolve-DnsName nicht gefunden. Benoetigt Windows PowerShell 5.1+ mit dem Modul DnsClient (Windows)."
exit 2
}
# Eine DNSSEC-fuehrende Abfrage; gibt die Records zurueck oder wirft.
function Invoke-DnsQuery {
param(
[string] $Name,
[string] $Type,
[switch] $Cd # -DnssecCd statt -DnssecOk (checking disabled)
)
$common = @{
Name = $Name
Type = $Type
Server = $Resolver
QuickTimeout = $true
DnsOnly = $true
ErrorAction = 'Stop'
}
if ($Cd) { Resolve-DnsName @common -DnssecCd }
else { Resolve-DnsName @common -DnssecOk }
}
# Kernpruefung: liefert 'aktiv' | 'inaktiv' | 'fail'
function Test-Domain {
param([string] $Domain)
try {
$ans = Invoke-DnsQuery -Name $Domain -Type $QType
}
catch {
# Fehler (SERVFAIL / NXDOMAIN / Timeout). Gegenprobe mit +cd:
# kommt MIT checking-disabled eine Antwort, lag es an der Validierung.
try { Invoke-DnsQuery -Name $Domain -Type $QType -Cd | Out-Null; return 'fail' }
catch { return 'fail' }
}
# Antwort erhalten -> signiert, wenn RRSIG-Records dabei sind
$hasRRSIG = @($ans | Where-Object { $_.Type -eq 'RRSIG' }).Count -gt 0
if ($hasRRSIG) { return 'aktiv' } else { return 'inaktiv' }
}
# Selbsttest: validiert der Resolver ueberhaupt?
# Rueckgabe (ueber die Pipeline): 0 = validiert, 1 = nein, 2 = unklar/Netzfehler
function Invoke-SelfCheck {
# 1) kaputte Domain: OHNE cd muss sie scheitern
$badFails = $false
try { Invoke-DnsQuery -Name $SelfBadDomain -Type A | Out-Null; $badFails = $false }
catch { $badFails = $true }
# 2) kaputte Domain MIT cd sollte aufloesbar sein
$cdOk = $false
try { Invoke-DnsQuery -Name $SelfBadDomain -Type A -Cd | Out-Null; $cdOk = $true }
catch { $cdOk = $false }
# 3) gute Domain muss aufloesen und signiert sein
$goodOk = $false; $goodSigned = $false
try {
$g = Invoke-DnsQuery -Name $SelfGoodDomain -Type A
$goodOk = $true
$goodSigned = @($g | Where-Object { $_.Type -eq 'RRSIG' }).Count -gt 0
} catch { $goodOk = $false }
# Erreichbarkeitsproblem? (nichts geht durch)
if (-not $cdOk -and -not $goodOk) {
Write-Host "Resolver-Selbsttest: " -NoNewline
Write-Host "UNKLAR" -ForegroundColor Yellow -NoNewline
Write-Host " (Resolver $Resolver nicht erreichbar?)"
return 2
}
if ($badFails -and $cdOk -and $goodOk -and $goodSigned) {
Write-Host "Resolver-Selbsttest: " -NoNewline
Write-Host "VALIDIERT DNSSEC" -ForegroundColor Green -NoNewline
Write-Host " ($SelfBadDomain scheitert, $SelfGoodDomain signiert+aufgeloest)"
return 0
}
else {
Write-Host "Resolver-Selbsttest: " -NoNewline
Write-Host "KEINE VALIDIERUNG" -ForegroundColor Red -NoNewline
Write-Host (" (bad_fails={0}, cd_ok={1}, good_ok={2}, good_signed={3})" -f `
$badFails, $cdOk, $goodOk, $goodSigned)
return 1
}
}
# ------------------------------------------------------------------
# Ausgabe
# ------------------------------------------------------------------
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Nur Selbsttest gewuenscht?
if ($SelfCheck) {
Write-Host "Resolver: $Resolver ($stamp)"
$rc = Invoke-SelfCheck
exit $rc
}
Write-Host "DNSSEC-Pruefung ueber Resolver: $Resolver ($stamp)"
# Zuerst pruefen, ob der Resolver validiert - sonst sind die Ergebnisse
# darunter nur eingeschraenkt aussagekraeftig.
$scRc = Invoke-SelfCheck
if ($scRc -ne 0) {
Write-Host "Hinweis: " -ForegroundColor Yellow -NoNewline
Write-Host 'Resolver validiert nicht/unklar - "aktiv/fail" unten sind nur eingeschraenkt verlaesslich.'
}
Write-Host ""
"{0,-40} {1}" -f "DOMAIN", "STATUS" | Write-Host
"{0,-40} {1}" -f ("-" * 40), "--------" | Write-Host
$exit = 0
foreach ($d in $Domains) {
$st = Test-Domain -Domain $d
switch ($st) {
'aktiv' { $col = 'Green' }
'inaktiv' { $col = 'Yellow' }
default { $col = 'Red'; $exit = 1 }
}
Write-Host ("{0,-40} " -f $d) -NoNewline
Write-Host $st -ForegroundColor $col
}
# Exit-Code: 0 = kein fail, 1 = mindestens ein fail (fuer Monitoring/Scheduled Task)
exit $exit