rebuild actual zonefiles
[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 if not result.secure:
55 print("CRIT (does not verify) %s" % (name, ))
56 return 2
57
58 nameservers = result.data.as_domain_list()
59 nsips = []
60 for nameserver in nameservers:
61 for rrtype in [RR_TYPE_AAAA, RR_TYPE_A]:
62 s, result = resolver.resolve(nameserver, rrtype=rrtype)
63 if 0 != s:
64 ub_strerror(s)
65 return 3
66 nsips = nsips + result.data.as_address_list()
67
68 results = dict()
69 for ip in nsips:
70 newres = ub_ctx()
71 newres.set_fwd(ip)
72 s, result = newres.resolve(name, rrtype=RR_TYPE_SOA)
73 if 0 != s:
74 ub_strerror(s)
75 return 3
76
77 s, result = ldns.ldns_wire2pkt(result.packet)
78 soas = list(result.rr_list_by_type(RR_TYPE_SOA,
79 ldns.LDNS_SECTION_ANSWER).rrs())
80
81 if len(soas) != 1:
82 return 3
83 serial = str(soas[0]).split()[6]
84 results[serial] = results.get(serial, []) + [ip]
85
86 if len(results) == 1:
87 return 0
88 else:
89 print("CRIT (different SOAs): %s", results)
90 return 2
91
92 def main():
93 parser = OptionParser()
94 parser.add_option("-n", "--name",
95 action="append", type="string", dest="names",
96 help="DNS Names to check")
97 parser.add_option("-a", "--ancor",
98 action="store", type="string", dest="ancor",
99 default="/etc/unbound/root.key",
100 help="DNSSEC root ancor")
101 parser.add_option("-w", "--warning-days",
102 action="store", type=int, dest="warn", default=5,
103 help="minimum remaining validity in days before a warning is issued")
104 parser.add_option("-c", "--critical-days",
105 action="store", type=int, dest="crit", default=2,
106 help="minimum remaining validity in days before a warning is issued")
107 parser.add_option("-v", action="store_true", dest="verbose", default=False)
108 parser.add_option("-q", action="store_false", dest="verbose")
109
110 opts, _args = parser.parse_args()
111 if not opts.names:
112 parser.error("needs at least one DNS name")
113
114 resolver = ub_ctx()
115 resolver.add_ta_file(opts.ancor)
116 encoding = sys.getfilesystemencoding()
117
118 final = 0
119 for name in opts.names:
120 name = idn2dname(name.decode(encoding))
121 result1 = check_zone_synced(resolver, name)
122 if result1 == 2:
123 final = 2
124 elif result1 == 1 and final != 2:
125 final = 1
126 elif result1 == 3 and final not in [1, 2]:
127 final = 3
128
129 result2 = check_dnssec_expire(resolver, name,
130 timedelta(opts.warn), timedelta(opts.crit))
131 if result1 + result2 == 0 and opts.verbose:
132 print("OK %s" % name)
133 if result2 == 2:
134 final = 2
135 elif result2 == 1 and final != 2:
136 final = 1
137 elif result2 == 3 and final not in [1, 2]:
138 final = 3
139
140 sys.exit(final)
141
142 if __name__ == "__main__":
143 main()