]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Convert kasp default test cases to pytest
authorMatthijs Mekking <matthijs@isc.org>
Fri, 14 Mar 2025 11:52:36 +0000 (12:52 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Thu, 17 Apr 2025 11:50:49 +0000 (13:50 +0200)
This commit deals with converting the test cases related to the default
dnssec-policy.

This requires a new method 'check_update_is_signed'. This method will
be used in future tests as well, and checks if an expected record is
in the zone and is properly signed.

Remove the counterparts for the newly added test from the kasp shell
tests script.

bin/tests/system/isctest/kasp.py
bin/tests/system/kasp/tests.sh
bin/tests/system/kasp/tests_kasp.py

index e4b3ead5cc811aa69261656c923136812df5ad1d..9e23b9a6ef2269fcdc2c5f53f38dbd52a7b2c948 100644 (file)
@@ -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.
index 86b0bfbcd71d02afd2d1137f20b445d97ab154ef..305ae548ac03a99db259a9788431cc124dc00218 100644 (file)
@@ -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.
 #
index 55dbf12f89155ec2840e07c7e6d19f6a949fa49e..ba9217b3ee3bfb4b7ffe96b0f0dd0a76ff7037fc 100644 (file)
@@ -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)