diff --git a/README.md b/README.md index a0493a9..d4d01a9 100644 --- a/README.md +++ b/README.md @@ -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. - Die Standardausgabe orientiert sich am `dig`-Format mit `QUESTION`- und `ANSWER SECTION`. - 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. diff --git a/dnssec_check b/dnssec_check new file mode 100755 index 0000000..bc401a0 --- /dev/null +++ b/dnssec_check @@ -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 </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 diff --git a/dnssec_check.ps1 b/dnssec_check.ps1 new file mode 100644 index 0000000..1b342e3 --- /dev/null +++ b/dnssec_check.ps1 @@ -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