diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index e4b3ead5cc..9e23b9a6ef 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -1020,6 +1020,35 @@ def check_subdomain(server, zone, ksks, zsks, tsig=None): check_signatures(rrsigs, qtype, fqdn, ksks, zsks) +def verify_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None): + """ + Test an RRset below the apex and verify it is updated and signed correctly. + """ + response = _query(server, qname, qtype, tsig=tsig) + + if response.rcode() != dns.rcode.NOERROR: + return False + + rrtype = dns.rdatatype.to_text(qtype) + match = f"{qname} {DEFAULT_TTL} IN {rrtype} {rdata}" + rrsigs = [] + for rrset in response.answer: + if rrset.match( + dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype + ): + rrsigs.append(rrset) + elif not match in rrset.to_text(): + return False + + if len(rrsigs) == 0: + return False + + # Zone is updated, ready to verify the signatures. + check_signatures(rrsigs, qtype, fqdn, ksks, zsks) + + return True + + def next_key_event_equals(server, zone, next_event): if next_event is None: # No next key event check. diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 86b0bfbcd7..305ae548ac 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -64,6 +64,7 @@ next_key_event_threshold=100 # infinite loops if there is an error. n=$((n + 1)) echo_i "waiting for kasp signing changes to take effect ($n)" +ret=0 _wait_for_done_apexnsec() { while read -r zone; do @@ -93,9 +94,6 @@ grep "loading from master file ${ZONE}.db failed: out of range" "ns3/named.run" test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) -# -# Zone: default.kasp. -# set_keytimes_csk_policy() { # The first key is immediately published and activated. created=$(key_get KEY1 CREATED) @@ -108,10 +106,6 @@ set_keytimes_csk_policy() { # Key lifetime is unlimited, so not setting RETIRED and REMOVED. } -# Check the zone with default kasp policy has loaded and is signed. -set_zone "default.kasp" -set_policy "default" "1" "3600" -set_server "ns3" "10.53.0.3" # Key properties. set_keyrole "KEY1" "csk" set_keylifetime "KEY1" "0" @@ -125,59 +119,6 @@ set_keystate "KEY1" "STATE_KRRSIG" "rumoured" set_keystate "KEY1" "STATE_ZRRSIG" "rumoured" set_keystate "KEY1" "STATE_DS" "hidden" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_csk_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Trigger a keymgr run. Make sure the key files are not touched if there are -# no modifications to the key metadata. -n=$((n + 1)) -echo_i "make sure key files are untouched if metadata does not change ($n)" -ret=0 -basefile=$(key_get KEY1 BASEFILE) -privkey_stat=$(key_get KEY1 PRIVKEY_STAT) -pubkey_stat=$(key_get KEY1 PUBKEY_STAT) -state_stat=$(key_get KEY1 STATE_STAT) - -nextpart $DIR/named.run >/dev/null -rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed" -wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1 -privkey_stat2=$(key_stat "${basefile}.private") -pubkey_stat2=$(key_stat "${basefile}.key") -state_stat2=$(key_stat "${basefile}.state") -test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)" -test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)" -test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)" -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "again ($n)" -ret=0 - -nextpart $DIR/named.run >/dev/null -rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed" -wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1 -privkey_stat2=$(key_stat "${basefile}.private") -pubkey_stat2=$(key_stat "${basefile}.key") -state_stat2=$(key_stat "${basefile}.state") -test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)" -test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)" -test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)" -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# Update zone. -n=$((n + 1)) -echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)" -ret=0 -cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db" -rndccmd 10.53.0.3 reload "$ZONE" >/dev/null || log_error "rndc reload zone ${ZONE} failed" - update_is_signed() { ip_a=$1 ip_d=$2 @@ -201,31 +142,6 @@ update_is_signed() { fi } -retry_quiet 10 update_is_signed "10.0.0.11" "10.0.0.44" || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# Move the private key file, a rekey event should not introduce replacement -# keys. -ret=0 -echo_i "test that if private key files are inaccessible this doesn't trigger a rollover ($n)" -basefile=$(key_get KEY1 BASEFILE) -mv "${basefile}.private" "${basefile}.offline" -rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed" -wait_for_log 3 "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" $DIR/named.run || ret=1 -mv "${basefile}.offline" "${basefile}.private" -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# Nothing has changed. -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_csk_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - # # A zone with special characters. # diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 55dbf12f89..ba9217b3ee 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -14,6 +14,7 @@ import shutil from datetime import timedelta +import dns import pytest import isctest @@ -29,6 +30,7 @@ pytestmark = pytest.mark.extra_artifacts( "K*.cmp", "K*.key", "K*.state", + "*.axfr", "*.created", "dig.out*", "keyevent.out.*", @@ -44,14 +46,18 @@ pytestmark = pytest.mark.extra_artifacts( "unused.key-*", "verify.out.*", "zone.out.*", - "ns*/K*.private", "ns*/K*.key", + "ns*/K*.offline", + "ns*/K*.private", "ns*/K*.state", "ns*/*.db", "ns*/*.db.infile", "ns*/*.db.signed", + "ns*/*.db.signed.tmp", "ns*/*.jbk", "ns*/*.jnl", + "ns*/*.zsk1", + "ns*/*.zsk2", "ns*/dsset-*", "ns*/keygen.out.*", "ns*/keys", @@ -65,14 +71,116 @@ pytestmark = pytest.mark.extra_artifacts( "ns*/signer.out.*", "ns*/zones", "ns*/policies/*.conf", - "ns*/*.zsk1", - "ns*/*.zsk2", "ns3/legacy-keys.*", "ns3/dynamic-signed-inline-signing.kasp.db.signed.signed", ] ) +def check_all(server, zone, policy, ksks, zsks, tsig=None): + isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy) + isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig) + isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig) + isctest.kasp.check_dnssec_verify(server, zone) + + +def set_keytimes_default_policy(kp): + # The first key is immediately published and activated. + kp.timing["Generated"] = kp.key.get_timing("Created") + kp.timing["Published"] = kp.timing["Generated"] + kp.timing["Active"] = kp.timing["Generated"] + # The DS can be published if the DNSKEY and RRSIG records are + # OMNIPRESENT. This happens after max-zone-ttl (1d) plus + # plus zone-propagation-delay (300s). + kp.timing["PublishCDS"] = kp.timing["Published"] + timedelta(days=1, seconds=300) + # Key lifetime is unlimited, so not setting 'Retired' nor 'Removed'. + kp.timing["DNSKEYChange"] = kp.timing["Published"] + kp.timing["DSChange"] = kp.timing["Published"] + kp.timing["KRRSIGChange"] = kp.timing["Active"] + kp.timing["ZRRSIGChange"] = kp.timing["Active"] + + +def test_kasp_default(servers): + server = servers["ns3"] + + # check the zone with default kasp policy has loaded and is signed. + isctest.log.info("check a zone with the default policy is signed") + zone = "default.kasp" + policy = "default" + + # Key properties. + # DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait. + keyprops = [ + "csk 0 13 256 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden", + ] + expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops) + keys = isctest.kasp.keydir_to_keylist(zone, "ns3") + isctest.kasp.check_zone_is_signed(server, zone) + isctest.kasp.check_keys(zone, keys, expected) + set_keytimes_default_policy(expected[0]) + isctest.kasp.check_keytimes(keys, expected) + check_all(server, zone, policy, keys, []) + + # Trigger a keymgr run. Make sure the key files are not touched if there + # are no modifications to the key metadata. + isctest.log.info( + "check that key files are untouched if there are no metadata changes" + ) + key = keys[0] + privkey_stat = os.stat(key.privatefile) + pubkey_stat = os.stat(key.keyfile) + state_stat = os.stat(key.statefile) + + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime + assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime + assert state_stat.st_mtime == os.stat(key.statefile).st_mtime + + # again + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime + assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime + assert state_stat.st_mtime == os.stat(key.statefile).st_mtime + + # modify unsigned zone file and check that new record is signed. + isctest.log.info("check that an updated zone signs the new record") + shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db") + server.rndc(f"reload {zone}", log=False) + + def update_is_signed(): + parts = update.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + rdata = parts[2] + return isctest.kasp.verify_update_is_signed( + server, zone, qname, qtype, rdata, keys, [] + ) + + expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"] + for update in expected_updates: + isctest.run.retry_with_timeout(update_is_signed, timeout=5) + + # Move the private key file, a rekey event should not introduce + # replacement keys. + isctest.log.info("check that missing private key doesn't trigger rollover") + shutil.move(f"{key.privatefile}", f"{key.path}.offline") + expectmsg = "zone_rekey:zone_verifykeys failed: some key files are missing" + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"zone {zone}/IN (signed): {expectmsg}") + # Nothing has changed. + expected[0].properties["private"] = False + isctest.kasp.check_keys(zone, keys, expected) + isctest.kasp.check_keytimes(keys, expected) + check_all(server, zone, policy, keys, []) + + def test_kasp_dnssec_keygen(): def keygen(zone, policy, keydir=None): if keydir is None: @@ -95,7 +203,7 @@ def test_kasp_dnssec_keygen(): lifetime = { "P1Y": int(timedelta(days=365).total_seconds()), "P30D": int(timedelta(days=30).total_seconds()), - "P6M": int(timedelta(days=31*6).total_seconds()), + "P6M": int(timedelta(days=31 * 6).total_seconds()), } keyprops = [ f"csk {lifetime['P1Y']} 13 256", @@ -103,7 +211,7 @@ def test_kasp_dnssec_keygen(): f"zsk {lifetime['P30D']} 8 2048", f"zsk {lifetime['P6M']} 8 3072", ] - keydir="keys" + keydir = "keys" out = keygen("kasp", "kasp", keydir) keys = isctest.kasp.keystr_to_keylist(out, keydir) expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)