Add standalone Bash incident check scripts
lint / shell-yaml-ansible (push) Failing after 16s

This commit is contained in:
Mateusz Suski
2026-05-11 18:49:00 +00:00
parent 8a7b7c5abc
commit e851568c8c
27 changed files with 1623 additions and 6 deletions
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
host_name=""
port=443
cert_file=""
warning_days=30
critical_days=7
servername=""
usage() {
cat <<'USAGE'
Usage: check_certificate_expiry.sh (--host HOST [--port PORT] | --file CERT_FILE) [--servername SNI_NAME] [--warning-days DAYS] [--critical-days DAYS] [--help]
Check TLS certificate expiry for a remote endpoint or local certificate file.
USAGE
}
is_number() {
[[ "$1" =~ ^[0-9]+$ ]]
}
while (($# > 0)); do
case "$1" in
--host) [[ $# -ge 2 ]] || { printf 'CRITICAL: --host requires a value\n'; exit 2; }; host_name="$2"; shift 2 ;;
--port) [[ $# -ge 2 ]] || { printf 'CRITICAL: --port requires a value\n'; exit 2; }; port="$2"; shift 2 ;;
--file) [[ $# -ge 2 ]] || { printf 'CRITICAL: --file requires a value\n'; exit 2; }; cert_file="$2"; shift 2 ;;
--servername) [[ $# -ge 2 ]] || { printf 'CRITICAL: --servername requires a value\n'; exit 2; }; servername="$2"; shift 2 ;;
--warning-days) [[ $# -ge 2 ]] || { printf 'CRITICAL: --warning-days requires a value\n'; exit 2; }; warning_days="$2"; shift 2 ;;
--critical-days) [[ $# -ge 2 ]] || { printf 'CRITICAL: --critical-days requires a value\n'; exit 2; }; critical_days="$2"; shift 2 ;;
--help|-h) usage; exit 0 ;;
*) printf 'CRITICAL: unknown option: %s\n' "$1"; usage; exit 2 ;;
esac
done
if ! command -v openssl >/dev/null 2>&1; then
printf 'CRITICAL: required command not found: openssl\n'
exit 2
fi
for value in "$port" "$warning_days" "$critical_days"; do
if ! is_number "$value"; then
printf 'CRITICAL: numeric option expected, got: %s\n' "$value"
exit 2
fi
done
if ((critical_days >= warning_days)); then
printf 'CRITICAL: --critical-days must be lower than --warning-days\n'
exit 2
fi
if [[ -n "$host_name" && -n "$cert_file" ]]; then
printf 'CRITICAL: use either --host or --file, not both\n'
exit 2
fi
if [[ -z "$host_name" && -z "$cert_file" ]]; then
printf 'CRITICAL: either --host or --file is required\n'
usage
exit 2
fi
if [[ -n "$cert_file" && ! -r "$cert_file" ]]; then
printf 'CRITICAL: certificate file is not readable: %s\n' "$cert_file"
exit 2
fi
if [[ -z "$servername" ]]; then
servername="$host_name"
fi
tmp_cert="$(mktemp)"
trap 'rm -f "$tmp_cert"' EXIT
if [[ -n "$host_name" ]]; then
if ! openssl s_client -connect "${host_name}:${port}" -servername "$servername" -showcerts </dev/null 2>/dev/null \
| openssl x509 -outform PEM > "$tmp_cert" 2>/dev/null; then
printf 'CRITICAL: unable to retrieve certificate from %s:%s\n' "$host_name" "$port"
exit 2
fi
else
cp "$cert_file" "$tmp_cert"
fi
subject="$(openssl x509 -in "$tmp_cert" -noout -subject 2>/dev/null | sed 's/^subject=//')"
issuer="$(openssl x509 -in "$tmp_cert" -noout -issuer 2>/dev/null | sed 's/^issuer=//')"
not_before="$(openssl x509 -in "$tmp_cert" -noout -startdate 2>/dev/null | sed 's/^notBefore=//')"
not_after="$(openssl x509 -in "$tmp_cert" -noout -enddate 2>/dev/null | sed 's/^notAfter=//')"
san_text="$(openssl x509 -in "$tmp_cert" -noout -ext subjectAltName 2>/dev/null | sed '1d' | sed 's/^ *//')"
expiry_epoch="$(date -d "$not_after" +%s 2>/dev/null || printf '')"
now_epoch="$(date +%s)"
if [[ -z "$expiry_epoch" ]]; then
printf 'CRITICAL: unable to parse certificate expiry date: %s\n' "$not_after"
exit 2
fi
seconds_left=$((expiry_epoch - now_epoch))
days_left=$((seconds_left / 86400))
status="OK"
exit_code=0
if ((days_left < critical_days)); then
status="CRITICAL"
exit_code=3
elif ((days_left < warning_days)); then
status="WARNING"
exit_code=1
fi
target="$cert_file"
if [[ -n "$host_name" ]]; then
target="${host_name}:${port}"
fi
printf '%s: Certificate for %s expires in %s day(s)\n\n' "$status" "$target" "$days_left"
printf 'Certificate details:\n'
printf 'Subject: %s\n' "$subject"
printf 'Issuer: %s\n' "$issuer"
printf 'notBefore: %s\n' "$not_before"
printf 'notAfter: %s\n' "$not_after"
printf 'SAN/CN: %s\n' "${san_text:-$subject}"
printf '\n'
printf 'Evidence:\n'
printf 'Target: %s\n' "$target"
printf 'SNI: %s\n' "${servername:-not used}"
printf 'Thresholds: warning=%s days critical=%s days\n\n' "$warning_days" "$critical_days"
printf 'Recommended next steps:\n'
printf -- '- Renew certificate before the operational threshold is breached\n'
printf -- '- Check the full chain and intermediate certificates\n'
printf -- '- Check the load balancer, ingress, or reverse proxy serving this certificate\n'
printf -- '- Verify monitoring threshold and alert ownership\n'
printf -- '- Attach this output to incident or change ticket\n'
exit "$exit_code"