From: Matthijs Mekking Date: Fri, 10 Oct 2025 08:57:50 +0000 (+0200) Subject: Convert model2.multisigner test to pytest X-Git-Tag: v9.21.16~12^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=773ce8d99b4c5074e0368e0c1ef2af250b043255;p=thirdparty%2Fbind9.git Convert model2.multisigner test to pytest This converts the model2.multisigner tests from the multisigner system test to pytest based code. Crappy shell test functions such as 'zsks_are_published', 'records_published' and others are replaced with the standard test code from isctest.kasp and by setting 'private=False' and 'legacy=True' on the keys from the other providers so we don't do any key file testing. --- diff --git a/bin/tests/system/multisigner/ns3/setup.sh b/bin/tests/system/multisigner/ns3/setup.sh index 123528f35c8..80327a1300c 100644 --- a/bin/tests/system/multisigner/ns3/setup.sh +++ b/bin/tests/system/multisigner/ns3/setup.sh @@ -28,8 +28,6 @@ KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out. ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 -# ZSK will be added to the other provider with nsupdate. -cat "${ZSK}.key" | grep -v ";.*" >"${zone}.zsk" zone="model2.secondary" echo_i "setting up zone: $zone" diff --git a/bin/tests/system/multisigner/ns4/setup.sh b/bin/tests/system/multisigner/ns4/setup.sh index dc3fc7cebd1..c58ddce4954 100644 --- a/bin/tests/system/multisigner/ns4/setup.sh +++ b/bin/tests/system/multisigner/ns4/setup.sh @@ -28,8 +28,6 @@ KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out. ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 -# ZSK will be added to the other provider with nsupdate. -cat "${ZSK}.key" | grep -v ";.*" >"${zone}.zsk" zone="model2.secondary" echo_i "setting up zone: $zone" diff --git a/bin/tests/system/multisigner/tests.sh b/bin/tests/system/multisigner/tests.sh index abe19ff2154..c0141e8d853 100644 --- a/bin/tests/system/multisigner/tests.sh +++ b/bin/tests/system/multisigner/tests.sh @@ -26,82 +26,6 @@ start_time="$(TZ=UTC date +%s)" status=0 n=0 -set_zone "model2.multisigner" -set_policy "model2" "2" "3600" - -# Key properties and states. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "0" -set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY1" "STATE_KRRSIG" "omnipresent" -set_keystate "KEY1" "STATE_DS" "omnipresent" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "0" -set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent" - -key_clear "KEY3" -key_clear "KEY4" - -set_keytimes_model2() { - # The first KSK is immediately published and activated. - created=$(key_get KEY1 CREATED) - set_keytime "KEY1" "PUBLISHED" "${created}" - set_keytime "KEY1" "ACTIVE" "${created}" - set_keytime "KEY1" "SYNCPUBLISH" "${created}" - - # The first ZSKs are immediately published and activated. - created=$(key_get KEY2 CREATED) - set_keytime "KEY2" "PUBLISHED" "${created}" - set_keytime "KEY2" "ACTIVE" "${created}" -} - -set_server "ns3" "10.53.0.3" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_model2 -check_keytimes -check_apex -dnssec_verify - -set_server "ns4" "10.53.0.4" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_model2 -check_keytimes -check_apex -dnssec_verify - -# -# Update DNSKEY RRset. -# - -# Check that the ZSKs from the other provider are published. -zsks_are_published() { - dig_with_opts "$ZONE" "@${SERVER}" DNSKEY >"dig.out.$DIR.test$n" || return 1 - cat dig.out.$DIR.test$n | tr [:blank:] ' ' >dig.out.$DIR.test$n.tr || return 1 - # We should have two ZSKs. - lines=$(grep "256 3 13" dig.out.$DIR.test$n.tr | wc -l) - test "$lines" -eq 2 || return 1 - # Both ZSKs are published. - grep "$(cat ns3/${ZONE}.zsk | tr [:blank:] ' ')" dig.out.$DIR.test$n.tr >/dev/null || return 1 - grep "$(cat ns4/${ZONE}.zsk | tr [:blank:] ' ')" dig.out.$DIR.test$n.tr >/dev/null || return 1 - # And one KSK. - lines=$(grep "257 3 13" dig.out.$DIR.test$n.tr | wc -l) - test "$lines" -eq 1 || return 1 -} - # Test to make sure no DNSSEC records end up in the raw journal. no_dnssec_in_journal() { n=$((n + 1)) @@ -124,150 +48,6 @@ rrset_exists() ( test "$lines" -gt 0 ) -n=$((n + 1)) -echo_i "add dnskey record: update zone ${ZONE} at ns3 with ZSK from provider ns4 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the new DNSKEY RRset. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the logs for find zone keys errors. -n=$((n + 1)) -echo_i "make sure we did not try to sign with the keys added with nsupdate for zone ${ZONE} ($n)" -ret=0 -grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Verify again. -dnssec_verify - -n=$((n + 1)) -echo_i "add dnskey record: - update zone ${ZONE} at ns4 with ZSK from provider ns3 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "ns3/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the new DNSKEY RRset. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the logs for find zone keys errors. -n=$((n + 1)) -echo_i "make sure we did not try to sign with the keys added with nsupdate for zone ${ZONE} ($n)" -ret=0 -grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Verify again. -dnssec_verify -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove dnskey record: - try to remove ns3 ZSK from provider ns3 (should fail) ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns3/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Both ZSKs should still be published. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after failed update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove dnskey record: remove ns4 ZSK from provider ns3 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# We should have only the KSK and ZSK from provider ns3. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -check_keys -check_apex -dnssec_verify - -n=$((n + 1)) -echo_i "remove dnskey record: try to remove ns4 ZSK from provider ns4 (should fail) ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Both ZSKs should still be published. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after failed update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove dnskey record: remove ns3 ZSK from provider ns4 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns3/${ZONE}.zsk") - echo send -) | $NSUPDATE -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# We should have only the KSK and ZSK from provider ns4. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -check_keys -check_apex -dnssec_verify -no_dnssec_in_journal - -# -# Update CDNSKEY RRset. -# - # Check that the CDNSKEY from both providers are published. records_published() { _rrtype=$1 @@ -278,201 +58,6 @@ records_published() { test "$lines" -eq "$_expect" || return 1 } -# Retrieve CDNSKEY records from the other provider. -dig_with_opts ${ZONE} @10.53.0.3 CDNSKEY >dig.out.ns3.cdnskey -awk '$4 == "CDNSKEY" {print}' dig.out.ns3.cdnskey >cdnskey.ns3 -dig_with_opts ${ZONE} @10.53.0.4 CDNSKEY >dig.out.ns4.cdnskey -awk '$4 == "CDNSKEY" {print}' dig.out.ns4.cdnskey >cdnskey.ns4 - -n=$((n + 1)) -echo_i "add cdnskey record: update zone ${ZONE} at ns3 with CDNSKEY from provider ns4 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -# Initially there should be one CDNSKEY. -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cdnskey.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDNSKEY records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "add cdnskey record: update zone ${ZONE} at ns4 with CDNSKEY from provider ns3 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -# Initially there should be one CDNSKEY. -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cdnskey.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDNSKEY records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove cdnskey record: remove ns4 CDNSKEY from provider ns3 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cdnskey.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDNSKEY record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove cdnskey record: remove ns3 CDNSKEY from provider ns4 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cdnskey.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDNSKEY record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)"ret=0 -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -# -# Update CDS RRset. -# - -# Retrieve CDS records from the other provider. -dig_with_opts ${ZONE} @10.53.0.3 CDS >dig.out.ns3.cds -awk '$4 == "CDS" {print}' dig.out.ns3.cds >cds.ns3 -dig_with_opts ${ZONE} @10.53.0.4 CDS >dig.out.ns4.cds -awk '$4 == "CDS" {print}' dig.out.ns4.cds >cds.ns4 - -n=$((n + 1)) -echo_i "add cds record: update zone ${ZONE} at ns3 with CDS from provider ns4 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -# Initially there should be one CDS. -retry_quiet 10 records_published CDS 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cds.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDS records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "add cds record: update zone ${ZONE} at ns4 with CDS from provider ns3 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -# Initially there should be one CDS. -retry_quiet 10 records_published CDS 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cds.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDS records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove cds record: remove ns4 CDS from provider ns3 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cds.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDS record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove cds record: remove ns3 CDS from provider ns4 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cds.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDS record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - # # Check secondary server behaviour. # diff --git a/bin/tests/system/multisigner/tests_multisigner.py b/bin/tests/system/multisigner/tests_multisigner.py new file mode 100644 index 00000000000..9638bc3e26f --- /dev/null +++ b/bin/tests/system/multisigner/tests_multisigner.py @@ -0,0 +1,454 @@ +# 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. + +from datetime import timedelta +import os +import re + +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import dns +import dns.update + +import isctest + +pytestmark = pytest.mark.extra_artifacts( + [ + "*.axfr", + "*.created", + "cdnskey.ns*", + "cds.ns*", + "dig.out.*", + "rndc.dnssec.status.out.*", + "secondary.cdnskey.ns*", + "secondary.cds.ns*", + "unused.*", + "verify.out.*", + "ns*/K*", + "ns*/db-*", + "ns*/keygen.out.*", + "ns*/*.jbk", + "ns*/*.jnl", + "ns*/*.zsk", + "ns*/*.signed", + "ns*/*.journal.out.*", + "ns*/settime.out.*", + "ns*/model2.secondary.db", + ] +) + +ALGORITHM = os.environ["DEFAULT_ALGORITHM_NUMBER"] +SIZE = os.environ["DEFAULT_BITS"] +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=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), +} +TTL = 3600 + + +def dsfromkey(key): + dsfromkey_command = [ + os.environ.get("DSFROMKEY"), + "-T", + str(TTL), + "-a", + "SHA-256", + "-C", + "-w", + str(key.keyfile), + ] + out = isctest.run.cmd(dsfromkey_command) + return out.stdout.decode("utf-8").split() + + +def check_dnssec(server, zone, keys, expected): + 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) + + for kp in expected: + kp.set_expected_keytimes(CONFIG) + kp.set_expected_keytimes(CONFIG) + start = kp.key.get_timing("Created") + kp.timing["Published"] = start + kp.timing["Active"] = start + if kp.role != "zsk": + kp.timing["PublishCDS"] = start + + isctest.kasp.check_dnssec_verify(server, zone) + isctest.kasp.check_apex(server, zone, ksks, zsks) + + +def check_no_dnssec_in_journal(server, zone): + journalprint = [ + os.environ.get("JOURNALPRINT"), + f"{server.identifier}/{zone}.db.jnl", + ] + + out = isctest.run.cmd(journalprint) + contents = out.stdout.decode("utf-8") + pattern = re.compile( + r"^\s*(?:\S+\s+){4}(NSEC|NSEC3|NSEC3PARAM|RRSIG)", flags=re.MULTILINE + ) + match = pattern.search(contents) + assert not match, f"{match.group(1)} record found in journal" + + +def check_add_zsk(server, zone, keys, expected, zsk, extra): + isctest.log.info("add dnskey record:") + + isctest.log.info( + f"- zone {zone} {server.identifier}: update zone with ZSK from other provider" + ) + + dnskey = zsk.dnskey().split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.add(f"{zone}.", TTL, "DNSKEY", rdata) + server.nsupdate(update_msg) + + # Check the new DNSKEY RRset. + isctest.log.info( + f"- zone {zone} {server.identifier}: check DNSKEY RRset after update add" + ) + check_dnssec(server, zone, keys + [zsk], expected + extra) + + # Check the logs for find zone keys errors. + isctest.log.info( + f"- zone {zone} {server.identifier}: make sure we did not try to sign with the keys added with nsupdate" + ) + server.log.prohibit(f"dns_zone_findkeys: error reading ./K{zone}") + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + [zsk], expected + extra) + server.log.prohibit(f"dns_zone_findkeys: error reading ./K{zone}") + + +def check_remove_zsk(server, zone, keys, expected, zsk, extra): + isctest.log.info("remove dnskey record:") + + isctest.log.info( + f"- zone {zone} {server.identifier}: try to remove own ZSK (should fail)" + ) + + zsks = [k for k in keys if not k.is_ksk()] + dnskey = zsks[0].dnskey().split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "DNSKEY", rdata) + with server.watch_log_from_here() as watcher: + server.nsupdate(update_msg) + watcher.wait_for_line( + f"updating zone '{zone}/IN': attempt to delete in use DNSKEY ignored" + ) + + # Both ZSKs should still be published. + isctest.log.info( + f"- zone {zone} {server.identifier}: check DNSKEY RRset after update remove" + ) + check_dnssec(server, zone, keys + [zsk], expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + [zsk], expected + extra) + + # Remove actual ZSK. + isctest.log.info( + f"- zone {zone} {server.identifier}: remove ZSK from other provider" + ) + + dnskey = zsk.dnskey().split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "DNSKEY", rdata) + server.nsupdate(update_msg) + + # We should have only the KSK and ZSK from server. + isctest.log.info( + f"- zone {zone} {server.identifier}: check DNSKEY RRset after update remove" + ) + check_dnssec(server, zone, keys, expected) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys, expected) + + +def check_add_cdnskey(server, zone, keys, expected, ksk, extra): + isctest.log.info("add cdnskey record:") + + isctest.log.info( + f"- zone {zone} {server.identifier}: update zone with CDNSKEY from other provider" + ) + + # Retrieve CDNSKEY records from the other provider. + dnskey = ksk.dnskey().split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.add(f"{zone}.", TTL, "CDNSKEY", rdata) + server.nsupdate(update_msg) + + # Now there should be two CDNSKEY records. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDNSKEY RRset after update add" + ) + check_dnssec(server, zone, keys + [ksk], expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + [ksk], expected + extra) + + +def check_remove_cdnskey(server, zone, keys, expected, ksk, extra): + isctest.log.info("remove cdnskey record:") + + isctest.log.info( + f"- zone {zone} {server.identifier}: try to remove own CDNSKEY (should fail)" + ) + + ksks = [k for k in keys if not k.is_ksk()] + dnskey = ksks[0].dnskey().split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDNSKEY", rdata) + with server.watch_log_from_here() as watcher: + server.nsupdate(update_msg) + watcher.wait_for_line( + f"updating zone '{zone}/IN': attempt to delete in use CDNSKEY ignored" + ) + + # Both CDNSKEY records should still be published. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDNSKEY RRset after update remove" + ) + check_dnssec(server, zone, keys + [ksk], expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + [ksk], expected + extra) + + # Remove actual CDNSKEY. + isctest.log.info( + f"- zone {zone} {server.identifier}: remove CDNSKEY from other provider" + ) + + dnskey = ksk.dnskey().split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDNSKEY", rdata) + server.nsupdate(update_msg) + + # Now there should be one CDNSKEY record again. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDNSKEY RRset after update remove" + ) + check_dnssec(server, zone, keys, expected) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys, expected) + + +def check_add_cds(server, zone, keys, expected, ksk, extra): + isctest.log.info("add cds record:") + + isctest.log.info( + f"- zone {zone} {server.identifier}: update zone with CDS from other provider" + ) + + # Retrieve CDS records from the other provider. + ds = dsfromkey(ksk) + rdata = " ".join(ds[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.add(f"{zone}.", TTL, "CDS", rdata) + server.nsupdate(update_msg) + + # Now there should be two CDS records. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDS RRset after update add" + ) + check_dnssec(server, zone, keys + [ksk], expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + [ksk], expected + extra) + + +def check_remove_cds(server, zone, keys, expected, ksk, extra): + isctest.log.info("remove cds record:") + + isctest.log.info( + f"- zone {zone} {server.identifier}: try to remove own CDS (should fail)" + ) + + ksks = [k for k in keys if not k.is_ksk()] + ds = dsfromkey(ksks[0]) + rdata = " ".join(ds[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDS", rdata) + with server.watch_log_from_here() as watcher: + server.nsupdate(update_msg) + watcher.wait_for_line( + f"updating zone '{zone}/IN': attempt to delete in use CDS ignored" + ) + + # Both CDS records should still be published. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDS RRset after update remove" + ) + check_dnssec(server, zone, keys + [ksk], expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + [ksk], expected + extra) + + # Remove actual CDS. + isctest.log.info( + f"- zone {zone} {server.identifier}: remove CDS from other provider" + ) + + ds = dsfromkey(ksk) + rdata = " ".join(ds[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDS", rdata) + server.nsupdate(update_msg) + + # Now there should be one CDS record again. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDS RRset after update remove" + ) + check_dnssec(server, zone, keys, expected) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys, expected) + + +def test_multisigner(ns3, ns4): + zone = "model2.multisigner" + keyprops = [ + f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + + # First make sure the zone is properly signed. + isctest.log.info(f"basic DNSSEC tests for {zone}") + isctest.kasp.wait_keymgr_done(ns3, zone) + isctest.kasp.wait_keymgr_done(ns4, zone) + + keys3 = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) + ksks3 = [k for k in keys3 if k.is_ksk()] + zsks3 = [k for k in keys3 if not k.is_ksk()] + expected3 = isctest.kasp.policy_to_properties(ttl=TTL, keys=keyprops) + + check_dnssec(ns3, zone, keys3, expected3) + + keys4 = isctest.kasp.keydir_to_keylist(zone, ns4.identifier) + ksks4 = [k for k in keys4 if k.is_ksk()] + zsks4 = [k for k in keys4 if not k.is_ksk()] + expected4 = isctest.kasp.policy_to_properties(ttl=TTL, keys=keyprops) + + check_dnssec(ns4, zone, keys4, expected4) + + # Add DNSKEY to RRset. + newprops = [f"zsk unlimited {ALGORITHM} {SIZE}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False # noqa + extra[0].legacy = True # noqa + + check_add_zsk(ns3, zone, keys3, expected3, zsks4[0], extra) + check_add_zsk(ns4, zone, keys4, expected4, zsks3[0], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Remove DNSKEY from RRset. + check_remove_zsk(ns3, zone, keys3, expected3, zsks4[0], extra) + check_remove_zsk(ns4, zone, keys4, expected4, zsks3[0], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Add CDNSKEY RRset. + newprops = [f"ksk unlimited {ALGORITHM} {SIZE}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False # noqa + extra[0].legacy = True # noqa + + check_add_cdnskey(ns3, zone, keys3, expected3, ksks4[0], extra) + check_add_cdnskey(ns4, zone, keys4, expected4, ksks3[0], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Remove CDNSKEY RRset. + check_remove_cdnskey(ns3, zone, keys3, expected3, ksks4[0], extra) + check_remove_cdnskey(ns4, zone, keys4, expected4, ksks3[0], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Update CDS RRset. + check_add_cds(ns3, zone, keys3, expected3, ksks4[0], extra) + check_add_cds(ns4, zone, keys4, expected4, ksks3[0], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Remove CDS RRset. + check_remove_cds(ns3, zone, keys3, expected3, ksks4[0], extra) + check_remove_cds(ns4, zone, keys4, expected4, ksks3[0], extra) + check_no_dnssec_in_journal(ns4, zone)