diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 10 | ||||
-rwxr-xr-x | crypt-expiry-check.in | 195 | ||||
-rw-r--r-- | crypt-expiry-check.service.in | 6 | ||||
-rw-r--r-- | crypt-expiry-check.timer.in | 11 |
5 files changed, 162 insertions, 63 deletions
@@ -3,3 +3,6 @@ crypt-expiry-check.cron man.commons *.common *.1 +crypt-expiry-check.service +crypt-expiry-check.timer +.idea @@ -24,10 +24,11 @@ ETCDIR = /etc CRONDIR = /etc/cron.daily BINDIR = /usr/bin MANDIR = /usr/share/man +SYSTEMDDIR = /usr/lib/systemd/system -VERSION = 4.0.12 +VERSION = 4.4.3 -all: man.commons crypt-expiry-check crypt-expiry-check.cron crypt-expiry-check.1 +all: man.commons crypt-expiry-check crypt-expiry-check.cron crypt-expiry-check.1 crypt-expiry-check.service crypt-expiry-check.timer %: %.in sed "s/#VERSION#/$(VERSION)/; s@#CRONDIR#@$(CRONDIR)@; s@#BINDIR#@$(BINDIR)@; s@#ETCDIR#@$(ETCDIR)@" $< > $@ @@ -43,7 +44,8 @@ all: man.commons crypt-expiry-check crypt-expiry-check.cron crypt-expiry-check.1 .PHONY: install dist clean install: all - install -D -m0755 crypt-expiry-check.cron $(DESTDIR)$(CRONDIR)/crypt-expiry-check + [ -z "$(CRONDIR)" ] || install -D -m0755 crypt-expiry-check.cron $(DESTDIR)$(CRONDIR)/crypt-expiry-check + [ -z "$(SYSTEMDDIR)" ] || install -D -m0644 -t $(DESTDIR)$(SYSTEMDDIR) crypt-expiry-check.service crypt-expiry-check.timer install -D -m0755 -t $(DESTDIR)$(BINDIR) crypt-expiry-check install -D -m0644 -t $(DESTDIR)$(MANDIR)/man1 crypt-expiry-check.1 install -D -m0644 -t $(DESTDIR)$(ETCDIR) crypt-expiry.checks @@ -51,7 +53,7 @@ install: all clean: ls -A | \ grep "^\($(shell sed 's|\.|\\.|; s|\*|.*|; s|$$|\\|' .gitignore | tr '\n' '\|')\)\$$" | \ - xargs -r rm + xargs -r rm -rf --one-file-system dist: clean git status --porcelain 2> /dev/null | grep -q "\S" && (git add .; git commit -m"neue Version: $(VERSION)") || true diff --git a/crypt-expiry-check.in b/crypt-expiry-check.in index 3846d7f..7ce5eae 100755 --- a/crypt-expiry-check.in +++ b/crypt-expiry-check.in @@ -6,13 +6,13 @@ # Author of ssl-cert-check: Matty < matty91 at gmail dot com > # Maintainer of crypt-expiry-check: Erich < crux at eckner dot net > # -# Purpose: +# Purpose: # crypt-expiry-check checks to see if a digital certificate in X.509 format -# or a GnuPG-key has expired. ssl-cert-check can be run in interactive -# and batch mode, and provides facilities to alarm if a certificate is +# or a GnuPG-key has expired. ssl-cert-check can be run in interactive +# and batch mode, and provides facilities to alarm if a certificate is # about to expire. # -# License: +# License: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or @@ -25,12 +25,12 @@ # # Requirements: # Requires openssl gnupg -# -# Installation: +# +# Installation: # Copy the shell script to a suitable location # # Usage: -# Refer to the usage() sub-routine, or invoke crypt-expiry-check +# Refer to the usage() sub-routine, or invoke crypt-expiry-check # with the "-h" option. PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/ssl/bin:/usr/sfw/bin @@ -80,11 +80,12 @@ OPENSSL=$(which openssl) PRINTF=$(which printf) SED=$(which sed) TEE=$(which tee) +TR=$(which tr) SORT=$(which sort) TAIL=$(which tail) MKTEMP=$(which mktemp) GPG=$(which gpg) -HOSTNAME=$(hostname) +HOSTNAME=$(uname -n) # Try to find a mail client MAIL="cantfindit" @@ -112,26 +113,36 @@ set_retcode() [ ${RETCODE} -lt $1 ] && RETCODE=$1 } +format_date() +{ + if echo "$*" | ${GREP} -qx '[0-9]\+'; then + ${DATE} '+%F' -d@"$*" + elif echo "$*" | ${GREP} -qx '[0-9]\+-[0-9]\+-[0-9]\+'; then + ${DATE} '+%F' -d"$*" + else + ${DATE} -d"$(echo "$*" | ${AWK} '{ print $1, $2, $4 }')" +%F + fi +} + ##################################################################### # Purpose: Print a line with the expiraton interval # Arguments: # $1 -> Hostname -# $2 -> TCP Port +# $2 -> TCP Port # $3 -> Status of certification (e.g., expired or valid) # $4 -> Date when certificate will expire -# $5 -> Days left until the certificate will expire +# $5 -> Days left until the certificate will expire # $6 -> Issuer of the certificate ##################################################################### -prints() +prints() { if ${ISSUER} && ! ${VALIDATION} then - MIN_DATE=$(echo $4 | ${AWK} '{ print $1, $2, $4 }') if ${NAGIOS} then - ${PRINTF} "%-35s %-17s %-8s %-11s %-4s %-30s\n" "$1:$2" "$6" "$3" "${MIN_DATE}" \|days="$5" + ${PRINTF} "%-35s %-17s %-8s %-11s %-4s %-30s\n" "$1:$2" "$6" "$3" "$4" \|days="$5" else - ${PRINTF} "%-35s %-17s %-8s %-11s %-4s %-30s\n" "$1:$2" "$6" "$3" "${MIN_DATE}" "$5" + ${PRINTF} "%-35s %-17s %-8s %-11s %-4s %-30s\n" "$1:$2" "$6" "$3" "$4" "$5" fi elif ${ISSUER} && ${VALIDATION} then @@ -139,13 +150,12 @@ prints() elif ! ${VALIDATION} then - MIN_DATE=$(echo $4 | ${AWK} '{ print $1, $2, $4 }') if ${NAGIOS} then - ${PRINTF} "%-47s %-12s %-12s %-4s %-30s\n" "$1:$2" "$3" "${MIN_DATE}" \|days="$5" + ${PRINTF} "%-47s %-12s %-12s %-4s %-30s\n" "$1:$2" "$3" "$4" \|days="$5" else - ${PRINTF} "%-47s %-12s %-12s %-4s %-30s\n" "$1:$2" "$3" "${MIN_DATE}" "$5" - fi + ${PRINTF} "%-47s %-12s %-12s %-4s %-30s\n" "$1:$2" "$3" "$4" "$5" + fi else ${PRINTF} "%-35s %-35s %-32s\n" "$1:$2" "$7" "$8" fi @@ -157,10 +167,10 @@ prints() # Arguments: # None #################################################### -print_heading() +print_heading() { if ! ${NOHEADER} - then + then if ${ISSUER} && ! ${NAGIOS} && ! ${VALIDATION} then ${PRINTF} "\n%-35s %-17s %-8s %-11s %-4s\n" "Host" "Issuer" "Status" "Expires" "Days" >> ${STDOUT_TMP} @@ -170,12 +180,12 @@ print_heading() then ${PRINTF} "\n%-35s %-35s %-32s %-17s\n" "Host" "Common Name" "Serial #" "Issuer" >> ${STDOUT_TMP} echo "----------------------------------- ----------------------------------- -------------------------------- -----------------" >> ${STDOUT_TMP} - + elif ! ${NAGIOS} && ! ${VALIDATION} then ${PRINTF} "\n%-47s %-12s %-12s %-4s\n" "Host" "Status" "Expires" "Days" >> ${STDOUT_TMP} echo "----------------------------------------------- ------------ ------------ ----" >> ${STDOUT_TMP} - + elif ! ${NAGIOS} && ${VALIDATION} then ${PRINTF} "\n%-35s %-35s %-32s\n" "Host" "Common Name" "Serial #" >> ${STDOUT_TMP} @@ -191,11 +201,11 @@ print_heading() # Arguments: # None ########################################## -usage() +usage() { >&2 echo "$(basename "$0") checks expiration of gpg keys and X.509 certificates and sends emails if keys are about to expire." >&2 echo "" - >&2 echo "Usage: $0 [ -e email address ] [ -x days ] [-q] [-a] [-b] [-h] [-i] [-n] [-v] { [ -s common_name:port] } || { [ -f cert_file ] } || { [ -c certificate file ] } || { [ -g email address ] }" + >&2 echo "Usage: $0 [ -e email address ] [ -x days ] [-q] [-a] [-b] [-h] [-i] [-n] [-v] { [ -s common_name:port] } || { [ -f cert_file ] } || { [ -c certificate file ] } || { [ -g email address ] }" >&2 echo "" >&2 echo " -a Send a warning message through E-mail" >&2 echo " -b Will not print header" @@ -208,7 +218,9 @@ usage() >&2 echo " -i Print the issuer of the certificate" >&2 echo " -k password PKCS12 file password" >&2 echo " -n Run as a Nagios plugin" + >&2 echo " -N directory Check nginx certificates in directory's config files." >&2 echo " -q Don't print anything on the console" + >&2 echo " -r url Like -c, but download cert from url." >&2 echo " -s commmon_name:port Server and Port to connect to (interactive mode)" >&2 echo " -t type Specify the certificate type" >&2 echo " -v Specify a specific protocol version to use (tls, ssl2, ssl3)" @@ -239,7 +251,7 @@ check_server_status() { elif [ "_${2}" = "_pop3" -o "_${2}" = "_110" ] then TLSFLAG="-starttls pop3" - + elif [ "_${2}" = "_imap" -o "_${2}" = "_143" ] then TLSFLAG="-starttls imap" @@ -282,12 +294,12 @@ check_server_status() { then prints ${1} ${2} "Cannot resolve domain" "Unknown" | ${TEE} -a ${MAILOUT_TMP} >> ${STDOUT_TMP} set_retcode 3 - + elif ${GREP} -iq "Operation timed out" ${ERROR_TMP} then prints ${1} ${2} "Operation timed out" "Unknown" | ${TEE} -a ${MAILOUT_TMP} >> ${STDOUT_TMP} set_retcode 3 - + elif ${GREP} -iq "ssl handshake failure" ${ERROR_TMP} then prints ${1} ${2} "SSL handshake failed" "Unknown" | ${TEE} -a ${MAILOUT_TMP} >> ${STDOUT_TMP} @@ -297,7 +309,7 @@ check_server_status() { then prints ${1} ${2} "Connection timed out" "Unknown" | ${TEE} -a ${MAILOUT_TMP} >> ${STDOUT_TMP} set_retcode 3 - + else check_file_status ${CERT_TMP} $1 $2 $3 fi @@ -330,6 +342,50 @@ check_remote_file_status() { } ##################################################### +### Check the expiration status of nginx certificates +### Accepts one parameter: +### $1 -> directory to nginx configurations +##################################################### + +check_nginx_configuration() { + CONFIGDIR=${1} + FWARNDAYS=${2:-${WARNDAYS}} + + while read -r line; do + { + read -r _ HOST _ + read -r _ FILE + } < <( + printf '%s\n' "${line}" \ + | "${SED}" ' + s/[;{}]/\0\n/g + ' \ + | "${SED}" -n ' + /^\(server_name\|ssl_certificate\)\s/p + ' \ + | "${SORT}" \ + | "${SED}" ' + s/;$// + ' + ) + check_file_status "${FILE}" 'NGINX' "${HOST// /, }" "${WARNDAYS}" + done < <( + find "${CONFIGDIR}" \ + \( -type f -o -type l \) \ + -exec "${SED}" ' + s/#.*$// + s/^\s*// + s/\s*$// + ' {} \; \ + | "${TR}" -d '\n' \ + | "${SED}" ' + s/}server\s*{/\n\0/g + ' \ + | "${GREP}" -wF 'ssl_certificate' + ) +} + +##################################################### ### Check the expiration status of a certificate file ### Accepts three parameters: ### $1 -> certificate file to process @@ -359,10 +415,11 @@ check_file_status() { # send the informational message to /dev/null ${OPENSSL} pkcs12 -nokeys -in ${CERTFILE} \ -out ${CERT_TMP} -clcerts -password pass:${PKCSDBPASSWD} 2> /dev/null - + # Extract the expiration date from the certificate CERTDATE=$(${OPENSSL} x509 -in ${CERT_TMP} -enddate -noout | \ ${SED} 's/notAfter\=//') + CERTDATE=$(${DATE} +%s -d "${CERTDATE}") # Extract the issuer from the certificate CERTISSUER=$(${OPENSSL} x509 -in ${CERT_TMP} -issuer -noout | \ @@ -373,14 +430,15 @@ check_file_status() { COMMONNAME=$(${OPENSSL} x509 -in ${CERT_TMP} -subject -noout | \ ${SED} -e 's/.*CN=//' | \ ${SED} -e 's/\/.*//') - + ### Grab the serial number from the X.509 certificate SERIAL=$(${OPENSSL} x509 -in ${CERT_TMP} -serial -noout | \ ${SED} -e 's/serial=//') else # Extract the expiration date from the ceriticate - CERTDATE=$(${OPENSSL} x509 -in ${CERTFILE} -enddate -noout -inform ${CERTTYPE} | \ - ${SED} 's/notAfter\=//') + CERTDATE=$(while ${OPENSSL} x509 -enddate -noout -inform ${CERTTYPE} 2>/dev/null; do :; done <${CERTFILE} | \ + ${SED} 's/notAfter\=//' | \ + xargs -rI __ ${DATE} +%s -d "__") # Extract the issuer from the certificate CERTISSUER=$(${OPENSSL} x509 -in ${CERTFILE} -issuer -noout -inform ${CERTTYPE} | \ @@ -396,7 +454,8 @@ check_file_status() { fi # Convert the date to seconds, and get the diff between NOW and the expiration date - CERTDIFF=$[$(date +%s -d "${CERTDATE}") - $(date +%s)] + CERTDIFF=$[${CERTDATE} - $(${DATE} +%s)] + CERTDATE=$(format_date "${CERTDATE}") if [ ${CERTDIFF} -lt 0 ] then CERTDIFF=$[$[${CERTDIFF}+1]/3600/24-1] @@ -441,25 +500,31 @@ check_gpg_key_status() { exit 1 fi - KEY_INFO=$(${GPG_COMMAND} --list-secret-keys "${GPG_ADDRESS}" 2> /dev/null) - [ -z "${KEY_INFO}" ] && KEY_INFO=$(${GPG_COMMAND} --list-keys "${GPG_ADDRESS}") - - KEY_DATE_STR=$( - echo "${KEY_INFO}" | \ - ${GREP} "\[\(expire[ds]\|verfallen\|verf..\?llt\):[^]]*]" | \ - ${SED} "s#^.*\[\(expire[ds]\|verfallen\|verf..\?llt\):\s*\(\S[^]]*\)].*\$#\2#" | \ - ${SORT} | \ - ${TAIL} -n1 - ) - if [ -z "${KEY_DATE_STR}" ] + KEYS=$(${GPG_COMMAND} --list-secret-keys --with-colons "${GPG_ADDRESS}" 2>/dev/null) + [ -z "${KEYS}" ] && KEYS=$(${GPG_COMMAND} --list-keys --with-colons "${GPG_ADDRESS}" 2>/dev/null) + + KEY_DATE=$( + echo "${KEYS}" \ + | ${AWK} -F: '$1 == "fpr" {print $10}' \ + | ${SORT} -u \ + | while read -r KEY; do + ${GPG_COMMAND} --list-keys --with-colons "${KEY}" \ + | awk -F: '$1 == "sub" || $1 == "pub" {print $7}' \ + | ${SORT} -r \ + | tail -n1 + done \ + | ${SORT} \ + | tail -n1 + ) + if [ -z "${KEY_DATE}" ] then echo "No valid gpg-key found for ${GPG_ADDRESS}." | ${TEE} -a ${MAILOUT_TMP} >> ${STDOUT_TMP} set_retcode 2 - else - KEY_DATE=$(date +%s -ud "${KEY_DATE_STR}") + return fi - KEY_DIFF=$[${KEY_DATE} - $(date +%s)] + KEY_DIFF=$[${KEY_DATE} - $(${DATE} +%s)] + KEY_DATE=$(format_date "${KEY_DATE}") if [ ${KEY_DIFF} -lt 0 ] then KEY_DIFF=$[$[${KEY_DIFF}+1]/3600/24-1] @@ -470,24 +535,24 @@ check_gpg_key_status() { if [ ${KEY_DIFF} -lt 0 ] then echo "The GPG key for ${GPG_ADDRESS} has expired!" >> ${MAILOUT_TMP} - prints "GPG" " ${GPG_ADDRESS}" "Expired" "${KEY_DATE_STR}" "${KEY_DIFF}" "" "" "" >> ${STDOUT_TMP} + prints "GPG" " ${GPG_ADDRESS}" "Expired" "${KEY_DATE}" "${KEY_DIFF}" "" "" "" >> ${STDOUT_TMP} set_retcode 2 elif [ ${KEY_DIFF} -lt ${FWARNDAYS} ] then - echo "The GPG key for ${GPG_ADDRESS} will expire on ${KEY_DATE_STR}" >> ${MAILOUT_TMP} - prints "GPG" " ${GPG_ADDRESS}" "Expiring" "${KEY_DATE_STR}" "${KEY_DIFF}" "" "" "" >> ${STDOUT_TMP} + echo "The GPG key for ${GPG_ADDRESS} will expire on ${KEY_DATE}" >> ${MAILOUT_TMP} + prints "GPG" " ${GPG_ADDRESS}" "Expiring" "${KEY_DATE}" "${KEY_DIFF}" "" "" "" >> ${STDOUT_TMP} set_retcode 1 else - prints "GPG" " ${GPG_ADDRESS}" "Valid" "${KEY_DATE_STR}" "${KEY_DIFF}" "" "" "" >> ${STDOUT_TMP} + prints "GPG" " ${GPG_ADDRESS}" "Valid" "${KEY_DATE}" "${KEY_DIFF}" "" "" "" >> ${STDOUT_TMP} fi } ################################# ### Start of main program ################################# -while getopts abc:e:f:g:G:hik:nqr:s:t:x:v:VZ option +while getopts abc:e:f:g:G:hik:nN:qr:s:t:v:Vx:Z option do case "${option}" in @@ -523,6 +588,9 @@ do n) NAGIOS=true ;; + N) + NGINXDIRS[${#NGINXDIRS[@]}]=${OPTARG} + ;; q) QUIET=true ;; @@ -595,10 +663,10 @@ then fi ### Check to make sure the sed and awk binaries are available -if [ ! -f ${SED} ] || [ ! -f ${AWK} ] || [ ! -f ${TEE} ] || [ ! -f ${SORT} ] || [ ! -f ${TAIL} ] +if [ ! -f ${SED} ] || [ ! -f ${AWK} ] || [ ! -f ${TEE} ] || [ ! -f ${SORT} ] || [ ! -f ${TAIL} ] || [ ! -f "${TR}" ] then - >&2 echo "ERROR: Unable to locate the sed, awk, tee, sort or tail binary." - >&2 echo "FIX: Please modify the \${SED}, \${AWK}, \${TEE}, \${SORT}, \${TAIL} variables in the program header." + >&2 echo "ERROR: Unable to locate the sed, awk, tee, sort, tail or tr binary." + >&2 echo "FIX: Please modify the \${SED}, \${AWK}, \${TEE}, \${SORT}, \${TAIL}, \${TR} variables in the program header." exit 1 fi @@ -629,7 +697,7 @@ else exit 1 fi -if [ $[${#HOSTS[@]} + ${#SERVERFILES[@]} + ${#CERTFILES[@]} + ${#REMOTECERTFILES[@]} + ${#CHECKADDRESSES[@]}] -eq 0 ] +if [ $[${#HOSTS[@]} + ${#SERVERFILES[@]} + ${#CERTFILES[@]} + ${#REMOTECERTFILES[@]} + ${#CHECKADDRESSES[@]} + ${#NGINXDIRS[@]}] -eq 0 ] then >&2 echo "ERROR: Nothing to check." usage @@ -643,6 +711,12 @@ do check_server_status "${HOSTS[${i}]}" "${PORTS[${i}]}" done +for (( i=0; i<${#NGINXDIRS[@]}; i++ )) +do + check_nginx_configuration "${NGINXDIRS[@]}" +# check_file_status "${HOST}" "FILE" "${HOST}" "${FWARNDAYS}" +done + for (( i=0; i<${#SERVERFILES[@]}; i++ )) do while read FWARNDAYS PORT HOST @@ -653,6 +727,9 @@ do elif [ "${PORT}" = "REMOTEFILE" ] then check_remote_file_status "${HOST}" "REMOTEFILE" "${HOST}" "${FWARNDAYS}" + elif [ "${PORT}" = "NGINX" ] + then + check_nginx_configuration "${HOST}" "${FWARNDAYS}" elif [ "${PORT}" = "GPG" ] then check_gpg_key_status "${GPG}" "${HOST}" "${FWARNDAYS}" @@ -663,8 +740,8 @@ do check_server_status "${HOST}" "${PORT}" "${FWARNDAYS}" fi done < <( - sed ' - /^#|^$/d + "${SED}" ' + /^#\|^$/d s/^\([0-9]\+\) \+\(.*\S\) \+\(\S\+\)$/\1 \3 \2/ t s/^\(.*\S\) \+\(\S\+\)$/'"${WARNDAYS}"' \2 \1/ @@ -697,7 +774,7 @@ then ( echo "To: ${ADMIN}" echo "From: $(whoami)@$(hostname)" - echo "Subject: $(basename $0) at $(date)" + echo "Subject: $(basename $0) at $(${DATE})" echo "" cat ${MAILOUT_TMP} ) | ${MAIL} -t diff --git a/crypt-expiry-check.service.in b/crypt-expiry-check.service.in new file mode 100644 index 0000000..18e320d --- /dev/null +++ b/crypt-expiry-check.service.in @@ -0,0 +1,6 @@ +[Unit] +Description=check expiry of surveilled keys/certificates + +[Service] +Environment=MAILTO=me@example.com +ExecStart=#BINDIR#/crypt-expiry-check -qa -e $MAILTO -f #ETCDIR#/crypt-expiry.checks diff --git a/crypt-expiry-check.timer.in b/crypt-expiry-check.timer.in new file mode 100644 index 0000000..e153ba5 --- /dev/null +++ b/crypt-expiry-check.timer.in @@ -0,0 +1,11 @@ +[Unit] +Description=check expiry of surveilled keys/certificates twice a day + +[Timer] +AccuracySec=1us +OnBootSec=15min +OnUnitActiveSec=12h +RandomizedDelaySec=1h + +[Install] +WantedBy=timers.target |