From c353e03a5bfb7a5d29e0123a988c0abf4036cb70 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Mon, 23 Mar 2026 15:43:11 +0100 Subject: [PATCH 1/9] Testing different ranges --- tests/test_07_monitoring_range.py | 180 ++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/tests/test_07_monitoring_range.py b/tests/test_07_monitoring_range.py index 9c8fe35..0441b0c 100755 --- a/tests/test_07_monitoring_range.py +++ b/tests/test_07_monitoring_range.py @@ -158,6 +158,184 @@ def test_init(self): self.assertEqual(mon_range.end_infinity, test_data["end_infinity"]) self.assertEqual(mon_range.alert_on, test_data["alert_on"]) + # ------------------------------------------------------------------------- + def test_operator_in(self): + """Test of 'in' and 'not in' operators.""" + LOG.info(self.get_method_doc()) + + from monitoring.range import MonitoringRange + from monitoring.range import RangeAlertOn + + mon_range = MonitoringRange("6") + LOG.debug("Initialized range:\n" + pp(mon_range.as_dict())) + + test_data = ( + (-0.1, False), + (0, True), + (0.1, True), + (4, True), + (5.99, True), + (6, True), + (6.01, False), + ) + + for token in test_data: + val = token[0] + exp = token[1] + + LOG.debug(f"Test value {val} in range {str(mon_range)!r}: {exp}") + if exp: + self.assertIn(val, mon_range) + else: + self.assertNotIn(val, mon_range) + + if self.verbose >= 1: + print() + mon_range = MonitoringRange("@6") + LOG.debug("Initialized inverted range:\n" + pp(mon_range.as_dict())) + + test_data = ( + (-0.1, True), + (0, False), + (0.1, False), + (4, False), + (5.99, False), + (6, False), + (6.01, True), + ) + + for token in test_data: + val = token[0] + exp = token[1] + + LOG.debug(f"Test value {val} outside of range {str(mon_range)!r}: {exp}") + if exp: + self.assertIn(val, mon_range) + else: + self.assertNotIn(val, mon_range) + + # ------------------------------------------------------------------------- + def test_check_value(self): + """Test checking different values.""" + LOG.info(self.get_method_doc()) + + from monitoring.range import MonitoringRange + from monitoring.range import RangeAlertOn + + test_data = ( + ( + "-7:23", ( + (-23, False), + (-7, True), + (-1, True), + (0, True), + (4, True), + (23, True), + (23.1, False), + (79.999999, False), + ), + ), + ( + ":5.75", ( + (-1, False), + (0, True), + (4, True), + (5.75, True), + (5.7501, False), + (6, False), + ), + ), + ( + "~:-95.99", ( + (-1001341, True), + (-96, True), + (-95.999, True), + (-95.99, True), + (-95.989, False), + (-95, False), + (0, False), + (5.7501, False), + ), + ), + ( + "10:", ( + (-95.999, False), + (-1, False), + (0, False), + (9.91, False), + (10, True), + (11.11, True), + (123456789012346, True), + ), + ), + ( + "123456789012345:", ( + (-95.999, False), + (0, False), + (123456789012344.91, False), + (123456789012345, True), + (123456789012345.61, True), + (123456789012346, True), + ), + ), + ( + "~:0", ( + (-123456789012344.91, True), + (-1, True), + (0, True), + (.001, False), + (123456789012345, False), + ), + ), + ( + "@0:657.8210567", ( + (-134151, True), + (-1, True), + (0, False), + (.001, False), + (657.8210567, False), + (657.9, True), + (123456789012345, True), + ), + ), + ( + "1:1", ( + (-1, False), + (0, False), + (0.99, False), + (1, True), + (1.001, False), + (5.2, False), + ), + ), + ) + + for token in test_data: + if self.verbose >= 1: + print() + + range_str = token[0] + test_tokens = token[1] + + mon_range = MonitoringRange(range_str) + + for test_pair in test_tokens: + + val = test_pair[0] + exp = test_pair[1] + if exp: + if mon_range.invert_match: + LOG.debug(f"Test value {val} outside of range {str(mon_range)!r}.") + else: + LOG.debug(f"Test value {val} in range {str(mon_range)!r}.") + self.assertIn(val, mon_range) + else: + if mon_range.invert_match: + LOG.debug(f"Test value {val} in range {str(mon_range)!r}.") + else: + LOG.debug(f"Test value {val} not in range {str(mon_range)!r}.") + self.assertNotIn(val, mon_range) + # ============================================================================= if __name__ == "__main__": @@ -172,6 +350,8 @@ def test_init(self): suite = unittest.TestSuite() suite.addTest(TestMonitoringRange("test_init", verbose)) + suite.addTest(TestMonitoringRange("test_operator_in", verbose)) + suite.addTest(TestMonitoringRange("test_check_value", verbose)) runner = unittest.TextTestRunner(verbosity=verbose) From f8be7789b14c59b7d24f39e9b9f2fa65db607b9d Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Mon, 23 Mar 2026 17:04:50 +0100 Subject: [PATCH 2/9] Adding tests/test_10_monitoring_threshold.py --- tests/test_10_monitoring_threshold.py | 247 ++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100755 tests/test_10_monitoring_threshold.py diff --git a/tests/test_10_monitoring_threshold.py b/tests/test_10_monitoring_threshold.py new file mode 100755 index 0000000..475162e --- /dev/null +++ b/tests/test_10_monitoring_threshold.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +@summary: Test script (and module) for unit tests on monitoring.threshold. + +In this test case the MonitoringThreshold object is tested. + +@author: Frank Brehm +@contact: frank@brehm-online.com +@copyright: © 2023 - 2026 Frank Brehm, Berlin +@license: GPL3 +""" + +import logging +import unittest + +from general import MonitoringScriptsTestcase +from general import get_arg_verbose +from general import init_root_logger +from general import pp + +LOG = logging.getLogger("test_monitoring_threshold") + + +# ============================================================================= +class TestMonitoringThreshold(MonitoringScriptsTestcase): + """Testcase class for testing MonitoringThreshold object.""" + + # ------------------------------------------------------------------------- + def setUp(self): + """Execute this on seting up before calling each particular test method.""" + if self.verbose >= 1: + print() + + # ------------------------------------------------------------------------- + def test_init(self): + """Test init of a monitoring.MonitoringThreshold.""" + LOG.info(self.get_method_doc()) + + from monitoring import MonitoringThreshold + + threshold = MonitoringThreshold() + + if self.verbose > 2: + LOG.debug("Initialized object:\n" + pp(threshold.as_dict())) + + LOG.debug("Test, whether warning is not set.") + self.assertFalse(threshold.warning.is_set) + + LOG.debug("Test, whether critical is not set.") + self.assertFalse(threshold.critical.is_set) + + if self.verbose >= 1: + print() + + LOG.debug("Set warning and critical to ''.") + threshold.set_thresholds(warning="", critical="") + + LOG.debug("Test, whether warning is not set.") + self.assertFalse(threshold.warning.is_set) + + LOG.debug("Test, whether critical is not set.") + self.assertFalse(threshold.critical.is_set) + + # ------------------------------------------------------------------------- + def test_use_ranges(self): + """Test init of a monitoring.MonitoringThreshold with ranges.""" + LOG.info(self.get_method_doc()) + + from monitoring import MonitoringThreshold + + warn_value = 80 + crit_value = 90 + + threshold = MonitoringThreshold(warning=f"{warn_value}", critical=f"{crit_value}") + + if self.verbose > 2: + LOG.debug("Initialized object:\n" + pp(threshold.as_dict())) + + LOG.debug("Test, whether warning is set.") + self.assertTrue(threshold.warning.is_set) + + LOG.debug("Test, whether critical is set.") + self.assertTrue(threshold.critical.is_set) + + LOG.debug(f"Test, whether warning.start == 0.") + self.assertEqual(threshold.warning.start, 0) + + LOG.debug(f"Test, whether warning.end == {warn_value}.") + self.assertEqual(threshold.warning.end, warn_value) + + LOG.debug(f"Test, whether critical.start == 0.") + self.assertEqual(threshold.critical.start, 0) + + LOG.debug(f"Test, whether critical.end == {crit_value}.") + self.assertEqual(threshold.critical.end, crit_value) + + # ------------------------------------------------------------------------- + def test_bad_ranges(self): + """Test init of a monitoring.MonitoringThreshold with bad ranges.""" + LOG.info(self.get_method_doc()) + + from monitoring import MonitoringThreshold + from monitoring import InvalidRangeError + + LOG.debug("Try create Threshold wih warning='total', critical='rubbish'.") + with self.assertRaises(InvalidRangeError) as cm: + threshold = MonitoringThreshold(warning="total", critical="rubbish") + LOG.error(f"This should never be visible: {threshold!r}.") + e = cm.exception + LOG.debug("{c} raised: {e}".format(c=e.__class__.__name__, e=e)) + + # ------------------------------------------------------------------------- + def test_get_status(self): + """Test get status of a monitoring.MonitoringThreshold by a value.""" + LOG.info(self.get_method_doc()) + + from monitoring import MonitoringThreshold + from monitoring import MonitoringObject + + ok = MonitoringObject.errors["OK"] + warning = MonitoringObject.errors["WARNING"] + critical = MonitoringObject.errors["CRITICAL"] + + test_data = ( + { + "warning": "5:33", + "tests": ( + (-1, "WARNING"), + (4, "WARNING"), + (4.9999, "WARNING"), + (5, "OK"), + (14.21, "OK"), + (33, "OK"), + (33.01, "WARNING"), + (10231, "WARNING"), + ), + }, + { + "warning": "~:30", + "critical": "~:60", + "tests": ( + (-1, "OK"), + (4, "OK"), + (29.999999, "OK"), + (30, "OK"), + (30.1, "WARNING"), + (59.9, "WARNING"), + (60, "WARNING"), + (60.00001, "CRITICAL"), + (10231, "CRITICAL"), + ), + }, + { + "critical": "~:25", + "tests": ( + (-1, "OK"), + (4, "OK"), + (24.999999, "OK"), + (25, "OK"), + (25.001, "CRITICAL"), + (31001, "CRITICAL"), + ), + }, + { + "warning": "10:25", + "critical": "~:25", + "tests": ( + (-1, "WARNING"), + (4, "WARNING"), + (4.9999, "WARNING"), + (10, "OK"), + (14.21, "OK"), + (25, "OK"), + (25.01, "CRITICAL"), + (31001, "CRITICAL"), + ), + }, + { + "warning": "@10:25", + "critical": "10:", + "tests": ( + (-1, "CRITICAL"), + (4, "CRITICAL"), + (4.9999, "CRITICAL"), + (10, "WARNING"), + (14.21, "WARNING"), + (25, "WARNING"), + (25.01, "OK"), + (31001, "OK"), + ), + }, + ) + + for test_token in test_data: + if self.verbose >= 1: + print() + + if "warning" in test_token: + w = test_token["warning"] + else: + w = None + + if "critical" in test_token: + c = test_token["critical"] + else: + c = None + + LOG.info(f"Testing threshold with warning={w!r} and critical={c!r}.") + threshold = MonitoringThreshold(warning=w, critical=c) + + for test_pair in test_token["tests"]: + val = test_pair[0] + exp = test_pair[1] + status_exp = threshold.errors[exp] + + LOG.debug(f"Test threshold against {val} => {status_exp} ({exp!r}).") + status_got = threshold.get_status(val) + LOG.debug(f"Got status {status_got}.") + self.assertEqual(status_exp, status_got) + + + +# ============================================================================= +if __name__ == "__main__": + + verbose = get_arg_verbose() + if verbose is None: + verbose = 0 + init_root_logger(verbose) + + LOG.info("Starting tests ...") + + suite = unittest.TestSuite() + + suite.addTest(TestMonitoringThreshold("test_init", verbose)) + suite.addTest(TestMonitoringThreshold("test_use_ranges", verbose)) + suite.addTest(TestMonitoringThreshold("test_bad_ranges", verbose)) + suite.addTest(TestMonitoringThreshold("test_get_status", verbose)) + + runner = unittest.TextTestRunner(verbosity=verbose) + + result = runner.run(suite) + +# ============================================================================= + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 From 846c49c83858bdad07c308562f159b8b9e0fc83f Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Mon, 23 Mar 2026 17:42:27 +0100 Subject: [PATCH 3/9] Adding tests/test_10_monitoring_threshold.py --- tests/test_10_monitoring_threshold.py | 1 - tests/test_20_monitoring_performance.py | 92 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100755 tests/test_20_monitoring_performance.py diff --git a/tests/test_10_monitoring_threshold.py b/tests/test_10_monitoring_threshold.py index 475162e..5c0a43e 100755 --- a/tests/test_10_monitoring_threshold.py +++ b/tests/test_10_monitoring_threshold.py @@ -220,7 +220,6 @@ def test_get_status(self): self.assertEqual(status_exp, status_got) - # ============================================================================= if __name__ == "__main__": diff --git a/tests/test_20_monitoring_performance.py b/tests/test_20_monitoring_performance.py new file mode 100755 index 0000000..20291d4 --- /dev/null +++ b/tests/test_20_monitoring_performance.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +@summary: Test script (and module) for unit tests on monitoring.perf. + +In this test case the MonitoringPerformance object is tested. + +@author: Frank Brehm +@contact: frank@brehm-online.com +@copyright: © 2023 - 2026 Frank Brehm, Berlin +@license: GPL3 +""" + +import logging +import unittest + +from general import MonitoringScriptsTestcase +from general import get_arg_verbose +from general import init_root_logger +from general import pp + +LOG = logging.getLogger("test_monitoring_performance") + + +# ============================================================================= +class TestMonitoringPerformance(MonitoringScriptsTestcase): + """Testcase class for testing MonitoringPerformance object.""" + + # ------------------------------------------------------------------------- + def setUp(self): + """Execute this on seting up before calling each particular test method.""" + if self.verbose >= 1: + print() + + # ------------------------------------------------------------------------- + def test_init(self): + """Test init of a monitoring.MonitoringPerformance.""" + LOG.info(self.get_method_doc()) + + from monitoring import MonitoringPerformance + from monitoring import MonitoringPerformanceError + + perf = MonitoringPerformance("sample", 0) + + if self.verbose > 2: + LOG.debug("Initialized object:\n" + pp(perf.as_dict())) + + if self.verbose >= 1: + print() + + LOG.info("Test init of MonitoringPerformance with bad arguments.") + + bad_init_data = ( + [], + [""], + ["", 0], + [" "], + [" ", 0], + ["sample"], + ["sample", "bla"], + ) + + for args in bad_init_data: + LOG.debug(f"Trying to init MonitoringPerformance with: {pp(args)}.") + with self.assertRaises((MonitoringPerformanceError, TypeError)) as cm: + perf = MonitoringPerformance(*args) + LOG.error(f"This should never be visible: {perf!r}.") + e = cm.exception + LOG.debug("{c} raised: {e}".format(c=e.__class__.__name__, e=e)) + + +# ============================================================================= +if __name__ == "__main__": + + verbose = get_arg_verbose() + if verbose is None: + verbose = 0 + init_root_logger(verbose) + + LOG.info("Starting tests ...") + + suite = unittest.TestSuite() + + suite.addTest(TestMonitoringPerformance("test_init", verbose)) + + runner = unittest.TextTestRunner(verbosity=verbose) + + result = runner.run(suite) + +# ============================================================================= + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 From 2cff4513cc5b380680303675daf798aeec9c28f8 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Mon, 23 Mar 2026 17:45:17 +0100 Subject: [PATCH 4/9] Make the linter happy --- tests/test_07_monitoring_range.py | 2 -- tests/test_10_monitoring_threshold.py | 11 +++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/test_07_monitoring_range.py b/tests/test_07_monitoring_range.py index 0441b0c..21fae39 100755 --- a/tests/test_07_monitoring_range.py +++ b/tests/test_07_monitoring_range.py @@ -164,7 +164,6 @@ def test_operator_in(self): LOG.info(self.get_method_doc()) from monitoring.range import MonitoringRange - from monitoring.range import RangeAlertOn mon_range = MonitoringRange("6") LOG.debug("Initialized range:\n" + pp(mon_range.as_dict())) @@ -220,7 +219,6 @@ def test_check_value(self): LOG.info(self.get_method_doc()) from monitoring.range import MonitoringRange - from monitoring.range import RangeAlertOn test_data = ( ( diff --git a/tests/test_10_monitoring_threshold.py b/tests/test_10_monitoring_threshold.py index 5c0a43e..a11f158 100755 --- a/tests/test_10_monitoring_threshold.py +++ b/tests/test_10_monitoring_threshold.py @@ -83,13 +83,13 @@ def test_use_ranges(self): LOG.debug("Test, whether critical is set.") self.assertTrue(threshold.critical.is_set) - LOG.debug(f"Test, whether warning.start == 0.") + LOG.debug("Test, whether warning.start == 0.") self.assertEqual(threshold.warning.start, 0) LOG.debug(f"Test, whether warning.end == {warn_value}.") self.assertEqual(threshold.warning.end, warn_value) - LOG.debug(f"Test, whether critical.start == 0.") + LOG.debug("Test, whether critical.start == 0.") self.assertEqual(threshold.critical.start, 0) LOG.debug(f"Test, whether critical.end == {crit_value}.") @@ -116,11 +116,6 @@ def test_get_status(self): LOG.info(self.get_method_doc()) from monitoring import MonitoringThreshold - from monitoring import MonitoringObject - - ok = MonitoringObject.errors["OK"] - warning = MonitoringObject.errors["WARNING"] - critical = MonitoringObject.errors["CRITICAL"] test_data = ( { @@ -195,7 +190,7 @@ def test_get_status(self): for test_token in test_data: if self.verbose >= 1: print() - + if "warning" in test_token: w = test_token["warning"] else: From 323e7844da586b52883a5753989f76b159d0f078 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 12 May 2026 15:00:18 +0200 Subject: [PATCH 5/9] Fixing update-env.sh. --- update-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update-env.sh b/update-env.sh index 35689bc..db92167 100755 --- a/update-env.sh +++ b/update-env.sh @@ -188,7 +188,7 @@ get_options() { local tmp= local short_options="dvqhV" - local long_options="debug,verbose,quiet,help,version" + local long_options="debug,verbose,quiet,nocolor,help,version" set +e tmp=$( getopt -o "${short_options}" --long "${long_options}" -n "${BASENAME}" -- "$@" ) From 28970c3e93392adc31ae83c99532be6e80aa8a25 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 12 May 2026 15:01:46 +0200 Subject: [PATCH 6/9] Adding check-dns-zone.sh --- check-dns-zone.sh | 338 ++++++++++++++++++++++++++++++++++++++++++++++ update-env.sh | 2 +- 2 files changed, 339 insertions(+), 1 deletion(-) create mode 100755 check-dns-zone.sh diff --git a/check-dns-zone.sh b/check-dns-zone.sh new file mode 100755 index 0000000..e866dd4 --- /dev/null +++ b/check-dns-zone.sh @@ -0,0 +1,338 @@ +#!/bin/bash +# shellcheck disable=SC2317 # Don't warn about unreachable commands in this file +# +# After ideas from https://raw.githubusercontent.com/ableyjoe/checksig.sh/master/checksig.sh +# + +set -e +set -u + +VERBOSE="" +DEBUG="" + +VERSION="0.1" + +BASENAME=$(basename "${0}" ) +BASE_DIR=$( dirname "$0" ) +cd "${BASE_DIR}" +BASE_DIR=$( readlink -f . ) +MAN_PARENT_DIR="data/share/man" + +# DIG Options +DNSSEC= +SERVER= +ZONE= +WARN=$(( 2 * 7 * 24 * 60 * 60 )) +CRIT=$(( 7 * 24 * 60 * 60 )) + +#------------------------------------------------------------------------------ +description() { + cat <<-EOF + Checks the validity of the given DNS zone by retrieving the SOA for this zone. + + If the option --dnssec is given, additionally the DNSSEC RRSIG of the SOA is + checked for its existence and its remaining livetime. + + The threshold may be given as integers of seconds, or as minutes with the suffix 'm', + as hours with the suffix 'h' or as days with the suffix 'd'. + + EOF + +} + +#------------------------------------------------------------------------------ +usage() { + + cat <<-EOF + Usage: ${BASENAME} [-d|--debug] [-v|--verbose] [-D|--dnssec] [-s|--server NAMESERVER] [-w|--warn WARNING_TIME] [-c|--crit CRITICAL_TIME} -z|--zone ZONE + ${BASENAME} [-h|--help] + ${BASENAME} [-V|--version] + + Options: + -d|--debug Debug output (bash -x). + -v|--verbose Set verbosity on. + -D|--dnssec Check the DNSSEC RRDS of the SOA of the zone. + -s|--server NAMESERVER + The nameserver to use for checking the zone. + If omitted, one arbitrary nameserver from /etc/resolv.conf is used. + -w|--warn WARNING_TIME + The threshold for warning about the remaining livetime of the RRSIG. + Not used, if --dnssec was not given. + Default: ${WARN} seconds. + -c|--crit CRITICAL_TIME + The threshold for critical about the remaining livetime of the RRSIG. + Not used, if --dnssec was not given. + Default: ${CRIT} seconds. + -z|--zone ZONE The zone to check. Mandatory option. + -h|--help Show this output and exit. + -V|--version Prints out version number of the script and exit. + + EOF + +} + +#------------------------------------------------------------------------------ +timespec () { + local val=$(echo $1 | tr -d dhm) + + case $1 in + *m) + echo "$((${val} * 60))" + ;; + *h) + echo "$((${val} * 60 * 60))" + ;; + *d) + echo "$((${val} * 60 * 60 * 24))" + ;; + *) + echo $1 + ;; + esac +} + +#------------------------------------------------------------------------------ +seconds2human() { + + local seconds_total="$1" + local out="" + + local seconds=$(( ${seconds_total} % 60 )) + local minutes_total=$(( ${seconds_total} / 60 )) + local minutes=$(( ${minutes_total} % 60 )) + local hours_total=$(( ${minutes_total} / 60 )) + local hours=$(( ${hours_total} % 24 )) + local days=$(( ${hours_total} / 24 )) + + if [[ "${days}" -gt 0 ]] ; then + out="${days}d ${hours}h ${minutes}m ${seconds}s" + elif [[ "${hours}" -gt 0 ]] ; then + out="${hours}h ${minutes}m ${seconds}s" + elif [[ "${minutes_total}" -gt 0 ]] ; then + out="${minutes}m ${seconds}s" + else + out="${seconds}s" + fi + + echo "${out}" + +} + +#------------------------------------------------------------------------------ +get_options() { + + local tmp= + local short_options="dvDs:z:w:c:hV" + local long_options="debug,verbose,dnssec,server:,zone:,warn:,crit:,help,version" + + set +e + tmp=$( getopt -o "${short_options}" --long "${long_options}" -n "${BASENAME}" -- "$@" ) + ret="$?" + if [[ "${ret}" != 0 ]] ; then + echo "" >&2 + usage >&2 + exit 1 + fi + set -e + + # Note the quotes around `$TEMP': they are essential! + eval set -- "${tmp}" + + while true ; do + case "$1" in + -d|--debug) + DEBUG="y" + shift + ;; + -v|--verbose) + VERBOSE="y" + shift + ;; + -D|--dnssec) + DNSSEC="y" + shift + ;; + -s|--server) + SERVER="$2" + shift + shift + ;; + -w|--warn) + WARN=$( timespec "$2" ) + shift + shift + ;; + -c|--crit) + CRIT=$( timespec "$2" ) + shift + shift + ;; + -z|--zone) + ZONE="$2" + shift + shift + ;; + -h|--help) + description + echo + usage + exit 0 + ;; + -V|--version) + echo "${BASENAME} version: ${VERSION}" + exit 0 + ;; + --) shift + break + ;; + *) echo "Internal error!" + exit 1 + ;; + esac + done + + if [[ "${DEBUG}" = "y" ]] ; then + set -x + fi + + if [[ -z "${ZONE}" ]] ; then + echo "UNKNOWN - ${BASENAME}: No zone was given." + echo + usage + exit 3 + fi + + if [[ "${WARN}" -lt "${CRIT}" ]] ; then + echo "UNKNOWN - ${BASENAME}: The warning threshold (${WARN} seconds) is less than the critical threshold (${CRIT} seconds), which is weird." + exit 3 + fi + +} + +#------------------------------------------------------------------------------ +check_soa() { + + local cmd + local response + local server + local remaining + local msg + local remaining_out + local warn_out + local crit_out + + cmd="dig \"${ZONE}\" " + + if [[ -n "${SERVER}" ]] ; then + cmd+="\"@${SERVER}\" " + fi + + cmd+="SOA +nocomments +noadditional +nocmd +noquestion" + + if [[ "${DNSSEC}" ]] ; then + cmd+=" +dnssec" + fi + + if [[ "${VERBOSE}" ]] ; then + echo "Executing: ${cmd}" >&2 + fi + response=$( eval ${cmd} ) + if [[ "${VERBOSE}" ]] ; then + echo -e "Response:\n${response}" >&2 + fi + + server=$( echo "${response}" | grep -P -i '^;; SERVER:' | sed -e 's/^;; SERVER:[ ][ ]*//i' ) + + if echo "${response}" | grep -P -q -i "^${ZONE}\\.\\s.*\\ssoa\\s" ; then + : + else + echo "CRITICAL - ${BASENAME}: Did not found SOA of zone '${ZONE}', as observed on server ${server}." + exit 2 + fi + + msg="Found SOA of zone '${ZONE}', as observed on server ${server}." + + if [[ "${DNSSEC}" ]] ; then + + remaining= + remaining=$( echo "${response}" | gawk ' + /RRSIG/ { + expiration = $9; + time_left = mktime(\ + substr(expiration, 1, 4) " " \ + substr(expiration, 5, 2) " " \ + substr(expiration, 7, 2) " " \ + substr(expiration, 9, 2) " " \ + substr(expiration, 11, 2) " " \ + substr(expiration, 13, 2)) - systime(); + if (remaining) + remaining = (remaining > time_left ? time_left : remaining); + else + remaining = time_left + } + + END { + print remaining; + } + ' ) + + remaining_out=$( seconds2human "${remaining}" ) + warn_out=$( seconds2human "${WARN}" ) + crit_out=$( seconds2human "${CRIT}" ) + + if [[ "${VERBOSE}" ]] ; then + echo -e "Remaining: ${remaining_out}" >&2 + fi + + if [[ -z "${remaining}" ]] ; then + msg+=" Could not find RRSIG of SOA or the signature expiration for zone '${ZONE}'." + echo "CRITICAL - ${BASENAME}: ${msg}" + exit 2 + fi + + if [[ "${remaining}" -lt 0 ]] ; then + msg+=" Signatures in zone '${ZONE}' expired $((-1 * ${remaining})) seconds ago." + echo "CRITICAL - ${BASENAME}: ${msg}" + exit 2 + fi + + if [[ "${remaining}" -lt "${CRIT}" ]] ; then + msg+=" Remaining signature validity for zone '${ZONE}' is in ${remaining_out}, less than the critical threshold of ${crit_out}." + echo "CRITICAL - ${BASENAME}: ${msg}" + exit 2 + fi + + if [[ "${remaining}" -lt "${WARN}" ]] ; then + msg+=" Remaining signature validity for zone '${ZONE}' is ${remaining_out}, less than the warning threshold of ${warn_out}." + echo "WARNING - ${BASENAME}: ${msg}" + exit 1 + fi + + msg+" Remaining signature validity for zone '${ZONE}' is ${remaining} seconds." + fi + + echo "OK - ${BASENAME}: ${msg}" + exit 0 + +} + +################################################################################ +## +## Main +## +################################################################################ + +#------------------------------------------------------------------------------ +main() { + + get_options "$@" + check_soa + +} + +main "$@" + + +exit 0 + +# vim: ts=4 list diff --git a/update-env.sh b/update-env.sh index db92167..7d6e067 100755 --- a/update-env.sh +++ b/update-env.sh @@ -8,7 +8,7 @@ VERBOSE="n" DEBUG="n" QUIET='n' -VERSION="3.0" +VERSION="3.1" # console colors: RED="" From 13f67e782c14f252d182619a762768810c6cd804 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 12 May 2026 15:16:13 +0200 Subject: [PATCH 7/9] Applying shellcheck to check-dns-zone.sh --- check-dns-zone.sh | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/check-dns-zone.sh b/check-dns-zone.sh index e866dd4..3aaed2e 100755 --- a/check-dns-zone.sh +++ b/check-dns-zone.sh @@ -16,7 +16,6 @@ BASENAME=$(basename "${0}" ) BASE_DIR=$( dirname "$0" ) cd "${BASE_DIR}" BASE_DIR=$( readlink -f . ) -MAN_PARENT_DIR="data/share/man" # DIG Options DNSSEC= @@ -73,20 +72,22 @@ usage() { #------------------------------------------------------------------------------ timespec () { - local val=$(echo $1 | tr -d dhm) + lorcval val + + val=$(echo "$1" | tr -d dhm) case $1 in *m) - echo "$((${val} * 60))" + echo "$(( val * 60))" ;; *h) - echo "$((${val} * 60 * 60))" + echo "$(( val * 60 * 60 ))" ;; *d) - echo "$((${val} * 60 * 60 * 24))" + echo "$(( val * 60 * 60 * 24 ))" ;; *) - echo $1 + echo "$1" ;; esac } @@ -97,12 +98,12 @@ seconds2human() { local seconds_total="$1" local out="" - local seconds=$(( ${seconds_total} % 60 )) - local minutes_total=$(( ${seconds_total} / 60 )) - local minutes=$(( ${minutes_total} % 60 )) - local hours_total=$(( ${minutes_total} / 60 )) - local hours=$(( ${hours_total} % 24 )) - local days=$(( ${hours_total} / 24 )) + local seconds=$(( seconds_total % 60 )) + local minutes_total=$(( seconds_total / 60 )) + local minutes=$(( minutes_total % 60 )) + local hours_total=$(( minutes_total / 60 )) + local hours=$(( hours_total % 24 )) + local days=$(( hours_total / 24 )) if [[ "${days}" -gt 0 ]] ; then out="${days}d ${hours}h ${minutes}m ${seconds}s" @@ -236,6 +237,7 @@ check_soa() { if [[ "${VERBOSE}" ]] ; then echo "Executing: ${cmd}" >&2 fi + # shellcheck disable=disable=SC2086,SC2294 response=$( eval ${cmd} ) if [[ "${VERBOSE}" ]] ; then echo -e "Response:\n${response}" >&2 @@ -291,7 +293,7 @@ check_soa() { fi if [[ "${remaining}" -lt 0 ]] ; then - msg+=" Signatures in zone '${ZONE}' expired $((-1 * ${remaining})) seconds ago." + msg+=" Signatures in zone '${ZONE}' expired $((-1 * remaining)) seconds ago." echo "CRITICAL - ${BASENAME}: ${msg}" exit 2 fi @@ -308,7 +310,7 @@ check_soa() { exit 1 fi - msg+" Remaining signature validity for zone '${ZONE}' is ${remaining} seconds." + msg+=" Remaining signature validity for zone '${ZONE}' is ${remaining} seconds." fi echo "OK - ${BASENAME}: ${msg}" From cdf9ea5c9c4e7b849ca348b401815bc3e02de5d4 Mon Sep 17 00:00:00 2001 From: cruelsmith <92088441+cruelsmith@users.noreply.github.com> Date: Tue, 12 May 2026 17:05:52 +0200 Subject: [PATCH 8/9] `check-dns-zone.sh` apply changes * replace awk by date from coreutils * using bash for substring and replacement * fix handling in case of missing rrsig responce --- check-dns-zone.sh | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/check-dns-zone.sh b/check-dns-zone.sh index 3aaed2e..634eb07 100755 --- a/check-dns-zone.sh +++ b/check-dns-zone.sh @@ -72,19 +72,15 @@ usage() { #------------------------------------------------------------------------------ timespec () { - lorcval val - - val=$(echo "$1" | tr -d dhm) - case $1 in *m) - echo "$(( val * 60))" + echo "$(( ${1/[dhm]/} * 60))" ;; *h) - echo "$(( val * 60 * 60 ))" + echo "$(( ${1/[dhm]/} * 60 * 60 ))" ;; *d) - echo "$(( val * 60 * 60 * 24 ))" + echo "$(( ${1/[dhm]/} * 60 * 60 * 24 ))" ;; *) echo "$1" @@ -243,9 +239,10 @@ check_soa() { echo -e "Response:\n${response}" >&2 fi - server=$( echo "${response}" | grep -P -i '^;; SERVER:' | sed -e 's/^;; SERVER:[ ][ ]*//i' ) + server=$( echo "${response}" | grep -P -i '^;; SERVER:') + server="${server/;; SERVER: /}" - if echo "${response}" | grep -P -q -i "^${ZONE}\\.\\s.*\\ssoa\\s" ; then + if echo "${response}" | grep -P -q -i "^${ZONE}\\.\\s.*\\ssoa\\s" ; then : else echo "CRITICAL - ${BASENAME}: Did not found SOA of zone '${ZONE}', as observed on server ${server}." @@ -256,27 +253,15 @@ check_soa() { if [[ "${DNSSEC}" ]] ; then - remaining= - remaining=$( echo "${response}" | gawk ' - /RRSIG/ { - expiration = $9; - time_left = mktime(\ - substr(expiration, 1, 4) " " \ - substr(expiration, 5, 2) " " \ - substr(expiration, 7, 2) " " \ - substr(expiration, 9, 2) " " \ - substr(expiration, 11, 2) " " \ - substr(expiration, 13, 2)) - systime(); - if (remaining) - remaining = (remaining > time_left ? time_left : remaining); - else - remaining = time_left - } - - END { - print remaining; - } - ' ) + if echo "${response}" | grep -P -q -i "^${ZONE}\\.\\s.*\\sRRSIG\\s" ; then + : + else + echo "CRITICAL - ${BASENAME}: Missing RRSIG response for SOA of '${ZONE}', as observed on server ${server}." + exit 2 + fi + + rrsig=( $( echo "${response}" | grep -P -i '\s+IN\s+RRSIG\s+SOA\s+') ) + remaining=$(( $(date --utc --date="${rrsig[8]:0:4}-${rrsig[8]:4:2}-${rrsig[8]:6:2} ${rrsig[8]:8:2}:${rrsig[8]:10:2}:${rrsig[8]:12:2}" +%s) - $(date --utc +%s) )) remaining_out=$( seconds2human "${remaining}" ) warn_out=$( seconds2human "${WARN}" ) @@ -293,7 +278,7 @@ check_soa() { fi if [[ "${remaining}" -lt 0 ]] ; then - msg+=" Signatures in zone '${ZONE}' expired $((-1 * remaining)) seconds ago." + msg+=" Signatures in zone '${ZONE}' expired ${remaining} seconds ago." echo "CRITICAL - ${BASENAME}: ${msg}" exit 2 fi From f4b740681f4fb02aa1545399cbbfbea1e785e9f5 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 12 May 2026 17:21:40 +0200 Subject: [PATCH 9/9] Changing thresholds for check-dns-zone.sh --- check-dns-zone.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/check-dns-zone.sh b/check-dns-zone.sh index 634eb07..d6c32eb 100755 --- a/check-dns-zone.sh +++ b/check-dns-zone.sh @@ -21,8 +21,8 @@ BASE_DIR=$( readlink -f . ) DNSSEC= SERVER= ZONE= -WARN=$(( 2 * 7 * 24 * 60 * 60 )) -CRIT=$(( 7 * 24 * 60 * 60 )) +WARN=$(( 7 * 24 * 60 * 60 )) +CRIT=$(( 5 * 24 * 60 * 60 )) #------------------------------------------------------------------------------ description() {