]> git.siccegge.de Git - tools.git/blob - dnssec-check
Add check for same SOA on all NSes
[tools.git] / dnssec-check
1 #!/usr/bin/python
2
3 from __future__ import print_function
4 import ldns
5 from unbound import ub_ctx, idn2dname, ub_strerror
6 from unbound import RR_TYPE_SOA, RR_TYPE_DNSKEY, RR_TYPE_RRSIG, RR_TYPE_NS
7 from unbound import RR_TYPE_A, RR_TYPE_AAAA
8 from optparse import OptionParser
9 import sys
10 from datetime import datetime, timedelta
11
12 def parse_rrsig_expire(expirestring):
13 expires = datetime(int(expirestring[:4]),
14 int(expirestring[4:6]),
15 int(expirestring[6:8]),
16 int(expirestring[8:10]),
17 int(expirestring[10:12]),
18 int(expirestring[12:14]))
19
20 delta = expires - datetime.utcnow()
21 return delta
22
23 def check_dnssec_expire(resolver, name, warn, crit):
24 for rrtype in [RR_TYPE_SOA, RR_TYPE_DNSKEY]:
25 s, result = resolver.resolve(name, rrtype=rrtype)
26 if 0 != s:
27 ub_strerror(s)
28 return 3
29
30 if not result.secure:
31 print("CRIT (does not verify) %s" % (name, ))
32 return 2
33
34 s, packet = ldns.ldns_wire2pkt(result.packet)
35 rrsigs = packet.rr_list_by_type(RR_TYPE_RRSIG, ldns.LDNS_SECTION_ANSWER).rrs()
36
37 for rrsig in rrsigs:
38 delta = parse_rrsig_expire(str(rrsig.rrsig_expiration()))
39
40 if delta < crit:
41 print("CRIT (expires in %s) %s" % (delta, name))
42 return 2
43 elif delta < warn:
44 print("WARN (expires in %s) %s" % (delta, name))
45 return 1
46 return 0
47
48 def check_zone_synced(resolver, name):
49 s, result = resolver.resolve(name, RR_TYPE_NS)
50 if 0 != s:
51 ub_strerror(s)
52 return 3
53
54 nameservers = result.data.as_domain_list()
55 nsips = []
56 for nameserver in nameservers:
57 for rrtype in [RR_TYPE_AAAA, RR_TYPE_A]:
58 s, result = resolver.resolve(nameserver, rrtype=rrtype)
59 if 0 != s:
60 ub_strerror(s)
61 return 3
62 nsips = nsips + result.data.as_address_list()
63
64 results = dict()
65 for ip in nsips:
66 newres = ub_ctx()
67 newres.set_fwd(ip)
68 s, result = newres.resolve(name, rrtype=RR_TYPE_SOA)
69 if 0 != s:
70 ub_strerror(s)
71 return 3
72
73 s, result = ldns.ldns_wire2pkt(result.packet)
74 soas = list(result.rr_list_by_type(RR_TYPE_SOA,
75 ldns.LDNS_SECTION_ANSWER).rrs())
76
77 if len(soas) != 1:
78 return 3
79 serial = str(soas[0]).split()[6]
80 results[serial] = results.get(serial, []) + [ip]
81
82 if len(results) == 1:
83 return 0
84 else:
85 print("CRIT (different SOAs): %s", results)
86 return 2
87
88 def main():
89 parser = OptionParser()
90 parser.add_option("-n", "--name",
91 action="append", type="string", dest="names",
92 help="DNS Names to check")
93 parser.add_option("-a", "--ancor",
94 action="store", type="string", dest="ancor",
95 default="/etc/unbound/root.key",
96 help="DNSSEC root ancor")
97 parser.add_option("-w", "--warning-days",
98 action="store", type=int, dest="warn", default=5,
99 help="minimum remaining validity in days before a warning is issued")
100 parser.add_option("-c", "--critical-days",
101 action="store", type=int, dest="crit", default=2,
102 help="minimum remaining validity in days before a warning is issued")
103 parser.add_option("-v", action="store_true", dest="verbose", default=False)
104 parser.add_option("-q", action="store_false", dest="verbose")
105
106 opts, _args = parser.parse_args()
107 if not opts.names:
108 parser.error("needs at least one DNS name")
109
110 resolver = ub_ctx()
111 resolver.add_ta_file(opts.ancor)
112 encoding = sys.getfilesystemencoding()
113
114 final = 0
115 for name in opts.names:
116 name = idn2dname(name.decode(encoding))
117 result1 = check_zone_synced(resolver, name)
118 if result1 == 2:
119 final = 2
120 elif result1 == 1 and final != 2:
121 final = 1
122 elif result1 == 3 and final not in [1, 2]:
123 final = 3
124
125 result2 = check_dnssec_expire(resolver, name,
126 timedelta(opts.warn), timedelta(opts.crit))
127 if result1 + result2 == 0 and opts.verbose:
128 print("OK %s" % name)
129 if result2 == 2:
130 final = 2
131 elif result2 == 1 and final != 2:
132 final = 1
133 elif result2 == 3 and final not in [1, 2]:
134 final = 3
135
136 sys.exit(final)
137
138 if __name__ == "__main__":
139 main()