]> git.siccegge.de Git - dane-monitoring-plugins.git/blob - check_dnssec
1d308539704395b8a2c4012c483b5d41d4027b54
[dane-monitoring-plugins.git] / check_dnssec
1 #!/usr/bin/python3
2
3 from __future__ import print_function
4
5 import sys
6 import argparse
7 import logging
8 import hashlib
9 import codecs
10
11 from unbound import RR_TYPE_SOA, RR_TYPE_DNSKEY, RR_TYPE_NS
12 from unbound import RR_TYPE_A, RR_TYPE_AAAA
13 from ldns import LDNS_SECTION_ANSWER
14 from ldns import ldns_wire2pkt
15
16
17 from check_dane.resolve import Resolver, ResolverException
18 from check_dane.resolve import format_address, dnssec_verify_rrsig_validity
19
20
21 def check_main_records(resolver, zone, args):
22 """Confirms that the necessary records on a zone all verify"""
23 retval = 0
24
25 for rrtype in [RR_TYPE_DNSKEY, RR_TYPE_NS, RR_TYPE_SOA]:
26 result = resolver.resolve(zone, rrtype=rrtype, secure=True)
27 nretval = dnssec_verify_rrsig_validity(result.packet, args.warndays, args.critdays)
28 retval = max(nretval, retval)
29
30 return retval
31
32
33 def check_nsec_cycle(resolver, zone, args):
34 """Confirms that NSEC records are completely available"""
35 return 0
36
37
38 def check_synced(resolver, zone, args):
39 """Makes sure the zone is at the same serial on all secondaries"""
40 try:
41 result = resolver.resolve(zone, RR_TYPE_NS, secure=True)
42
43 if result.data is None:
44 logging.error("No nameservers found for zone %s", zone)
45 return 2
46
47 nameservers = result.data.as_domain_list()
48 nameserver_ips = []
49 for nameserver in nameservers:
50 ips = []
51 for rrtype in [RR_TYPE_AAAA, RR_TYPE_A]:
52 result = resolver.resolve(nameserver, rrtype=rrtype, secure=True)
53 if result.data is not None:
54 ips = ips + [format_address(data, rrtype) for data in result.data.data]
55
56 if ips == []:
57 logging.warning("Could not find any address for nameserver %s", nameserver)
58
59 nameserver_ips = nameserver_ips + ips
60
61 if nameserver_ips == []:
62 logging.error("No authoritive nameserver for %s could be resolved", zone)
63 return 2
64
65 results = dict()
66 for ip in nameserver_ips:
67 newresolver = Resolver(args.ancor, ip)
68
69 # We can't request secure here as the authoritative
70 # nameservers for the zone won't let us retrieve the
71 # signature chain below their own zone. We'll later
72 # recheck the SOA record using the main recursor
73 #
74 # Alternatively one could get the DS / DNSKEY for the zone with
75 # resolver and add it to newresolver as a hint.
76 result = newresolver.resolve(zone, rrtype=RR_TYPE_SOA)
77
78 s, result = ldns_wire2pkt(result.packet)
79 if s != 0:
80 logging.error("Parsing packet failed with errorcode %d", s)
81 return 2
82
83 rrs = result.rr_list_by_type(RR_TYPE_SOA, LDNS_SECTION_ANSWER).rrs()
84 soa = next(rrs)
85
86 serial = str(soa).split()[6]
87 results[serial] = results.get(serial, []) + [ip]
88
89 if len(results) == 1:
90 return 0
91 else:
92 logging.error("different SOAs: %s", results)
93 return 2
94
95 except ResolverException as e:
96 logging.error(e.message)
97
98
99 def main():
100 logging.basicConfig(format='%(levelname)5s %(message)s')
101 parser = argparse.ArgumentParser()
102 parser.add_argument("Zone")
103
104 parser.add_argument("--verbose", action="store_true")
105 parser.add_argument("--quiet", action="store_true")
106
107 parser.add_argument("-a", "--ancor",
108 action="store", type=str, default="/etc/unbound/root.key",
109 help="DNSSEC root ancor")
110
111 parser.add_argument("--nsec", action="store_false",
112 help="Verifies the complete NSEC/NSEC3 cycle (default: false)")
113 parser.add_argument("--warndays", type=int, default=-1,
114 help="Days before rrsig expiration to warn")
115 parser.add_argument("--critdays", type=int, default=-1,
116 help="Days before rrsig expiration to raise error")
117
118
119 args = parser.parse_args()
120
121 if args.verbose:
122 logging.getLogger().setLevel(logging.DEBUG)
123 elif args.quiet:
124 logging.getLogger().setLevel(logging.WARNING)
125 else:
126 logging.getLogger().setLevel(logging.INFO)
127
128 resolver = Resolver(args.ancor)
129 zone = args.Zone.encode('idna').decode()
130
131 retval1 = check_synced(resolver, zone, args)
132 retval2 = check_main_records(resolver, zone, args)
133 if args.nsec:
134 retval3 = check_nsec_cycle(resolver, zone, args)
135 return max(retval1, retval2, retval3)
136 else:
137 return max(retval1, retval2)
138
139 if __name__ == '__main__':
140 sys.exit(main())