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 12 # pylint: disable=redefined-outer-name,unused-import 13 14 from datetime import timedelta 15 16 import pytest 17 18 import isctest 19 from isctest.kasp import Ipub, Iret 20 from isctest.util import param 21 from rollover.common import ( 22 pytestmark, 23 alg, 24 size, 25 TIMEDELTA, 26 ) 27 from rollover.setup import ( 28 configure_root, 29 configure_tld, 30 configure_zsk_prepub, 31 ) 32 33 CONFIG = { 34 "dnskey-ttl": TIMEDELTA["PT1H"], 35 "ds-ttl": TIMEDELTA["P1D"], 36 "max-zone-ttl": TIMEDELTA["P1D"], 37 "parent-propagation-delay": TIMEDELTA["PT1H"], 38 "publish-safety": TIMEDELTA["P1D"], 39 "purge-keys": TIMEDELTA["PT1H"], 40 "retire-safety": TIMEDELTA["P2D"], 41 "signatures-refresh": TIMEDELTA["P7D"], 42 "signatures-validity": TIMEDELTA["P14D"], 43 "zone-propagation-delay": TIMEDELTA["PT1H"], 44 } 45 POLICY = "zsk-prepub" 46 ZSK_LIFETIME = TIMEDELTA["P30D"] 47 LIFETIME_POLICY = int(ZSK_LIFETIME.total_seconds()) 48 IPUB = Ipub(CONFIG) 49 IRET = Iret(CONFIG) 50 KEYTTLPROP = CONFIG["dnskey-ttl"] + CONFIG["zone-propagation-delay"] 51 OFFSETS = {} 52 OFFSETS["step1-p"] = -int(TIMEDELTA["P7D"].total_seconds()) 53 OFFSETS["step2-p"] = -int(ZSK_LIFETIME.total_seconds() - IPUB.total_seconds()) 54 OFFSETS["step2-s"] = 0 55 OFFSETS["step3-p"] = -int(ZSK_LIFETIME.total_seconds()) 56 OFFSETS["step3-s"] = -int(IPUB.total_seconds()) 57 OFFSETS["step4-p"] = OFFSETS["step3-p"] - int(IRET.total_seconds()) 58 OFFSETS["step4-s"] = OFFSETS["step3-s"] - int(IRET.total_seconds()) 59 OFFSETS["step5-p"] = OFFSETS["step4-p"] - int(KEYTTLPROP.total_seconds()) 60 OFFSETS["step5-s"] = OFFSETS["step4-s"] - int(KEYTTLPROP.total_seconds()) 61 OFFSETS["step6-p"] = OFFSETS["step5-p"] - int(CONFIG["purge-keys"].total_seconds()) 62 OFFSETS["step6-s"] = OFFSETS["step5-s"] - int(CONFIG["purge-keys"].total_seconds()) 63 64 65 def bootstrap(): 66 data = { 67 "tlds": [], 68 "trust_anchors": [], 69 } 70 71 tlds = [] 72 for tld_name in [ 73 "autosign", 74 "manual", 75 ]: 76 delegations = configure_zsk_prepub(tld_name) 77 78 tld = configure_tld(tld_name, delegations) 79 tlds.append(tld) 80 81 data["tlds"].append(tld_name) 82 83 ta = configure_root(tlds) 84 data["trust_anchors"].append(ta) 85 86 return data 87 88 89 @pytest.mark.parametrize( 90 "tld", 91 [ 92 param("autosign"), 93 param("manual"), 94 ], 95 ) 96 def test_zsk_prepub_step1(tld, alg, size, ns3): 97 zone = f"step1.zsk-prepub.{tld}" 98 policy = f"{POLICY}-{tld}" 99 100 isctest.kasp.wait_keymgr_done(ns3, zone) 101 102 # manual-mode: Nothing changing in the zone, no 'dnssec -step' required. 103 # Note that the key was already generated during setup. 104 105 step = { 106 # Introduce the first key. This will immediately be active. 107 "zone": zone, 108 "keyprops": [ 109 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}", 110 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step1-p']}", 111 ], 112 # Next key event is when the successor ZSK needs to be published. 113 # That is the ZSK lifetime - prepublication time (minus time 114 # already passed). 115 "nextev": ZSK_LIFETIME - IPUB - timedelta(days=7), 116 } 117 isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 118 119 120 @pytest.mark.parametrize( 121 "tld", 122 [ 123 param("autosign"), 124 param("manual"), 125 ], 126 ) 127 def test_zsk_prepub_step2(tld, alg, size, ns3): 128 zone = f"step2.zsk-prepub.{tld}" 129 policy = f"{POLICY}-{tld}" 130 131 isctest.kasp.wait_keymgr_done(ns3, zone) 132 133 if tld == "manual": 134 # Same as step 1. 135 step = { 136 "zone": zone, 137 "keyprops": [ 138 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", 139 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", 140 ], 141 "manual-mode": True, 142 "nextev": None, 143 } 144 keys = isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 145 146 # Check logs. 147 tag = keys[1].key.tag 148 msg = f"keymgr-manual-mode: block ZSK rollover for key {zone}/ECDSAP256SHA256/{tag} (policy {policy})" 149 assert msg in ns3.log 150 151 # Force step. 152 with ns3.watch_log_from_here() as watcher: 153 ns3.rndc(f"dnssec -step {zone}") 154 watcher.wait_for_line( 155 f"zone {zone}/IN (signed): zone_rekey done: key {tag}/ECDSAP256SHA256" 156 ) 157 158 step = { 159 # it is time to pre-publish the successor zsk. 160 # zsk1 goal: omnipresent -> hidden 161 # zsk2 goal: hidden -> omnipresent 162 # zsk2 dnskey: hidden -> rumoured 163 "zone": zone, 164 "keyprops": [ 165 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}", 166 f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}", 167 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:{OFFSETS['step2-s']}", 168 ], 169 "keyrelationships": [1, 2], 170 # next key event is when the successor zsk becomes omnipresent. 171 # that is the dnskey ttl plus the zone propagation delay 172 "nextev": IPUB, 173 } 174 isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 175 176 177 @pytest.mark.parametrize( 178 "tld", 179 [ 180 param("autosign"), 181 param("manual"), 182 ], 183 ) 184 def test_zsk_prepub_step3(tld, alg, size, ns3): 185 zone = f"step3.zsk-prepub.{tld}" 186 policy = f"{POLICY}-{tld}" 187 188 isctest.kasp.wait_keymgr_done(ns3, zone) 189 190 if tld == "manual": 191 # Same as step 2, but DNSKEY has become OMNIPRESENT. 192 step = { 193 "zone": zone, 194 "keyprops": [ 195 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", 196 f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}", 197 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:hidden offset:{OFFSETS['step3-s']}", 198 ], 199 "keyrelationships": [1, 2], 200 "manual-mode": True, 201 "nextev": None, 202 } 203 keys = isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 204 205 # Check logs. 206 tag = keys[2].key.tag 207 msg = f"keymgr-manual-mode: block transition ZSK {zone}/ECDSAP256SHA256/{tag} type ZRRSIG state HIDDEN to state RUMOURED" 208 assert msg in ns3.log 209 210 # Force step. 211 with ns3.watch_log_from_here() as watcher: 212 ns3.rndc(f"dnssec -step {zone}") 213 watcher.wait_for_line( 214 f"zone {zone}/IN (signed): zone_rekey done: key {tag}/ECDSAP256SHA256" 215 ) 216 217 # Check logs. 218 tag = keys[1].key.tag 219 msg = f"keymgr-manual-mode: block transition ZSK {zone}/ECDSAP256SHA256/{tag} type ZRRSIG state OMNIPRESENT to state UNRETENTIVE" 220 if msg in ns3.log: 221 # Force step. 222 isctest.log.debug( 223 f"keymgr-manual-mode blocking transition ZSK {zone}/ECDSAP256SHA256/{tag} type ZRRSIG state OMNIPRESENT to state UNRETENTIVE, step again" 224 ) 225 with ns3.watch_log_from_here() as watcher: 226 ns3.rndc(f"dnssec -step {zone}") 227 watcher.wait_for_line( 228 f"zone {zone}/IN (signed): zone_rekey done: key {tag}/ECDSAP256SHA256" 229 ) 230 231 step = { 232 # predecessor zsk is no longer actively signing. successor zsk is 233 # now actively signing. 234 # zsk1 zrrsig: omnipresent -> unretentive 235 # zsk2 dnskey: rumoured -> omnipresent 236 # zsk2 zrrsig: hidden -> rumoured 237 "zone": zone, 238 "keyprops": [ 239 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step3-p']}", 240 f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:unretentive offset:{OFFSETS['step3-p']}", 241 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{OFFSETS['step3-s']}", 242 ], 243 "keyrelationships": [1, 2], 244 # next key event is when all the rrsig records have been replaced 245 # with signatures of the new zsk, in other words when zrrsig 246 # becomes omnipresent. 247 "nextev": IRET, 248 # set 'smooth' to true so expected signatures of subdomain are 249 # from the predecessor zsk. 250 "smooth": True, 251 } 252 isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 253 254 # Force full resign and check all signatures have been replaced. 255 with ns3.watch_log_from_here() as watcher: 256 ns3.rndc(f"sign {zone}") 257 watcher.wait_for_line(f"zone {zone}/IN (signed): sending notifies") 258 259 step["smooth"] = False 260 step["nextev"] = Iret(CONFIG, smooth=False) 261 isctest.kasp.check_rollover_step(ns3, CONFIG, POLICY, step) 262 263 264 @pytest.mark.parametrize( 265 "tld", 266 [ 267 param("autosign"), 268 param("manual"), 269 ], 270 ) 271 def test_zsk_prepub_step4(tld, alg, size, ns3): 272 zone = f"step4.zsk-prepub.{tld}" 273 policy = f"{POLICY}-{tld}" 274 275 isctest.kasp.wait_keymgr_done(ns3, zone) 276 277 if tld == "manual": 278 # Same as step 3, but zone signatures have become HIDDEN/OMNIPRESENT. 279 step = { 280 "zone": zone, 281 "keyprops": [ 282 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-p']}", 283 f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:hidden offset:{OFFSETS['step4-p']}", 284 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-s']}", 285 ], 286 "keyrelationships": [1, 2], 287 "manual-mode": True, 288 "nextev": None, 289 } 290 keys = isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 291 292 # Check logs. 293 tag = keys[1].key.tag 294 msg = f"keymgr-manual-mode: block transition ZSK {zone}/ECDSAP256SHA256/{tag} type DNSKEY state OMNIPRESENT to state UNRETENTIVE" 295 assert msg in ns3.log 296 297 # Force step. 298 tag = keys[2].key.tag 299 with ns3.watch_log_from_here() as watcher: 300 ns3.rndc(f"dnssec -step {zone}") 301 watcher.wait_for_line( 302 f"zone {zone}/IN (signed): zone_rekey done: key {tag}/ECDSAP256SHA256" 303 ) 304 305 step = { 306 # predecessor zsk is no longer needed. all rrsets are signed with 307 # the successor zsk. 308 # zsk1 dnskey: omnipresent -> unretentive 309 # zsk1 zrrsig: unretentive -> hidden 310 # zsk2 zrrsig: rumoured -> omnipresent 311 "zone": zone, 312 "keyprops": [ 313 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-p']}", 314 f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive zrrsig:hidden offset:{OFFSETS['step4-p']}", 315 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-s']}", 316 ], 317 "keyrelationships": [1, 2], 318 # next key event is when the dnskey enters the hidden state. 319 # this is the dnskey ttl plus zone propagation delay. 320 "nextev": KEYTTLPROP, 321 } 322 isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 323 324 325 @pytest.mark.parametrize( 326 "tld", 327 [ 328 param("autosign"), 329 param("manual"), 330 ], 331 ) 332 def test_zsk_prepub_step5(tld, alg, size, ns3): 333 zone = f"step5.zsk-prepub.{tld}" 334 policy = f"{POLICY}-{tld}" 335 336 isctest.kasp.wait_keymgr_done(ns3, zone) 337 338 # manual-mode: Nothing changing in the zone, no 'dnssec -step' required. 339 340 step = { 341 # predecessor zsk is now removed. 342 # zsk1 dnskey: unretentive -> hidden 343 "zone": zone, 344 "keyprops": [ 345 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-p']}", 346 f"zsk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden zrrsig:hidden offset:{OFFSETS['step5-p']}", 347 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step5-s']}", 348 ], 349 "keyrelationships": [1, 2], 350 # next key event is when the new successor needs to be published. 351 # this is the zsk lifetime minus IRET minus IPUB minus time 352 # elapsed. 353 "nextev": ZSK_LIFETIME - IRET - IPUB - KEYTTLPROP, 354 } 355 isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 356 357 358 @pytest.mark.parametrize( 359 "tld", 360 [ 361 param("autosign"), 362 param("manual"), 363 ], 364 ) 365 def test_zsk_prepub_step6(tld, alg, size, ns3): 366 zone = f"step6.zsk-prepub.{tld}" 367 policy = f"{POLICY}-{tld}" 368 369 isctest.kasp.wait_keymgr_done(ns3, zone) 370 371 # manual-mode: Nothing changing in the zone, no 'dnssec -step' required. 372 373 step = { 374 # predecessor zsk is now purged. 375 "zone": zone, 376 "keyprops": [ 377 f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-p']}", 378 f"zsk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step6-s']}", 379 ], 380 "nextev": None, 381 } 382 isctest.kasp.check_rollover_step(ns3, CONFIG, policy, step) 383