Files
nrpe/files/nrpe/check_k8s_pki_certs
T
2026-06-01 17:49:46 +02:00

194 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# check_k8s_pki_certs
# Checks PEM certificates under /etc/kubernetes/pki (by default) and alerts if expiry <= warn_days (30d by default).
# Exit codes: 0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN
#
# Usage:
# sudo /usr/lib/nagios/plugins/check_k8s_pki_certs
# sudo /usr/lib/nagios/plugins/check_k8s_pki_certs --path /etc/kubernetes/ssl --warn-days 30 --crit-days 7 --recursive
#
set -euo pipefail
PKI_PATH=${PKI_PATH:-/etc/kubernetes/pki}
WARN_DAYS=${WARN_DAYS:-30}
CRIT_DAYS=${CRIT_DAYS:-7}
RECURSIVE=0
print_usage() {
cat <<EOF
Usage: $0 [--path PATH] [--warn-days N] [--crit-days M] [--recursive] [-h|--help]
Options:
--path PATH directory to scan (default: $PKI_PATH)
--warn-days N warning threshold in days (default: $WARN_DAYS)
--crit-days M critical threshold in days (default: $CRIT_DAYS)
--recursive scan PATH and subdirectories recursively
-h, --help show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--path) PKI_PATH="$2"; shift 2;;
--warn-days) WARN_DAYS="$2"; shift 2;;
--crit-days) CRIT_DAYS="$2"; shift 2;;
--recursive) RECURSIVE=1; shift 1;;
-h|--help) print_usage; exit 3;;
*) echo "Unknown arg: $1"; print_usage; exit 3;;
esac
done
# tools
if ! command -v openssl >/dev/null 2>&1; then
echo "UNKNOWN - openssl not found"
exit 3
fi
if ! command -v date >/dev/null 2>&1; then
echo "UNKNOWN - date not found"
exit 3
fi
if ! command -v sed >/dev/null 2>&1; then
echo "UNKNOWN - sed not found"
exit 3
fi
if ! command -v awk >/dev/null 2>&1; then
echo "UNKNOWN - awk not found"
exit 3
fi
if ! command -v find >/dev/null 2>&1; then
echo "UNKNOWN - find not found"
exit 3
fi
# resolve symlink target (realpath or readlink -f)
if command -v realpath >/dev/null 2>&1; then
PKI_PATH_RESOLVED=$(realpath -e "$PKI_PATH" 2>/dev/null || true)
else
PKI_PATH_RESOLVED=$(readlink -f "$PKI_PATH" 2>/dev/null || true)
fi
if [[ -n "$PKI_PATH_RESOLVED" && -d "$PKI_PATH_RESOLVED" ]]; then
PKI_PATH="$PKI_PATH_RESOLVED"
fi
if [[ ! -d "$PKI_PATH" ]]; then
echo "UNKNOWN - path $PKI_PATH not found or not a directory"
exit 3
fi
now_s=$(date +%s)
# Initialize arrays explicitly to avoid "variable sans liaison" with set -u
critical=()
warning=()
ok=()
errors=()
file_count=0
cert_count=0
# build find command: follow symlinks (-L) so that symlinked directories/files are handled
if [[ $RECURSIVE -eq 1 ]]; then
FIND_CMD=(find -L "$PKI_PATH" -type f -print0)
else
FIND_CMD=(find -L "$PKI_PATH" -maxdepth 1 -type f -print0)
fi
# iterate files found
while IFS= read -r -d '' file; do
file_count=$((file_count+1))
# skip unreadable files
if [[ ! -r "$file" ]]; then
errors+=("Unreadable file: $file")
continue
fi
# skip files without PEM marker
if ! grep -q "BEGIN CERTIFICATE" "$file" 2>/dev/null; then
continue
fi
# find pairs of BEGIN/END certificate line numbers robustly using awk
# prints "start:end" for each certificate block
mapfile -t pairs < <(awk '
/BEGIN CERTIFICATE/ {start=NR}
/END CERTIFICATE/ && start { print start ":" NR; start=0 }
' "$file" 2>/dev/null || true)
if [[ ${#pairs[@]} -eq 0 ]]; then
errors+=("No certificate block pairs found in $file")
continue
fi
for p in "${pairs[@]}"; do
start=${p%%:*}
end=${p##*:}
# extract block via sed (line range), send to openssl via stdin
cert_block=$(sed -n "${start},${end}p" "$file" 2>/dev/null || true)
if [[ -z "$cert_block" ]]; then
errors+=("Failed to extract certificate block ${start}-${end} from $file")
continue
fi
# openssl expects a file or stdin; use stdin
endline=$(printf '%s\n' "$cert_block" | openssl x509 -noout -enddate -in /dev/stdin 2>/dev/null) || {
errors+=("Failed to parse certificate block ${start}-${end} in $file with openssl")
continue
}
# sample endline: notAfter=Oct 27 16:15:30 2125 GMT
notAfter=${endline#notAfter=}
expiry_s=$(date -d "$notAfter" +%s 2>/dev/null) || {
errors+=("Cannot parse date '$notAfter' for cert in $file")
continue
}
days_left=$(( (expiry_s - now_s) / 86400 ))
subj=$(printf '%s\n' "$cert_block" | openssl x509 -noout -subject -in /dev/stdin 2>/dev/null || true)
subj=${subj#subject= }
info="${file} :: ${subj} :: expires in ${days_left}d on ${notAfter}"
cert_count=$((cert_count+1))
if (( days_left <= CRIT_DAYS )); then
critical+=("$info")
elif (( days_left <= WARN_DAYS )); then
warning+=("$info")
else
ok+=("$info")
fi
done
done < <("${FIND_CMD[@]}")
# results and exit codes
if [[ ${#errors[@]} -gt 0 ]]; then
echo "UNKNOWN - parsing errors: ${errors[*]}"
exit 3
fi
if (( cert_count == 0 )); then
echo "UNKNOWN - no certificates found under $PKI_PATH"
exit 3
fi
if (( ${#critical[@]} > 0 )); then
echo "CRITICAL - ${#critical[@]} certificate(s) expiring soon (<= ${CRIT_DAYS} days):"
for c in "${critical[@]}"; do
echo " - $c"
done
if (( ${#warning[@]} > 0 )); then
echo "WARN (additional ${#warning[@]} cert(s) <= ${WARN_DAYS} days):"
for w in "${warning[@]}"; do
echo " - $w"
done
fi
exit 2
fi
if (( ${#warning[@]} > 0 )); then
echo "WARNING - ${#warning[@]} certificate(s) expiring within ${WARN_DAYS} days:"
for w in "${warning[@]}"; do
echo " - $w"
done
exit 1
fi
echo "OK - ${cert_count} cert(s) checked in ${file_count} file(s), no expiry within ${WARN_DAYS} days"
exit 0