#!/usr/bin/env bash
# check_k8s_pki_certs
# Vérifie les certificats PEM sous /etc/kubernetes/pki (par défaut) et alerte si expiration <= warn_days (30j par défaut).
# 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        répertoire à scanner (default: $PKI_PATH)
  --warn-days N      seuil warning en jours (default: $WARN_DAYS)
  --crit-days M      seuil critical en jours (default: $CRIT_DAYS)
  --recursive        scanner récursivement PATH et sous-dirs
  -h, --help         affiche cette aide
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