Files
homelab-infra/services/posture-check/cert-token-check.sh
T
2026-05-25 14:44:46 +02:00

145 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
OUTPUT_PATH="${OUTPUT_PATH:-/mnt/user/services/posture-check/cert-token-last.json}"
NTFY_BASE_URL="${NTFY_BASE_URL:-https://ntfy.kaleschke.info}"
WARNING_TOPIC="${WARNING_TOPIC:-homelab-alerts}"
CRITICAL_TOPIC="${CRITICAL_TOPIC:-homelab-alerts}"
SEND_NTFY="${SEND_NTFY:-1}"
CLOUDFLARE_TOKEN_FILE="${CLOUDFLARE_TOKEN_FILE:-/mnt/user/appdata/traefik/secrets/cloudflare_dns_api_token}"
WARN_DAYS="${WARN_DAYS:-14}"
CRITICAL_DAYS="${CRITICAL_DAYS:-7}"
DOMAINS="${DOMAINS:-traefik.kaleschke.info auth.kaleschke.info vault.kaleschke.info git.kaleschke.info cloud.kaleschke.info glance.kaleschke.info borg.kaleschke.info monitoring.kaleschke.info ntfy.kaleschke.info}"
TMP_DIR="${TMP_DIR:-/tmp/kallilab-cert-token-check}"
mkdir -p "$TMP_DIR"
RESULTS_FILE="$TMP_DIR/results.$$"
: > "$RESULTS_FILE"
trap 'rm -f "$RESULTS_FILE"' EXIT
json_escape() {
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g'
}
add_result() {
printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$RESULTS_FILE"
}
check_cert() {
local domain="$1"
local enddate
local end_epoch
local now_epoch
local days_left
if ! enddate="$(printf '' | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2-)"; then
add_result "critical" "cert_$domain" "Cannot read certificate for $domain"
return
fi
end_epoch="$(date -d "$enddate" +%s)"
now_epoch="$(date +%s)"
days_left="$(( (end_epoch - now_epoch) / 86400 ))"
if [ "$days_left" -lt "$CRITICAL_DAYS" ]; then
add_result "critical" "cert_$domain" "$domain certificate expires in ${days_left}d"
elif [ "$days_left" -lt "$WARN_DAYS" ]; then
add_result "warning" "cert_$domain" "$domain certificate expires in ${days_left}d"
else
add_result "ok" "cert_$domain" "$domain certificate expires in ${days_left}d"
fi
}
check_cloudflare_token() {
local token
local response
if [ ! -s "$CLOUDFLARE_TOKEN_FILE" ]; then
add_result "critical" "cloudflare_token" "Token file missing or empty: $CLOUDFLARE_TOKEN_FILE"
return
fi
token="$(cat "$CLOUDFLARE_TOKEN_FILE")"
if ! response="$(curl -fsS -H "Authorization: Bearer $token" https://api.cloudflare.com/client/v4/user/tokens/verify 2>/dev/null)"; then
add_result "critical" "cloudflare_token" "Cloudflare token verify request failed"
return
fi
if printf '%s' "$response" | grep -q '"success"[[:space:]]*:[[:space:]]*true'; then
add_result "ok" "cloudflare_token" "Cloudflare token verify succeeded"
else
add_result "critical" "cloudflare_token" "Cloudflare token verify returned non-success"
fi
}
send_ntfy() {
local severity="$1"
local topic="$2"
local body="$3"
if [ "$SEND_NTFY" != "1" ] || ! command -v curl >/dev/null 2>&1; then
return
fi
printf '%s\n' "$body" | curl -fsS \
-H "Title: KalliLab cert-token-check $severity" \
-H "Priority: high" \
--data-binary @- \
"$NTFY_BASE_URL/$topic" >/dev/null || true
}
write_json() {
local timestamp
local critical_count
local warning_count
local status
local first=1
timestamp="$(date -Iseconds)"
critical_count="$(awk -F '\t' '$1 == "critical" { count++ } END { print count + 0 }' "$RESULTS_FILE")"
warning_count="$(awk -F '\t' '$1 == "warning" { count++ } END { print count + 0 }' "$RESULTS_FILE")"
if [ "$critical_count" -gt 0 ]; then
status="critical"
elif [ "$warning_count" -gt 0 ]; then
status="warning"
else
status="ok"
fi
mkdir -p "$(dirname "$OUTPUT_PATH")"
{
printf '{\n'
printf ' "timestamp": "%s",\n' "$(printf '%s' "$timestamp" | json_escape)"
printf ' "status": "%s",\n' "$status"
printf ' "critical_count": %s,\n' "$critical_count"
printf ' "warning_count": %s,\n' "$warning_count"
printf ' "checks": [\n'
while IFS="$(printf '\t')" read -r severity name message; do
if [ "$first" -eq 0 ]; then printf ',\n'; fi
first=0
printf ' {"severity":"%s","name":"%s","message":"%s"}' \
"$(printf '%s' "$severity" | json_escape)" \
"$(printf '%s' "$name" | json_escape)" \
"$(printf '%s' "$message" | json_escape)"
done < "$RESULTS_FILE"
printf '\n ]\n}\n'
} > "$OUTPUT_PATH.tmp"
mv "$OUTPUT_PATH.tmp" "$OUTPUT_PATH"
cat "$OUTPUT_PATH"
if [ "$status" = "critical" ]; then
send_ntfy critical "$CRITICAL_TOPIC" "Certificate/token check critical: $critical_count critical, $warning_count warning. See $OUTPUT_PATH"
return 2
elif [ "$status" = "warning" ]; then
send_ntfy warning "$WARNING_TOPIC" "Certificate/token check warning: $warning_count warning. See $OUTPUT_PATH"
return 1
fi
}
for domain in $DOMAINS; do
check_cert "$domain"
done
check_cloudflare_token
write_json