diff --git a/bin/tests/system/Makefile.am b/bin/tests/system/Makefile.am index 3cc3db366a..5e94e8fb9e 100644 --- a/bin/tests/system/Makefile.am +++ b/bin/tests/system/Makefile.am @@ -140,6 +140,7 @@ TESTS = \ redirect \ resolver \ rndc \ + rollover \ rootkeysentinel \ rpzextra \ rrchecker \ diff --git a/bin/tests/system/kasp/ns3/named-fips.conf.in b/bin/tests/system/kasp/ns3/named-fips.conf.in index 33cfaa9a13..92e1536884 100644 --- a/bin/tests/system/kasp/ns3/named-fips.conf.in +++ b/bin/tests/system/kasp/ns3/named-fips.conf.in @@ -89,13 +89,6 @@ zone "unlimited.kasp" { dnssec-policy "unlimited"; }; -/* Manual rollover. */ -zone "manual-rollover.kasp" { - type primary; - file "manual-rollover.kasp.db"; - dnssec-policy "manual-rollover"; -}; - /* A zone that inherits dnssec-policy. */ zone "inherit.kasp" { type primary; diff --git a/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in b/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in index 93ca7293cb..2649b056d8 100644 --- a/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in +++ b/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in @@ -23,15 +23,6 @@ dnssec-policy "default-dynamic" { inline-signing no; }; -dnssec-policy "manual-rollover" { - dnskey-ttl 3600; - - keys { - ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; - zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; - }; -}; - dnssec-policy "multisigner-model2" { dnskey-ttl 3600; inline-signing no; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index 731ba06524..5432bfecc8 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -50,7 +50,7 @@ for zn in default dnssec-keygen some-keys legacy-keys pregenerated \ rumoured rsasha256 rsasha512 ecdsa256 ecdsa384 \ dynamic dynamic-inline-signing inline-signing \ checkds-ksk checkds-doubleksk checkds-csk inherit unlimited \ - manual-rollover multisigner-model2 keystore; do + multisigner-model2 keystore; do setup "${zn}.kasp" cp template.db.in "$zonefile" done @@ -160,21 +160,6 @@ $SETTIME -s -g $O -k $R $Tpub -z $R $Tpub "$ZSK2" >settime.out.$zone.2 2>&1 # Set up zones that are already signed. # -# Zone to test manual rollover. -setup manual-rollover.kasp -T="now-1d" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) -$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 -$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 -cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" -private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile" -private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile" -cp $infile $zonefile -$SIGNER -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1 - # We are signing the raw version of the zone here. This is unusual and not # common operation, but want to make sure that in such a case BIND 9 does not # schedule a resigning operation on the raw version. Add expired signatures so diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index c1b33ddb08..47af94bad1 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -369,128 +369,6 @@ test $(key_get KEY4 RID) -ge 0 -a $(key_get KEY4 RID) -le 32767 || ret=1 test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) -# -# Testing manual rollover. -# -set_zone "manual-rollover.kasp" -set_policy "manual-rollover" "2" "3600" -set_server "ns3" "10.53.0.3" -key_clear "KEY1" -key_clear "KEY2" -key_clear "KEY3" -key_clear "KEY4" -# Key properties. -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "0" -set_keyalgorithm "KEY1" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" - -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "0" -set_keyalgorithm "KEY2" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" -# During set up everything was set to OMNIPRESENT. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY1" "STATE_KRRSIG" "omnipresent" -set_keystate "KEY1" "STATE_DS" "omnipresent" - -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - -# The first keys were published and activated a day ago. -created=$(key_get KEY1 CREATED) -set_addkeytime "KEY1" "PUBLISHED" "${created}" -86400 -set_addkeytime "KEY1" "SYNCPUBLISH" "${created}" -86400 -set_addkeytime "KEY1" "ACTIVE" "${created}" -86400 -created=$(key_get KEY2 CREATED) -set_addkeytime "KEY2" "PUBLISHED" "${created}" -86400 -set_addkeytime "KEY2" "ACTIVE" "${created}" -86400 -# Key lifetimes are unlimited, so not setting RETIRED and REMOVED. -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Schedule KSK rollover in six months (15552000 seconds). -active=$(key_get KEY1 ACTIVE) -set_addkeytime "KEY1" "RETIRED" "${active}" 15552000 -retired=$(key_get KEY1 RETIRED) -rndc_rollover "$SERVER" "$DIR" $(key_get KEY1 ID) "${retired}" "$ZONE" -set_addkeytime "KEY1" "RETIRED" "${active}" 15559500 -retired=$(key_get KEY1 RETIRED) -# Retire interval of this policy is 26h (93600 seconds). -set_addkeytime "KEY1" "REMOVED" "${retired}" 93600 - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Schedule KSK rollover now. -set_policy "manual-rollover" "3" "3600" -set_keystate "KEY1" "GOAL" "hidden" -created=$(key_get KEY1 CREATED) -set_keytime "KEY1" "RETIRED" "${created}" -rndc_rollover "$SERVER" "$DIR" $(key_get KEY1 ID) "${created}" "$ZONE" -# New key is introduced. -set_keyrole "KEY3" "ksk" -set_keylifetime "KEY3" "0" -set_keyalgorithm "KEY3" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY3" "yes" -set_zonesigning "KEY3" "no" - -set_keystate "KEY3" "GOAL" "omnipresent" -set_keystate "KEY3" "STATE_DNSKEY" "rumoured" -set_keystate "KEY3" "STATE_KRRSIG" "rumoured" -set_keystate "KEY3" "STATE_DS" "hidden" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -check_apex -check_subdomain -dnssec_verify - -# Schedule ZSK rollover now. -set_policy "manual-rollover" "4" "3600" -set_keystate "KEY2" "GOAL" "hidden" -created=$(key_get KEY2 CREATED) -set_keytime "KEY2" "RETIRED" "${created}" -rndc_rollover "$SERVER" "$DIR" $(key_get KEY2 ID) "${created}" "$ZONE" -# New key is introduced. -set_keyrole "KEY4" "zsk" -set_keylifetime "KEY4" "0" -set_keyalgorithm "KEY4" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY4" "no" -set_zonesigning "KEY4" "no" # not yet, first prepublish DNSKEY. - -set_keystate "KEY4" "GOAL" "omnipresent" -set_keystate "KEY4" "STATE_DNSKEY" "rumoured" -set_keystate "KEY4" "STATE_ZRRSIG" "hidden" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -check_apex -check_subdomain -dnssec_verify - -# Try to schedule a ZSK rollover for an inactive key (should fail). -n=$((n + 1)) -echo_i "check that rndc dnssec -rollover fails if key is inactive ($n)" -ret=0 -rndccmd "$SERVER" dnssec -rollover -key $(key_get KEY4 ID) "$ZONE" >rndc.dnssec.rollover.out.$ZONE.$n || ret=1 -grep "key is not actively signing" rndc.dnssec.rollover.out.$ZONE.$n >/dev/null || log_error "bad error message" -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - # # Testing DNSSEC introduction. # diff --git a/bin/tests/system/rollover/ns3/kasp.conf.j2 b/bin/tests/system/rollover/ns3/kasp.conf.j2 new file mode 100644 index 0000000000..bbecf098a8 --- /dev/null +++ b/bin/tests/system/rollover/ns3/kasp.conf.j2 @@ -0,0 +1,21 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dnssec-policy "manual-rollover" { + dnskey-ttl 3600; + + keys { + ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + }; +}; diff --git a/bin/tests/system/rollover/ns3/named.conf.j2 b/bin/tests/system/rollover/ns3/named.conf.j2 new file mode 100644 index 0000000000..b11a77fc06 --- /dev/null +++ b/bin/tests/system/rollover/ns3/named.conf.j2 @@ -0,0 +1,50 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS3 + +include "kasp.conf"; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint.blackhole"; +}; + +/* Manual rollover. */ +zone "manual-rollover.kasp" { + type primary; + file "manual-rollover.kasp.db"; + dnssec-policy "manual-rollover"; +}; diff --git a/bin/tests/system/rollover/ns3/setup.sh b/bin/tests/system/rollover/ns3/setup.sh new file mode 100644 index 0000000000..59fd259aed --- /dev/null +++ b/bin/tests/system/rollover/ns3/setup.sh @@ -0,0 +1,54 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +echo_i "ns3/setup.sh" + +setup() { + zone="$1" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + infile="${zone}.db.infile" + echo "$zone" >>zones +} + +# Set in the key state files the Predecessor/Successor fields. +# Key $1 is the predecessor of key $2. +key_successor() { + id1=$(keyfile_to_key_id "$1") + id2=$(keyfile_to_key_id "$2") + echo "Predecessor: ${id1}" >>"${2}.state" + echo "Successor: ${id2}" >>"${1}.state" +} + +# Make lines shorter by storing key states in environment variables. +H="HIDDEN" +R="RUMOURED" +O="OMNIPRESENT" +U="UNRETENTIVE" + +# Zone to test manual rollover. +setup manual-rollover.kasp +T="now-7d" +keytimes="-P $T -A $T" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $keytimes $zone 2>keygen.out.$zone.2) +$SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile" +cp $infile $zonefile +$SIGNER -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/rollover/ns3/template.db.in b/bin/tests/system/rollover/ns3/template.db.in new file mode 100644 index 0000000000..010b05b3cb --- /dev/null +++ b/bin/tests/system/rollover/ns3/template.db.in @@ -0,0 +1,27 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 + diff --git a/bin/tests/system/rollover/setup.sh b/bin/tests/system/rollover/setup.sh new file mode 100644 index 0000000000..c72052cf48 --- /dev/null +++ b/bin/tests/system/rollover/setup.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../conf.sh + +set -e + +( + cd ns3 + $SHELL setup.sh +) diff --git a/bin/tests/system/rollover/tests_rollover.py b/bin/tests/system/rollover/tests_rollover.py new file mode 100644 index 0000000000..d85ff71c26 --- /dev/null +++ b/bin/tests/system/rollover/tests_rollover.py @@ -0,0 +1,222 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import os + +from datetime import timedelta + +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import dns.update + +import isctest +from isctest.kasp import KeyTimingMetadata + +pytestmark = pytest.mark.extra_artifacts( + [ + "*.axfr*", + "dig.out*", + "ns*/*.db", + "ns*/*.db.infile", + "ns*/*.db.jbk", + "ns*/*.db.signed", + "ns*/*.db.signed.jnl", + "ns*/*.conf", + "ns*/dsset-*", + "ns*/K*.key", + "ns*/K*.private", + "ns*/K*.state", + "ns*/keygen.out.*", + "ns*/settime.out.*", + "ns*/signer.out.*", + "ns*/zones", + ] +) + + +def Ipub(config): + return ( + config["dnskey-ttl"] + + config["zone-propagation-delay"] + + config["publish-safety"] + ) + + +def IpubC(config, rollover=True): + if rollover: + ttl = config["dnskey-ttl"] + safety_interval = config["publish-safety"] + else: + ttl = config["max-zone-ttl"] + safety_interval = timedelta(0) + + return ttl + config["zone-propagation-delay"] + safety_interval + + +def Iret(config, zsk=True, ksk=False, rollover=True): + sign_delay = timedelta(0) + safety_interval = timedelta(0) + if rollover: + sign_delay = config["signatures-validity"] - config["signatures-refresh"] + safety_interval = config["retire-safety"] + + iretKSK = timedelta(0) + if ksk: + # KSK: Double-KSK Method: Iret = DprpP + TTLds + iretKSK = ( + config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval + ) + + iretZSK = timedelta(0) + if zsk: + # ZSK: Pre-Publication Method: Iret = Dsgn + Dprp + TTLsig + iretZSK = ( + sign_delay + + config["zone-propagation-delay"] + + config["max-zone-ttl"] + + safety_interval + ) + + return max(iretKSK, iretZSK) + + +def test_rollover_manual(servers): + server = servers["ns3"] + policy = "manual-rollover" + config = { + "dnskey-ttl": timedelta(hours=1), + "ds-ttl": timedelta(days=1), + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=7), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), + } + ttl = int(config["dnskey-ttl"].total_seconds()) + alg = os.environ["DEFAULT_ALGORITHM_NUMBER"] + size = os.environ["DEFAULT_BITS"] + + zone = "manual-rollover.kasp" + key_properties = [ + f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + expected = isctest.kasp.policy_to_properties(ttl, key_properties) + keys = isctest.kasp.keydir_to_keylist(zone, server.identifier) + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] + + isctest.kasp.check_zone_is_signed(server, zone) + isctest.kasp.check_keys(zone, keys, expected) + + offset = -timedelta(days=7) + for kp in expected: + kp.set_expected_keytimes(config, offset=offset) + + isctest.kasp.check_keytimes(keys, expected) + isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy) + isctest.kasp.check_apex(server, zone, ksks, zsks) + isctest.kasp.check_subdomain(server, zone, ksks, zsks) + isctest.kasp.check_dnssec_verify(server, zone) + + # Schedule KSK rollover in six months. + assert len(ksks) == 1 + ksk = ksks[0] + startroll = expected[0].timing["Active"] + timedelta(days=30 * 6) + expected[0].timing["Retired"] = startroll + Ipub(config) + expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret( + config, zsk=False, ksk=True + ) + + with server.watch_log_from_here() as watcher: + server.rndc(f"dnssec -rollover -key {ksk.tag} -when {startroll} {zone}") + watcher.wait_for_line(f"keymgr: {zone} done") + + isctest.kasp.check_keys(zone, keys, expected) + isctest.kasp.check_keytimes(keys, expected) + isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy) + isctest.kasp.check_apex(server, zone, ksks, zsks) + isctest.kasp.check_subdomain(server, zone, ksks, zsks) + isctest.kasp.check_dnssec_verify(server, zone) + + # Schedule KSK rollover now. + now = KeyTimingMetadata.now() + with server.watch_log_from_here() as watcher: + server.rndc(f"dnssec -rollover -key {ksk.tag} -when {now} {zone}") + watcher.wait_for_line(f"keymgr: {zone} done") + + key_properties = [ + f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + expected = isctest.kasp.policy_to_properties(ttl, key_properties) + keys = isctest.kasp.keydir_to_keylist(zone, server.identifier) + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] + + isctest.kasp.check_keys(zone, keys, expected) + + expected[0].metadata["Successor"] = expected[1].key.tag + expected[1].metadata["Predecessor"] = expected[0].key.tag + isctest.kasp.check_keyrelationships(keys, expected) + + for kp in expected: + off = offset + if "Predecessor" in kp.metadata: + off = 0 + kp.set_expected_keytimes(config, offset=off) + + expected[0].timing["Retired"] = now + Ipub(config) + expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret( + config, zsk=False, ksk=True + ) + + isctest.kasp.check_keytimes(keys, expected) + isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy) + isctest.kasp.check_apex(server, zone, ksks, zsks) + isctest.kasp.check_subdomain(server, zone, ksks, zsks) + isctest.kasp.check_dnssec_verify(server, zone) + + # Schedule ZSK rollover now. + assert len(zsks) == 1 + zsk = zsks[0] + now = KeyTimingMetadata.now() + with server.watch_log_from_here() as watcher: + server.rndc(f"dnssec -rollover -key {zsk.tag} -when {now} {zone}") + watcher.wait_for_line(f"keymgr: {zone} done") + + key_properties = [ + f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden", + ] + expected = isctest.kasp.policy_to_properties(ttl, key_properties) + keys = isctest.kasp.keydir_to_keylist(zone, server.identifier) + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] + + isctest.kasp.check_keys(zone, keys, expected) + + expected[0].metadata["Successor"] = expected[1].key.tag + expected[1].metadata["Predecessor"] = expected[0].key.tag + expected[2].metadata["Successor"] = expected[3].key.tag + expected[3].metadata["Predecessor"] = expected[2].key.tag + isctest.kasp.check_keyrelationships(keys, expected) + + # Try to schedule a ZSK rollover for an inactive key (should fail). + zsk = expected[3].key + response = server.rndc(f"dnssec -rollover -key {zsk.tag} {zone}") + assert "key is not actively signing" in response