]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Test manual-mode with CSK algorithm rollover
authorMatthijs Mekking <matthijs@isc.org>
Wed, 23 Jul 2025 07:27:04 +0000 (09:27 +0200)
committerMatthijs Mekking <matthijs@isc.org>
Thu, 21 Aug 2025 14:09:55 +0000 (16:09 +0200)
Update check_rollover_step to return the found keys. This can be used
to test that keymgr-manual-mode messages are correctly logged.

Parametrize each test case and in case of manual-mode, execute
additional checks. First a keymgr run should not change the existing
key state (with exceptions of timing events such as moving from
RUMOURED to OMNIPRESENT, and from UNRETENTIVE to HIDDEN). Appropriate
messages must be logged.

After enforcing the next step with 'rndc dnssec -step', the key state
should be the same as if the step were to be taken automatically.

bin/tests/system/isctest/kasp.py
bin/tests/system/rollover-algo-csk/tests_rollover_algo_csk_reconfig.py

index 03dedd9aa96e110bb9eb572d038c4e5b210998ec..b750cb1f785916dabd619b0cc11e12c2cf209ff2 100644 (file)
@@ -1292,6 +1292,8 @@ def check_rollover_step(server, config, policy, step):
     if nextev is not None:
         isctest.run.retry_with_timeout(check_next_key_event, timeout=5)
 
+    return expected
+
 
 def verify_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None):
     """
index e10d15a5afae37997e9952813982d9a8a62ca04f..38527ad5e0dc21c2f2952f2ad9c79fbe9fa7ba67 100644 (file)
@@ -15,6 +15,7 @@ import pytest
 
 import isctest
 from isctest.kasp import KeyTimingMetadata
+from isctest.util import param
 from rollover.common import (
     pytestmark,
     alg,
@@ -28,6 +29,7 @@ from rollover.common import (
     ALGOROLL_KEYTTLPROP,
     ALGOROLL_OFFSETS,
     ALGOROLL_OFFVAL,
+    DURATION,
     TIMEDELTA,
 )
 
@@ -50,11 +52,45 @@ def reconfigure(ns6, templates):
     TIME_PASSED = KeyTimingMetadata.now().value - start_time.value
 
 
-def test_algoroll_csk_reconfig_step1(ns6, alg, size):
-    zone = "step1.csk-algorithm-roll.kasp"
+@pytest.mark.parametrize(
+    "tld",
+    [
+        param("kasp"),
+        param("manual"),
+    ],
+)
+def test_algoroll_csk_reconfig_step1(tld, ns6, alg, size):
+    zone = f"step1.csk-algorithm-roll.{tld}"
+    policy = f"{POLICY}-{tld}"
 
     isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
 
+    if tld == "manual":
+        # Same as initial.
+        step = {
+            "zone": zone,
+            "cdss": CDSS,
+            "keyprops": [
+                f"csk 0 8 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{-DURATION['P7D']}",
+            ],
+            "manual-mode": True,
+            "nextev": None,
+        }
+        keys = isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
+
+        # Check logs.
+        tag = keys[0].key.tag
+        msg1 = f"keymgr-manual-mode: block retire DNSKEY {zone}/RSASHA256/{tag} (CSK)"
+        msg2 = f"keymgr-manual-mode: block new key generation for zone {zone} (policy {policy})"
+        ns6.log.expect(msg1)
+        ns6.log.expect(msg2)
+
+        # Force step.
+        with ns6.watch_log_from_here() as watcher:
+            ns6.rndc(f"dnssec -step {zone}")
+            watcher.wait_for_line(f"keymgr: {zone} done")
+
+    # Check state after step.
     step = {
         "zone": zone,
         "cdss": CDSS,
@@ -67,14 +103,24 @@ def test_algoroll_csk_reconfig_step1(ns6, alg, size):
         # Next key event is when the ecdsa256 keys have been propagated.
         "nextev": ALGOROLL_IPUB,
     }
-    isctest.kasp.check_rollover_step(ns6, CONFIG, POLICY, step)
+    isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
 
 
-def test_algoroll_csk_reconfig_step2(ns6, alg, size):
-    zone = "step2.csk-algorithm-roll.kasp"
+@pytest.mark.parametrize(
+    "tld",
+    [
+        param("kasp"),
+        param("manual"),
+    ],
+)
+def test_algoroll_csk_reconfig_step2(tld, ns6, alg, size):
+    zone = f"step2.csk-algorithm-roll.{tld}"
+    policy = f"{POLICY}-{tld}"
 
     isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
 
+    # manual-mode: Nothing changing in the zone, no 'dnssec -step' required.
+
     step = {
         "zone": zone,
         "cdss": CDSS,
@@ -94,14 +140,58 @@ def test_algoroll_csk_reconfig_step2(ns6, alg, size):
         # the time passed between key creation and invoking 'rndc reconfig'.
         "nextev": ALGOROLL_IPUBC - ALGOROLL_IPUB - TIME_PASSED,
     }
-    isctest.kasp.check_rollover_step(ns6, CONFIG, POLICY, step)
+    isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
 
 
-def test_algoroll_csk_reconfig_step3(ns6, alg, size):
-    zone = "step3.csk-algorithm-roll.kasp"
+@pytest.mark.parametrize(
+    "tld",
+    [
+        param("kasp"),
+        param("manual"),
+    ],
+)
+def test_algoroll_csk_reconfig_step3(tld, ns6, alg, size):
+    zone = f"step3.csk-algorithm-roll.{tld}"
+    policy = f"{POLICY}-{tld}"
 
     isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
 
+    if tld == "manual":
+        # Same as step 2, but the zone signatures have become OMNIPRESENT.
+        step = {
+            "zone": zone,
+            "cdss": CDSS,
+            "keyprops": [
+                f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFVAL}",
+                f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFSETS['step3']}",
+            ],
+            "manual-mode": True,
+            "nextev": None,
+        }
+        keys = isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
+
+        # Check logs.
+        tag = keys[1].key.tag
+        msg = f"keymgr-manual-mode: block transition CSK {zone}/ECDSAP256SHA256/{tag} type DS state HIDDEN to state RUMOURED"
+        ns6.log.expect(msg)
+
+        # Force step.
+        with ns6.watch_log_from_here() as watcher:
+            ns6.rndc(f"dnssec -step {zone}")
+            watcher.wait_for_line(f"keymgr: {zone} done")
+
+        # Check logs.
+        tag = keys[0].key.tag
+        msg = f"keymgr-manual-mode: block transition CSK {zone}/RSASHA256/{tag} type DS state OMNIPRESENT to state UNRETENTIVE"
+        if msg in ns6.log:
+            # Force step.
+            isctest.log.debug(
+                f"keymgr-manual-mode blocking transition CSK {zone}/RSASHA256/{tag} type DS state OMNIPRESENT to state UNRETENTIVE, step again"
+            )
+            with ns6.watch_log_from_here() as watcher:
+                ns6.rndc(f"dnssec -step {zone}")
+                watcher.wait_for_line(f"keymgr: {zone} done")
+
     step = {
         "zone": zone,
         "cdss": CDSS,
@@ -114,14 +204,46 @@ def test_algoroll_csk_reconfig_step3(ns6, alg, size):
         # after the publication interval of the parent side.
         "nextev": ALGOROLL_IRETKSK - TIME_PASSED,
     }
-    isctest.kasp.check_rollover_step(ns6, CONFIG, POLICY, step)
+    isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
 
 
-def test_algoroll_csk_reconfig_step4(ns6, alg, size):
-    zone = "step4.csk-algorithm-roll.kasp"
+@pytest.mark.parametrize(
+    "tld",
+    [
+        param("kasp"),
+        param("manual"),
+    ],
+)
+def test_algoroll_csk_reconfig_step4(tld, ns6, alg, size):
+    zone = f"step4.csk-algorithm-roll.{tld}"
+    policy = f"{POLICY}-{tld}"
 
     isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
 
+    if tld == "manual":
+        # Same as step 3, but the DS has become HIDDEN/OMNIPRESENT.
+        step = {
+            "zone": zone,
+            "cdss": CDSS,
+            "keyprops": [
+                f"csk 0 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:hidden offset:{ALGOROLL_OFFVAL}",
+                f"csk 0 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{ALGOROLL_OFFSETS['step4']}",
+            ],
+            "manual-mode": True,
+            "nextev": None,
+        }
+        keys = isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
+
+        # Check logs.
+        tag = keys[0].key.tag
+        msg = f"keymgr-manual-mode: block transition CSK {zone}/RSASHA256/{tag} type DNSKEY state OMNIPRESENT to state UNRETENTIVE"
+        ns6.log.expect(msg)
+
+        # Force step.
+        with ns6.watch_log_from_here() as watcher:
+            ns6.rndc(f"dnssec -step {zone}")
+            watcher.wait_for_line(f"keymgr: {zone} done")
+
     step = {
         "zone": zone,
         "cdss": CDSS,
@@ -134,14 +256,24 @@ def test_algoroll_csk_reconfig_step4(ns6, alg, size):
         # This happens after the DNSKEY TTL plus zone propagation delay.
         "nextev": ALGOROLL_KEYTTLPROP,
     }
-    isctest.kasp.check_rollover_step(ns6, CONFIG, POLICY, step)
+    isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
 
 
-def test_algoroll_csk_reconfig_step5(ns6, alg, size):
-    zone = "step5.csk-algorithm-roll.kasp"
+@pytest.mark.parametrize(
+    "tld",
+    [
+        param("kasp"),
+        param("manual"),
+    ],
+)
+def test_algoroll_csk_reconfig_step5(tld, ns6, alg, size):
+    zone = f"step5.csk-algorithm-roll.{tld}"
+    policy = f"{POLICY}-{tld}"
 
     isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
 
+    # manual-mode: Nothing changing in the zone, no 'dnssec -step' required.
+
     step = {
         "zone": zone,
         "cdss": CDSS,
@@ -158,14 +290,24 @@ def test_algoroll_csk_reconfig_step5(ns6, alg, size):
         # between key creation and invoking 'rndc reconfig'.
         "nextev": ALGOROLL_IRET - ALGOROLL_IRETKSK - ALGOROLL_KEYTTLPROP - TIME_PASSED,
     }
-    isctest.kasp.check_rollover_step(ns6, CONFIG, POLICY, step)
+    isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)
 
 
-def test_algoroll_csk_reconfig_step6(ns6, alg, size):
-    zone = "step6.csk-algorithm-roll.kasp"
+@pytest.mark.parametrize(
+    "tld",
+    [
+        param("kasp"),
+        param("manual"),
+    ],
+)
+def test_algoroll_csk_reconfig_step6(tld, ns6, alg, size):
+    zone = f"step6.csk-algorithm-roll.{tld}"
+    policy = f"{POLICY}-{tld}"
 
     isctest.kasp.wait_keymgr_done(ns6, zone, reconfig=True)
 
+    # manual-mode: Nothing changing in the zone, no 'dnssec -step' required.
+
     step = {
         "zone": zone,
         "cdss": CDSS,
@@ -179,4 +321,4 @@ def test_algoroll_csk_reconfig_step6(ns6, alg, size):
         # loadkeys interval.
         "nextev": TIMEDELTA["PT1H"],
     }
-    isctest.kasp.check_rollover_step(ns6, CONFIG, POLICY, step)
+    isctest.kasp.check_rollover_step(ns6, CONFIG, policy, step)