You've already forked nrpe
194 lines
5.4 KiB
Bash
Executable File
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 |