TLSA | Validiere TLSA Records mit CheckMK
🧩 Checkmk TLSA Validation Plugin
Dieses Dokument beschreibt, wie du das TLSA Validation Script als Active Check in Checkmk einbindest und über ein Custom Attribute Ports pro Host konfigurierbar machst.
📜 1. Skriptinstallation
Kopiere das Skript check_tlsa nach:
/usr/lib/nagios/plugins/check_tlsa
chmod +x /usr/lib/nagios/plugins/check_tlsa
Das Skript prüft TLSA-DNS-Records für SMTP/IMAP/POP3/HTTPS Ports und gibt eine Checkmk-kompatible Ausgabe zurück.
⚙️ 2. Custom Attribute in Checkmk
Erstelle ein benutzerdefiniertes Host-Attribut:
| Feldname | Wert |
|---|---|
| Name | TLSA_PORTS |
| Label | TLSA Ports |
| Type | Text |
| Default Value | (optional z. B. 25,465,587) |
| Description | Ports für TLSA-Check |
Speichern und Änderungen aktivieren.
🔍 3. Active Check Regel in WATO
Gehe zu:
Setup → Active Checks → Classical active and passive Monitoring checks
Erstelle eine neue Regel mit:
| Parameter | Wert |
|---|---|
| Command line | $USER1$/check_tlsa -d $HOSTADDRESS$ -p $_HOSTTLSA_PORTS$ |
| Service description | TLSA Check |
Zuweisung: Nur für Hosts aktivieren, die TLSA verwenden.
🖥️ 4. Hostkonfiguration
Unter Custom Attributes im Host:
| Attribut | Beispielwert |
|---|---|
TLSA_PORTS |
25,587,465 |
Das Skript prüft dann nur diese Ports.
📊 5. Beispielausgabe
OK - TLSA Validation erfolgreich
Port 25: OK - TLSA stimmt überein
Port 587: OK - kein TLSA Eintrag vorhanden
Port 465: CRIT - TLSA Mismatch (usage=3 selector=1 mtype=1) | ports=3 ok=2 warn=0 crit=1
0→ OK1→ WARN2→ CRIT3→ UNKNOWN
Perfdata: ports, ok, warn, crit
🧩 6. Alternative Nutzung (Local Check)
Wenn du TLSA lokal auf einem Host prüfen willst:
- Skript nach
/usr/lib/check_mk_agent/local/kopieren - Ausgabeformat anpassen:
0 TLSA-Check - OK - TLSA Validation erfolgreich
Dann wird der Check direkt vom Agenten geliefert.
✅ Vorteile der Active-Check-Variante
- Zentrale TLSA-Prüfung von Monitoring-Server
- Keine Konfiguration pro Agent nötig
- Dynamische Portwahl per Custom Attribute
- Perfekte Integration in Checkmk-Checklogik
Script check_tlsa
#!/usr/bin/env bash
# check_tlsa robust
# !!! set -e entfernt, nur -u beibehalten
set -uo pipefail
usage() {
echo "Usage: $0 -d <domain> -p <ports>"
exit 3
}
DOMAIN=""
PORTS=""
while getopts ":d:p:" opt; do
case "$opt" in
d) DOMAIN="$OPTARG" ;;
p) PORTS="$OPTARG" ;;
*) usage ;;
esac
done
if [[ -z "${DOMAIN}" ]]; then
usage
fi
if [[ -z "${PORTS}" ]]; then
echo "UNKNOWN - keine Ports angegeben"
exit 3
fi
IFS=',' read -r -a ports <<< "$PORTS"
dig_cmd() {
dig +short TLSA "$1" 2>/dev/null || true
}
fetch_leaf_cert_pem() {
local host="$1" port="$2" sni="$3"
local starttls_opt=""
case "$port" in
25|587) starttls_opt="-starttls smtp" ;;
110) starttls_opt="-starttls pop3" ;;
143) starttls_opt="-starttls imap" ;;
esac
openssl s_client -connect "${host}:${port}" -servername "${sni}" $starttls_opt -showcerts < /dev/null 2>/dev/null \
| awk '/-----BEGIN CERTIFICATE-----/{p=1} p{print} /-----END CERTIFICATE-----/{print; exit}'
}
pem_to_der() {
local pem_data="$1" out="$2"
printf "%s\n" "$pem_data" > /tmp/_tlsa_tmp_cert.pem
openssl x509 -in /tmp/_tlsa_tmp_cert.pem -outform der -out "$out" 2>/dev/null || return 1
}
pem_to_spki_der() {
local pem_data="$1" out="$2"
printf "%s\n" "$pem_data" > /tmp/_tlsa_tmp_cert.pem
openssl x509 -in /tmp/_tlsa_tmp_cert.pem -pubkey -noout 2>/dev/null \
| openssl pkey -pubin -outform der -out "$out" 2>/dev/null || return 1
}
normalize_hex() {
echo "$1" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]'
}
overall_status=0
crit_count=0
warn_count=0
ok_count=0
messages=()
for p in "${ports[@]}"; do
qname="_${p}._tcp.${DOMAIN}."
tlsa_lines=$(dig_cmd "$qname")
if [[ -z "$tlsa_lines" ]]; then
messages+=("Port $p: OK - kein TLSA Eintrag vorhanden")
((ok_count++))
continue
fi
cert_pem=$(fetch_leaf_cert_pem "$DOMAIN" "$p" "$DOMAIN")
if [[ -z "$cert_pem" ]]; then
messages+=("Port $p: CRIT - kein Zertifikat abrufbar")
((crit_count++))
overall_status=2
continue
fi
port_status=0
matched=0
errors=()
while IFS= read -r line; do
[[ -z "$line" ]] && continue
line=$(echo "$line" | sed -e 's/^"//' -e 's/"$//')
usage_val=$(echo "$line" | awk '{print $1}')
selector=$(echo "$line" | awk '{print $2}')
mtype=$(echo "$line" | awk '{print $3}')
data=$(echo "$line" | cut -d' ' -f4-)
data=$(normalize_hex "$data")
tmp_der="/tmp/_tlsa_${p}_$$.der"
if [[ "$selector" == "0" ]]; then
pem_to_der "$cert_pem" "$tmp_der" || { errors+=("Fehler beim Konvertieren (full cert)"); port_status=2; continue; }
else
pem_to_spki_der "$cert_pem" "$tmp_der" || { errors+=("Fehler beim Konvertieren (SPKI)"); port_status=2; continue; }
fi
case "$mtype" in
0) got=$(xxd -p -c 999999 "$tmp_der" | tr -d '\n') ;;
1) got=$(sha256sum "$tmp_der" | awk '{print $1}') ;;
2) got=$(sha512sum "$tmp_der" | awk '{print $1}') ;;
*) errors+=("unbekannter mtype=$mtype"); port_status=1; continue ;;
esac
rm -f "$tmp_der" /tmp/_tlsa_tmp_cert.pem
if [[ -n "${got}" && "${got}" == "${data}" ]]; then
matched=1
else
errors+=("TLSA Mismatch (usage=$usage_val selector=$selector mtype=$mtype)")
port_status=2
fi
done <<< "$tlsa_lines"
if ((port_status == 0 && matched == 1)); then
messages+=("Port $p: OK - TLSA stimmt überein")
((ok_count++))
elif ((port_status == 1)); then
messages+=("Port $p: WARN - ${errors[*]}")
((warn_count++))
((overall_status = overall_status < 1 ? 1 : overall_status))
else
messages+=("Port $p: CRIT - ${errors[*]}")
((crit_count++))
overall_status=2
fi
done
output=$(printf "%s\n" "${messages[@]}")
if ((overall_status == 0)); then
echo -e "OK - TLSA Validation erfolgreich\n$output | ports=${#ports[@]} ok=$ok_count warn=$warn_count crit=$crit_count"
elif ((overall_status == 1)); then
echo -e "WARN - TLSA Probleme erkannt\n$output | ports=${#ports[@]} ok=$ok_count warn=$warn_count crit=$crit_count"
else
echo -e "CRIT - TLSA Fehler erkannt\n$output | ports=${#ports[@]} ok=$ok_count warn=$warn_count crit=$crit_count"
fi
exit $overall_status