Browse Source

Icinga2: Add check_dns_sync.

Signed-off-by: Maximilian Wilhelm <max@sdn.clinic>
Maximilian Wilhelm 4 years ago
parent
commit
4671116203
4 changed files with 209 additions and 4 deletions
  1. 31 0
      icinga2/commands.d/dns_sync.conf
  2. 1 4
      icinga2/init.sls
  3. 160 0
      icinga2/plugins/check_dns_sync
  4. 17 0
      icinga2/services/dns_sync.conf

+ 31 - 0
icinga2/commands.d/dns_sync.conf

@@ -0,0 +1,31 @@
+#
+# Check DNS sync
+object CheckCommand "dns_sync" {
+	import "plugin-check-command"
+
+	command = [  "/usr/local/share/monitoring-plugins/check_dns_sync" ]
+
+	arguments = {
+		"--reference-ns" = "$reference_ns$"
+		"--replica-ns" = "$replica_ns$"
+		"--timeout" = {
+			set_if = "$timeout$"
+			value = "$timeout$"
+		}
+		"--check_mode" = {
+			set_if = "$check_mode$"
+			value = "$check_mode$"
+		}
+		"zones" = {
+			value = "$zones$"
+			skip_key = true
+			order = 99
+		}
+	}
+
+	vars.reference_ns = ""			# IP of reference NS Server
+	vars.replica_ns = ""			# IP of replica NS Server
+	vars.timeout = ""			# Timeout for DNS operations
+	vars.check_mode = "serial"		# Check mode: serial or axfr
+	vars.zones = []				# List of zone names to be checked
+}

+ 1 - 4
icinga2/init.sls

@@ -23,12 +23,9 @@ monitoring-plugin-pkgs:
       - monitoring-plugins
       - nagios-plugins-contrib
       - libyaml-syck-perl
-{% if grains['oscodename'] == 'jessie' %}
-      - libnagios-plugin-perl
-{% else %}
       - libmonitoring-plugin-perl
-{% endif %}
       - lsof
+      - python3-dnspython
     - watch_in:
       - service: icinga2
 

+ 160 - 0
icinga2/plugins/check_dns_sync

@@ -0,0 +1,160 @@
+#!/usr/bin/python3
+#
+# Maximilian Wilhelm <max@rfc2324.org>
+#  --  Mon 30 Mar 2020 11:55:47 PM CEST
+#
+
+import argparse
+from dns.resolver import Resolver, NoNameservers
+from ipaddress import ip_address
+import sys
+import time
+
+# Exit code definitions
+OK = 0
+WARNING = 1
+CRITICAL = 2
+UNKNOWN = 3
+
+# Track start time
+time_start = time.time ()
+
+parser = argparse.ArgumentParser (description = 'Check DNS sync')
+parser.add_argument ('--reference-ns', required = True, help = 'IP address of reference NS')
+parser.add_argument ('--replica-ns', required = True, help = 'IP address of NS to be checked')
+parser.add_argument ('--check-mode', choices = [ 'serial', 'axfr' ], default = 'serial', help = 'Compare only serial or full zone content?')
+parser.add_argument ('--timeout', type = int, default = 10, help = 'Timeout for DNS operations')
+parser.add_argument ('--verbose', '-v', action = 'store_true', help = 'Be verbose in the output')
+parser.add_argument ('zones', nargs = '+', help = 'Zones to compare')
+
+args = parser.parse_args ()
+
+if args.check_mode == 'axfr':
+	print ("AXFR check mode not implemented yet. Send patches :)")
+	sys.exit (UNKNOWN)
+
+#
+# Helpers
+#
+
+def is_ip (ns):
+	try:
+		ip = ip_address (ns)
+	except ValueError:
+		return False
+
+	return True
+
+
+def check_zone (zone):
+	res = {
+		'state' : UNKNOWN,
+		'diff' : '',
+		'errors' : '',
+	}
+
+	if args.check_mode == 'serial':
+		try:
+			reference = reference_res.query (zone, 'SOA')
+		except Exception as e:
+			res['errors'] = "Error while checking reference NS %s: %s" % (args.reference_ns, e)
+			return res
+
+		try:
+			replica = replica_res.query (zone, 'SOA')
+		except Exception as e:
+			res['errors'] = "Error while checking replica NS %s: %s" % (args.replica_ns, e)
+			return res
+
+		try:
+			reference_serial = str (reference.response.answer[0]).split ()[6]
+			replica_serial = str (replica.response.answer[0]).split ()[6]
+		except AttributeError as a:
+			res['errors'] = a
+			return res
+		except IndexError as i:
+			res['errors'] = i
+			return res
+
+		if reference_serial == replica_serial:
+			res['state'] = OK
+		else:
+			res['state'] = CRITICAL
+			res['errors'] = "Serial mismatch: %s vs. %s" % (reference_serial, replica_serial)
+
+	return res
+
+
+#
+# Setup
+#
+
+# Check for possible badness
+if not is_ip (args.reference_ns):
+	print ("Error: Reference NS has to an IP address.")
+	sys.exit (CRITICAL)
+
+if not is_ip (args.replica_ns):
+	print ("Error: Replica NS has to an IP address.")
+	sys.exit (CRITICAL)
+
+if args.reference_ns == args.replica_ns:
+	print ("Error: Reference NS and replica NS must not be the same!")
+	sys.exit (CRITICAL)
+
+
+# Resolver for reference NS
+reference_res = Resolver (configure = False)
+reference_res.nameservers = [args.reference_ns]
+reference_res.lifetime = args.timeout
+
+# Resolver for NS to be checked
+replica_res = Resolver (configure = False)
+replica_res.nameservers = [args.replica_ns]
+replica_res.lifetime = args.timeout
+
+
+#
+# Let#s go
+#
+
+codes = {}
+ret_code = OK
+errors = ""
+in_sync = []
+
+for zone in args.zones:
+	check = check_zone (zone)
+
+	# Keep track of states
+	state = check['state']
+	codes[state] = codes.get (state, 0) + 1
+
+	if state == OK:
+		in_sync.append (zone)
+		continue
+
+	errors += "Zone '%s': %s\n" % (zone, check['errors'])
+
+	if state > ret_code:
+		ret_code = check['state']
+
+if errors:
+	print (errors)
+
+if in_sync:
+	if args.verbose:
+		print ("Zones in sync: %s" % ", ".join (sorted (in_sync)))
+
+time_delta = int (1000 * (time.time () - time_start))
+
+print ("Checked %d zones in %d ms. %d OK, %d WARN, %d CRIT, %d UNKN" % (
+	len (args.zones),
+	time_delta,
+	codes.get (OK, 0),
+	codes.get (WARNING, 0),
+	codes.get (CRITICAL ,0),
+	codes.get (UNKNOWN, 0),
+))
+
+sys.exit (ret_code)

+ 17 - 0
icinga2/services/dns_sync.conf

@@ -0,0 +1,17 @@
+#
+# Check DNS sync
+apply Service "dns_sync" {
+	import "generic-service"
+
+	check_command = "dns_sync"
+	vars.reference_ns = "80.70.181.60"
+	vars.replica_ns = host.address
+	vars.zones = [
+		"ffho.net",
+		"132.10.in-addr.arpa",
+		"30.172.in-addr.arpa",
+	]
+
+	assign where host.address && "dns-auth" in host.vars.roles
+	ignore where "dns-server-master" in host.vars.roles
+}