1# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 2# 3# SPDX-License-Identifier: MPL-2.0 4# 5# This Source Code Form is subject to the terms of the Mozilla Public 6# License, v. 2.0. If a copy of the MPL was not distributed with this 7# file, you can obtain one at https://mozilla.org/MPL/2.0/. 8# 9# See the COPYRIGHT file distributed with this work for additional 10# information regarding copyright ownership. 11 12from collections import defaultdict 13from .dnskey import * 14from .keydict import * 15from .keyevent import * 16 17 18class eventlist: 19 _K = defaultdict(lambda: defaultdict(list)) 20 _Z = defaultdict(lambda: defaultdict(list)) 21 _zones = set() 22 _kdict = None 23 24 def __init__(self, kdict): 25 properties = [ 26 "SyncPublish", 27 "Publish", 28 "SyncDelete", 29 "Activate", 30 "Inactive", 31 "Delete", 32 ] 33 self._kdict = kdict 34 for zone in kdict.zones(): 35 self._zones.add(zone) 36 for alg, keys in kdict[zone].items(): 37 for k in keys.values(): 38 for prop in properties: 39 t = k.gettime(prop) 40 if not t: 41 continue 42 e = keyevent(prop, k, t) 43 if k.sep: 44 self._K[zone][alg].append(e) 45 else: 46 self._Z[zone][alg].append(e) 47 48 self._K[zone][alg] = sorted( 49 self._K[zone][alg], key=lambda event: event.when 50 ) 51 self._Z[zone][alg] = sorted( 52 self._Z[zone][alg], key=lambda event: event.when 53 ) 54 55 # scan events per zone, algorithm, and key type, in order of 56 # occurrence, noting inconsistent states when found 57 def coverage(self, zone, keytype, until, output=None): 58 def noop(*args, **kwargs): 59 pass 60 61 if not output: 62 output = noop 63 64 no_zsk = True if (keytype and keytype == "KSK") else False 65 no_ksk = True if (keytype and keytype == "ZSK") else False 66 kok = zok = True 67 found = False 68 69 if zone and not zone in self._zones: 70 output("ERROR: No key events found for %s" % zone) 71 return False 72 73 if zone: 74 found = True 75 if not no_ksk: 76 kok = self.checkzone(zone, "KSK", until, output) 77 if not no_zsk: 78 zok = self.checkzone(zone, "ZSK", until, output) 79 else: 80 for z in self._zones: 81 if not no_ksk and z in self._K.keys(): 82 found = True 83 kok = self.checkzone(z, "KSK", until, output) 84 if not no_zsk and z in self._Z.keys(): 85 found = True 86 zok = self.checkzone(z, "ZSK", until, output) 87 88 if not found: 89 output("ERROR: No key events found") 90 return False 91 92 return kok and zok 93 94 def checkzone(self, zone, keytype, until, output): 95 allok = True 96 if keytype == "KSK": 97 kz = self._K[zone] 98 else: 99 kz = self._Z[zone] 100 101 for alg in kz.keys(): 102 output( 103 "Checking scheduled %s events for zone %s, " 104 "algorithm %s..." % (keytype, zone, dnskey.algstr(alg)) 105 ) 106 ok = eventlist.checkset(kz[alg], keytype, until, output) 107 if ok: 108 output("No errors found") 109 allok = allok and ok 110 111 return allok 112 113 @staticmethod 114 def showset(eventset, output): 115 if not eventset: 116 return 117 output(" " + eventset[0].showtime() + ":", skip=False) 118 for event in eventset: 119 output(" %s: %s" % (event.what, repr(event.key)), skip=False) 120 121 @staticmethod 122 def checkset(eventset, keytype, until, output): 123 groups = list() 124 group = list() 125 126 # collect up all events that have the same time 127 eventsfound = False 128 for event in eventset: 129 # we found an event 130 eventsfound = True 131 132 # add event to current group 133 if not group or group[0].when == event.when: 134 group.append(event) 135 136 # if we're at the end of the list, we're done. if 137 # we've found an event with a later time, start a new group 138 if group[0].when != event.when: 139 groups.append(group) 140 group = list() 141 group.append(event) 142 143 if group: 144 groups.append(group) 145 146 if not eventsfound: 147 output("ERROR: No %s events found" % keytype) 148 return False 149 150 active = published = None 151 for group in groups: 152 if until and calendar.timegm(group[0].when) > until: 153 output( 154 "Ignoring events after %s" 155 % time.strftime("%a %b %d %H:%M:%S UTC %Y", time.gmtime(until)) 156 ) 157 return True 158 159 for event in group: 160 (active, published) = event.status(active, published) 161 162 eventlist.showset(group, output) 163 164 # and then check for inconsistencies: 165 if not active: 166 output("ERROR: No %s's are active after this event" % keytype) 167 return False 168 elif not published: 169 output("ERROR: No %s's are published after this event" % keytype) 170 return False 171 elif not published.intersection(active): 172 output( 173 "ERROR: No %s's are both active and published " 174 "after this event" % keytype 175 ) 176 return False 177 178 return True 179