]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Test manual-mode error case
authorMatthijs Mekking <matthijs@isc.org>
Wed, 20 Aug 2025 13:41:13 +0000 (15:41 +0200)
committerMatthijs Mekking <matthijs@isc.org>
Thu, 21 Aug 2025 14:09:55 +0000 (16:09 +0200)
If we hit an error when issuing an 'rndc dnssec -step' command, and the
keymgr runs again at a later scheduled time, we don't want to enforce
transitions.

bin/tests/system/kasp/ns3/named-fips.conf.in
bin/tests/system/kasp/ns3/policies/autosign.conf.in
bin/tests/system/kasp/ns3/setup.sh
bin/tests/system/kasp/tests_kasp.py

index 109f3ad811fd9660d9afcc2fd352dd26ce4d967a..665b37821ef105cf855aa97845f831f352cb27ce 100644 (file)
@@ -286,6 +286,15 @@ zone "keyfiles-missing.autosign" {
        dnssec-policy "autosign";
 };
 
+/*
+ * Zone that has missing key files, manual-mode.
+ */
+zone "keyfiles-missing.manual" {
+       type primary;
+       file "keyfiles-missing.manual.db";
+       dnssec-policy "manual";
+};
+
 /*
  * Zone that has missing private KSK.
  */
index c54786247f6722ad5d455ae4ddbe4d45d43906b2..9ccc4e62b9bca66a9e850206bd75da68149d76fe 100644 (file)
@@ -24,3 +24,19 @@ dnssec-policy "autosign" {
                zsk key-directory lifetime P1Y algorithm @DEFAULT_ALGORITHM@;
        };
 };
+
+dnssec-policy "manual" {
+
+       signatures-refresh P1W;
+       signatures-validity P2W;
+       signatures-validity-dnskey P2W;
+
+       dnskey-ttl 300;
+
+       keys {
+               ksk key-directory lifetime P2Y algorithm @DEFAULT_ALGORITHM@;
+               zsk key-directory lifetime P2M algorithm @DEFAULT_ALGORITHM@;
+       };
+
+       manual-mode yes;
+};
index 4e521aa7696a08f76830a6fbbe9d764e8ad81c54..756c0af19c396f016c3ad4eb232fa1bb51c47ada 100644 (file)
@@ -261,6 +261,19 @@ private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
 cp $infile $zonefile
 $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
 
+# These signatures are still good, but the key files will be removed
+# before a second run of reconfiguring keys, now in manual-mode.
+setup keyfiles-missing.manual
+KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1)
+ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $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 -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
+
 # These signatures are already expired, and the private ZSK is retired.
 setup zsk-retired.autosign
 zsktimes="$keytimes -I now"
index f43f0dea6f12d1bc260724ae2e4799c08c16c745..4e91565a78fd1bf3fc11cf8e83224fef6d558973 100644 (file)
@@ -118,6 +118,7 @@ lifetime = {
     "P1Y": int(timedelta(days=365).total_seconds()),
     "P30D": int(timedelta(days=30).total_seconds()),
     "P6M": int(timedelta(days=31 * 6).total_seconds()),
+    "P2M": int(timedelta(days=31 * 2).total_seconds()),
 }
 
 KASP_INHERIT_TSIG_SECRET = {
@@ -157,10 +158,18 @@ def fips_properties(alg, bits=None):
     ]
 
 
-def check_all(server, zone, policy, ksks, zsks, zsk_missing=False, tsig=None):
+def check_all(
+    server, zone, policy, ksks, zsks, manual_mode=False, zsk_missing=False, tsig=None
+):
     isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy)
     isctest.kasp.check_apex(
-        server, zone, ksks, zsks, zsk_missing=zsk_missing, tsig=tsig
+        server,
+        zone,
+        ksks,
+        zsks,
+        manual_mode=manual_mode,
+        zsk_missing=zsk_missing,
+        tsig=tsig,
     )
     isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig)
 
@@ -1662,3 +1671,100 @@ def test_kasp_reload_restart(ns6):
 
     newttl = 400
     isctest.run.retry_with_timeout(check_soa_ttl, timeout=10)
+
+
+def test_kasp_manual_mode(ns3):
+
+    keydir = ns3.identifier
+    zone = "keyfiles-missing.manual"
+    policy = "manual"
+    ttl = int(autosign_config["dnskey-ttl"].total_seconds())
+    offset = -timedelta(days=30 * 6)
+    alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
+    size = os.environ["DEFAULT_BITS"]
+    keyprops = [
+        f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
+        f"zsk {lifetime['P2M']} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
+    ]
+
+    isctest.kasp.wait_keymgr_done(ns3, zone)
+
+    isctest.log.info(f"check test case zone {zone} policy {policy}")
+
+    # First make sure the zone is signed.
+    isctest.kasp.check_dnssec_verify(ns3, zone)
+
+    # Key properties.
+    expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=keyprops)
+
+    # Key files.
+    keys = isctest.kasp.keydir_to_keylist(zone, keydir)
+    ksks = [k for k in keys if k.is_ksk()]
+    zsks = [k for k in keys if not k.is_ksk()]
+    isctest.kasp.check_dnssec_verify(ns3, zone)
+    isctest.kasp.check_keys(zone, keys, expected)
+
+    for kp in expected:
+        kp.set_expected_keytimes(autosign_config, offset=offset)
+
+    isctest.kasp.check_keytimes(keys, expected)
+
+    check_all(ns3, zone, policy, ksks, zsks, manual_mode=True)
+
+    # Key rollover should have been be blocked.
+    tag = expected[1].key.tag
+    blockmsg = f"keymgr-manual-mode: block ZSK rollover for key {zone}/ECDSAP256SHA256/{tag} (policy {policy})"
+    ns3.log.expect(blockmsg)
+
+    # Remove files.
+    for key in ksks + zsks:
+        shutil.copyfile(key.privatefile, f"{key.privatefile}.backup")
+        shutil.copyfile(key.keyfile, f"{key.keyfile}.backup")
+        shutil.copyfile(key.statefile, f"{key.statefile}.backup")
+
+        os.remove(key.keyfile)
+        os.remove(key.privatefile)
+        os.remove(key.statefile)
+
+    # Force step.
+    with ns3.watch_log_from_here() as watcher:
+        ns3.rndc(f"dnssec -step {zone}", log=False)
+        watcher.wait_for_line(
+            f"zone {zone}/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing"
+        )
+
+    # Restore key files.
+    for key in ksks + zsks:
+        shutil.copyfile(f"{key.privatefile}.backup", key.privatefile)
+        shutil.copyfile(f"{key.keyfile}.backup", key.keyfile)
+        shutil.copyfile(f"{key.statefile}.backup", key.statefile)
+
+    # Load keys.
+    with ns3.watch_log_from_here() as watcher:
+        ns3.rndc(f"loadkeys {zone}", log=False)
+        watcher.wait_for_line(blockmsg)
+
+    # Check keys again, make sure no new keys are created.
+    isctest.kasp.check_keys(zone, keys, expected)
+    isctest.kasp.check_keytimes(keys, expected)
+    check_all(ns3, zone, policy, ksks, zsks, manual_mode=True)
+    isctest.kasp.check_dnssec_verify(ns3, zone)
+
+    # Force step.
+    with ns3.watch_log_from_here() as watcher:
+        ns3.rndc(f"dnssec -step {zone}", log=False)
+        watcher.wait_for_line(f"keymgr: {zone} done")
+
+    # Check keys again, make sure the rollover has started.
+    keyprops = [
+        f"ksk {lifetime['P2Y']} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
+        f"zsk {lifetime['P2M']} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent",
+        f"zsk {lifetime['P2M']} {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden",
+    ]
+    expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=keyprops)
+    keys = isctest.kasp.keydir_to_keylist(zone, keydir)
+    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)
+    check_all(ns3, zone, policy, ksks, zsks, manual_mode=True)
+    isctest.kasp.check_dnssec_verify(ns3, zone)