nagios/check_expiration_domain: first draft
This commit is contained in:
parent
70c9730237
commit
52746fd980
1 changed files with 369 additions and 0 deletions
369
nagios/check_expiration_domain_name_rdap_whois.sh
Executable file
369
nagios/check_expiration_domain_name_rdap_whois.sh
Executable file
|
@ -0,0 +1,369 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Monitoring script to check the expiration of domain names via RDAP and WHOIS.
|
||||
#
|
||||
# Copyright: chl-dev@bugness.org 2025 https://code.bugness.org/
|
||||
# Licence: WTFPL
|
||||
|
||||
# Stop at first uncaught error
|
||||
set -e
|
||||
|
||||
PROGPATH=$( echo $0 | sed -e 's,[\\/][^\\/][^\\/]*$,,' )
|
||||
REVISION="0.2"
|
||||
|
||||
# Default values
|
||||
IANA_URL_DOMAINS="https://data.iana.org/rdap/dns.json"
|
||||
IANA_CACHE_FILE="/tmp/.check_expiration_domain_name.cache.json"
|
||||
IANA_CACHE_DURATION=$(( 86400 * 30 ))
|
||||
THRESHOLD_WARNING="2592000" # 30 days
|
||||
THRESHOLD_CRITICAL="691200" # 8 days
|
||||
TIMESTAMP_NOW="$( date +%s )"
|
||||
OPTION_RDAP_QUERYING="yes"
|
||||
OPTION_WHOIS_QUERYING="yes"
|
||||
|
||||
# Include check_range()
|
||||
# Not needed at the moment
|
||||
#. $PROGPATH/utils.sh
|
||||
STATE_OK=0
|
||||
STATE_WARNING=1
|
||||
STATE_CRITICAL=2
|
||||
STATE_UNKNOWN=3
|
||||
STATE_DEPENDENT=4
|
||||
|
||||
# Output
|
||||
OUTPUT_EXIT_STATUS=0
|
||||
OUTPUT_DETAIL_WARNING=""
|
||||
OUTPUT_DETAIL_CRITICAL=""
|
||||
OUTPUT_PERFDATA=""
|
||||
|
||||
#
|
||||
# Help function
|
||||
#
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage :
|
||||
$0 [-w warning_threshold] [-c critical_threshold] [-rR] [-wW] domain1 [domain2] ...
|
||||
|
||||
-r enable RDAP querying $( test -n "$OPTION_RDAP_QUERYING" && echo '(default)' )
|
||||
-R disable RDAP querying $( test -z "$OPTION_RDAP_QUERYING" && echo '(default)' )
|
||||
-i enable WHOIS querying $( test -n "$OPTION_WHOIS_QUERYING" && echo '(default)' )
|
||||
-I disable WHOIS querying $( test -z "$OPTION_WHOIS_QUERYING" && echo '(default)' )
|
||||
|
||||
Thresholds in seconds (or suffix by 'd' for days).
|
||||
|
||||
|
||||
Default values:
|
||||
warning_threshold : $THRESHOLD_WARNING
|
||||
critical_threshold : $THRESHOLD_CRITICAL
|
||||
IANA URL : $IANA_URL_DOMAINS
|
||||
IANA cache file : $IANA_CACHE_FILE
|
||||
IANA cache duration : $IANA_CACHE_DURATION second(s)
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# Days/Months/Hours to seconds convert function
|
||||
#
|
||||
convert_to_seconds() {
|
||||
local UNIT VALUE
|
||||
UNIT="$( echo "$1" | sed 's/.*\(.\)$/\1/;q' )"
|
||||
VALUE="$( echo "$1" | sed 's/^\([0-9]*\).*/\1/;q' )"
|
||||
|
||||
case "$UNIT" in
|
||||
'd')
|
||||
# Unit is days
|
||||
echo "$( expr "$VALUE" \* 86400 )"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "$1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#
|
||||
# Wrapper to use whatever is available to make HTTP queries
|
||||
#
|
||||
fetch_with_curl_wget_or_whatever () {
|
||||
if which "wget" >/dev/null 2>&1; then
|
||||
wget -q -O - "$1"
|
||||
elif which "curl" >/dev/null 2>&1 ; then
|
||||
curl -s --fail "$1"
|
||||
else
|
||||
echo "No wget/curl/whatever available to make HTTP queries." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Echo the root IANA database
|
||||
#
|
||||
cat_iana_dns() {
|
||||
local PREVIOUS_NOCLOBBER_STATE_CMD="$( set +o | grep noclobber )"
|
||||
|
||||
# Check that our cache file exists, has not been emptily created by mistake,
|
||||
# and is sufficiently recent. We'll also check that the file belongs to us
|
||||
# afterwards.
|
||||
if [ ! -f "$IANA_CACHE_FILE" ] || [ ! -s "$IANA_CACHE_FILE" ] || [ "$( stat -c %Y "$IANA_CACHE_FILE" )" -lt "$(( $( date +%s ) - $IANA_CACHE_DURATION ))" ]; then
|
||||
rm -f "$IANA_CACHE_FILE"
|
||||
# For security, we activate the 'noclobber' : if a smartass raced us in
|
||||
# creating the file (or a symlink to a dangerous file), the '>' will simply
|
||||
# fail.
|
||||
set -C
|
||||
if ! ERROR_FETCH="$( fetch_with_curl_wget_or_whatever "$IANA_URL_DOMAINS" 2>&1 >"$IANA_CACHE_FILE" )"; then
|
||||
echo "failed to retrieve IANA database ($ERROR_FETCH)"
|
||||
$PREVIOUS_NOCLOBBER_STATE_CMD
|
||||
return 1
|
||||
fi
|
||||
$PREVIOUS_NOCLOBBER_STATE_CMD
|
||||
fi
|
||||
|
||||
# Check that the file belongs to us
|
||||
if [ ! -O "$IANA_CACHE_FILE" ]; then
|
||||
echo "Cache file ($IANA_CACHE_FILE) belongs to another user: stopping for security risk."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Output
|
||||
cat "$IANA_CACHE_FILE"
|
||||
}
|
||||
|
||||
#
|
||||
# Get expiration date from RDAP
|
||||
# (output the date as seconds since Unix epoch)
|
||||
# (error when exit status is != 0 or empty output)
|
||||
#
|
||||
get_expiration_date_rdap() {
|
||||
local DOMAIN="$1"
|
||||
local ROOT="$1"
|
||||
|
||||
# We truncate and loop until we find a suitable RDAP server (wikipedia.co.uk -> co.uk -> uk)
|
||||
while true; do
|
||||
# Truncate the first part of the domain name
|
||||
ROOT="$( echo "$ROOT" | sed 's/[^.]\+.\?//' )"
|
||||
test -n "$ROOT" || break
|
||||
|
||||
# Search for the RDAP servers
|
||||
if ! LIST_SERVERS="$( cat_iana_dns )" || ! LIST_SERVERS="$( echo "$LIST_SERVERS" | jq -r ".services[] | select(.[][] == \"$ROOT\") | .[1][]" )"; then
|
||||
echo "Error getting server list: $LIST_SERVERS"
|
||||
return 1
|
||||
fi
|
||||
# Try every server (usually only one ?)
|
||||
for SERVER in $LIST_SERVERS; do
|
||||
# Query the server
|
||||
# (small regexp to avoid some double '/' in the URL)
|
||||
if OUTPUT="$( fetch_with_curl_wget_or_whatever "$( echo "$SERVER" | sed 's#/$##' )/domain/$DOMAIN" 2>&1 )"; then
|
||||
EXPIRATION_DATE="$( echo "$OUTPUT" | jq -r '.events[] | select(."eventAction" == "expiration") | .eventDate' )"
|
||||
# Small protection against injection
|
||||
if [ -n "$( echo "$EXPIRATION_DATE" | LANG=C sed -n '/^[a-zA-Z0-9:.\-]\{1,64\}$/p' )" ]; then
|
||||
# We directly output the date as seconds from epoch.
|
||||
date --date="$EXPIRATION_DATE" +%s
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
# If we have a non-empty list of servers but didn't manage to get an
|
||||
# expiration date, we return an error.
|
||||
if [ -n "$LIST_SERVERS" ]; then
|
||||
echo "All servers ($LIST_SERVERS) failed for $DOMAIN / $ROOT (last output: $OUTPUT)"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "No RDAP server for $DOMAIN"
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Get expiration date from WHOIS
|
||||
# (output the date as seconds since Unix epoch)
|
||||
# (error when exit status is != 0 or empty output)
|
||||
#
|
||||
get_expiration_date_whois() {
|
||||
local DOMAIN="$1"
|
||||
local EXPIRATION_DATE
|
||||
|
||||
# WHOIS query outside of the sed pipe to catch erroneous exit value.
|
||||
if ! WHOIS_OUTPUT="$( whois "$DOMAIN" )"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# We put each format on a dedicated line
|
||||
# + filter only the first line
|
||||
# Note : some sed implementations seems not to offer
|
||||
# case-insensitive option, so we try not to
|
||||
# use it for the moment.
|
||||
# 1 - kernel.org, icann.org
|
||||
# 2 - coca-cola.com (we try to ignore lines starting with space...)
|
||||
# 3 - facebook.com (...but if there's nothing else, take it anyway)
|
||||
# 4 - some databases with simple display (.se, .si)
|
||||
# 5 - .it
|
||||
# 6 - .ru
|
||||
# 7 - .jp
|
||||
# Add your own filter here :)
|
||||
EXPIRATION_DATE="$( echo "$WHOIS_OUTPUT" | sed -n \
|
||||
-e 's/^Registry Expiry Date:[[:space:]]\+//p' \
|
||||
-e '/^[^[:space:]]/s/.*\(xpiry\|xpiration\) Date:[[:space:]]\+//p' \
|
||||
-e 's/.*\(xpiry\|xpiration\) Date:[[:space:]]\+//p' \
|
||||
-e 's/^expires\?:[[:space:]]*//p' \
|
||||
-e 's/^Expire Date:[[:space:]]*//p' \
|
||||
-e 's/^paid-till:[[:space:]]*//p' \
|
||||
-e 's/^\[Expires on\][[:space:]]*//p' \
|
||||
| sed 'q'
|
||||
)"
|
||||
|
||||
# Small protection against injection
|
||||
if [ -n "$( echo "$EXPIRATION_DATE" | LANG=C sed -n '/^[a-zA-Z0-9:.\-]\{1,64\}$/p' )" ]; then
|
||||
date --date="$EXPIRATION_DATE" +%s
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Check if arg is an integer
|
||||
# (copied from jilles @ http://stackoverflow.com/questions/806906/how-do-i-test-if-a-variable-is-a-number-in-bash )
|
||||
#
|
||||
is_int() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*) return 1;;
|
||||
*) return 0;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
# Some early checks
|
||||
#if ! which jq >/dev/null 2>&1; then
|
||||
# echo "UNKNOWN: 'jq' command not found."
|
||||
# exit 1
|
||||
#fi
|
||||
|
||||
|
||||
#
|
||||
# Parameters management
|
||||
#
|
||||
while [ "$#" -gt 0 ]; do
|
||||
while getopts hw:c:iIrR f; do
|
||||
case "$f" in
|
||||
'h')
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
|
||||
'w')
|
||||
THRESHOLD_WARNING="$( convert_to_seconds "$OPTARG" )"
|
||||
;;
|
||||
|
||||
'c')
|
||||
THRESHOLD_CRITICAL="$( convert_to_seconds "$OPTARG" )"
|
||||
;;
|
||||
|
||||
'i')
|
||||
OPTION_WHOIS_QUERYING="yes"
|
||||
;;
|
||||
|
||||
'I')
|
||||
OPTION_WHOIS_QUERYING=""
|
||||
;;
|
||||
|
||||
'r')
|
||||
OPTION_RDAP_QUERYING="yes"
|
||||
;;
|
||||
|
||||
'R')
|
||||
OPTION_RDAP_QUERYING=""
|
||||
;;
|
||||
|
||||
\?)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $( expr $OPTIND - 1 )
|
||||
|
||||
# Little checks
|
||||
if ! is_int "$THRESHOLD_WARNING" || ! is_int "$THRESHOLD_CRITICAL"; then
|
||||
echo "UNKNOWN invalid parameter : one of the threshold is not an integer."
|
||||
exit $STATE_UNKNOWN
|
||||
fi
|
||||
|
||||
# End of the options, we get the domain name
|
||||
if [ -z "$1" ]; then
|
||||
# No more options but no domain specified ? Weird but well...
|
||||
break
|
||||
fi
|
||||
DOMAIN="$1"
|
||||
shift
|
||||
|
||||
# Sleeping a bit to add some throttle if we already looped : we try not to
|
||||
# flood the rdap/whois servers.
|
||||
test -n "$EXPIRATION_DATE" && sleep 2
|
||||
EXPIRATION_DATE=""
|
||||
|
||||
# Get the expiration date via RDAP
|
||||
if [ -n "$OPTION_RDAP_QUERYING" ]; then
|
||||
if OUTPUT_RDAP="$( get_expiration_date_rdap "$DOMAIN" )" && [ -n "$OUTPUT_RDAP" ]; then
|
||||
EXPIRATION_DATE="$OUTPUT_RDAP"
|
||||
fi
|
||||
fi
|
||||
# If RDAP failed to get an expiration date, we try via WHOIS.
|
||||
if [ -z "$EXPIRATION_DATE" ] && [ -n "$OPTION_WHOIS_QUERYING" ]; then
|
||||
if OUTPUT_WHOIS="$( get_expiration_date_whois "$DOMAIN" )" && [ -n "$OUTPUT_WHOIS" ]; then
|
||||
EXPIRATION_DATE="$OUTPUT_WHOIS"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$EXPIRATION_DATE" ]; then
|
||||
# We couldn't get the date :-(
|
||||
if [ "$OUTPUT_EXIT_STATUS" -eq "$STATE_OK" ]; then
|
||||
OUTPUT_EXIT_STATUS="$STATE_UNKNOWN"
|
||||
fi
|
||||
OUTPUT_DETAIL_UNKNOWN="$OUTPUT_DETAIL_UNKNOWN $DOMAIN:could_not_get_date (output_rdap: $OUTPUT_RDAP)(output_whois: $OUTPUT_WHOIS)"
|
||||
break
|
||||
fi
|
||||
|
||||
# Dispatch in the OK/Warning/Critical boxes
|
||||
OUTPUT_DOMAIN_DETAIL="$DOMAIN:$( date --date=@$EXPIRATION_DATE +%FT%T%z)"
|
||||
if [ "$EXPIRATION_DATE" -le "$( expr "$TIMESTAMP_NOW" + "$THRESHOLD_CRITICAL" )" ]; then
|
||||
# Domain is critical
|
||||
OUTPUT_EXIT_STATUS="$STATE_CRITICAL"
|
||||
OUTPUT_DETAIL_CRITICAL="$OUTPUT_DETAIL_CRITICAL $OUTPUT_DOMAIN_DETAIL"
|
||||
elif [ "$EXPIRATION_DATE" -le "$( expr "$TIMESTAMP_NOW" + "$THRESHOLD_WARNING" )" ]; then
|
||||
# Domain is warning
|
||||
OUTPUT_DETAIL_WARNING="$OUTPUT_DETAIL_WARNING $OUTPUT_DOMAIN_DETAIL"
|
||||
# we don't change if the status is already Critical
|
||||
# (but we take precedence over Unknown)
|
||||
if [ "$OUTPUT_EXIT_STATUS" -ne "$STATE_CRITICAL" ]; then
|
||||
OUTPUT_EXIT_STATUS="$STATE_WARNING"
|
||||
fi
|
||||
else
|
||||
# Domain is Ok
|
||||
OUTPUT_DETAIL_OK="$OUTPUT_DETAIL_OK $OUTPUT_DOMAIN_DETAIL"
|
||||
fi
|
||||
done
|
||||
|
||||
# final output
|
||||
case "$OUTPUT_EXIT_STATUS" in
|
||||
"$STATE_OK")
|
||||
echo "OK $OUTPUT_DETAIL_OK"
|
||||
;;
|
||||
|
||||
"$STATE_WARNING")
|
||||
echo "WARNING $OUTPUT_DETAIL_WARNING"
|
||||
;;
|
||||
|
||||
"$STATE_CRITICAL")
|
||||
echo "CRITICAL $OUTPUT_DETAIL_CRITICAL"
|
||||
;;
|
||||
|
||||
"$STATE_UNKNOWN")
|
||||
echo "UNKNOWN $OUTPUT_DETAIL_UNKNOWN"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "WTF"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit "$OUTPUT_EXIT_STATUS"
|
Loading…
Add table
Add a link
Reference in a new issue