]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Test multi-master dnssec-policy setup 11268/head
authorMatthijs Mekking <matthijs@isc.org>
Mon, 24 Nov 2025 08:45:51 +0000 (09:45 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Tue, 7 Jul 2026 07:01:41 +0000 (09:01 +0200)
Update the manual rollover test case with a multi-master setup.  In this
scenario, key files are generated, as well as rollovers are started
on one server (ns3) and key files are copied to the other server (ns4).

Add checks that the begin and end key states are the same.

bin/tests/system/rollover/ns3/named.conf.j2
bin/tests/system/rollover/ns4/kasp.conf.j2 [new file with mode: 0644]
bin/tests/system/rollover/ns4/named.common.conf.j2 [new file with mode: 0644]
bin/tests/system/rollover/ns4/named.conf.j2 [new file with mode: 0644]
bin/tests/system/rollover/tests_rollover_manual.py

index 528376f386a584ba0f844646fd856d7e2d966bdf..09f895f2e04f606edf027789059780842b70e24f 100644 (file)
@@ -6,6 +6,7 @@ zone "@zone@" {
         type primary;
         file "@zone@.db";
         dnssec-policy "manual-rollover";
+        notify no;
 };
 
 {% endfor %}
diff --git a/bin/tests/system/rollover/ns4/kasp.conf.j2 b/bin/tests/system/rollover/ns4/kasp.conf.j2
new file mode 100644 (file)
index 0000000..bbecf09
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+dnssec-policy "manual-rollover" {
+       dnskey-ttl 3600;
+
+       keys {
+               ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
+               zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
+       };
+};
diff --git a/bin/tests/system/rollover/ns4/named.common.conf.j2 b/bin/tests/system/rollover/ns4/named.common.conf.j2
new file mode 100644 (file)
index 0000000..9fd3520
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+options {
+        query-source address 10.53.0.4;
+        notify-source 10.53.0.4;
+        transfer-source 10.53.0.4;
+        port @PORT@;
+        pid-file "named.pid";
+        listen-on { 10.53.0.4; };
+        listen-on-v6 { none; };
+        allow-transfer { any; };
+        recursion no;
+        dnssec-validation no;
+};
+
+key rndc_key {
+        secret "1234abcd8765";
+        algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+        inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." {
+        type hint;
+        file "../../_common/root.hint.blackhole";
+};
diff --git a/bin/tests/system/rollover/ns4/named.conf.j2 b/bin/tests/system/rollover/ns4/named.conf.j2
new file mode 100644 (file)
index 0000000..22d79cd
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+include "kasp.conf";
+include "named.common.conf";
+
+zone "manual-rollover.kasp" {
+       type primary;
+       file "manual-rollover.kasp.db";
+       dnssec-policy "manual-rollover";
+       notify no;
+};
index d7e326db9c246c8bb2cc099e1fa622338244687c..004cc1c9013588e7e7246b4314d85c84b1c72331 100644 (file)
@@ -11,6 +11,8 @@
 
 from datetime import timedelta
 
+import shutil
+
 from isctest.algorithms import Algorithm
 from isctest.kasp import (
     Ipub,
@@ -45,9 +47,11 @@ def setup_zone(zone, ksk_time, ksk_timings, zsk_time, zsk_timings) -> Zone:
     outfile = f"{zone}.db"
     # Key generation.
     ksk_name = keygen(
-        f"-f KSK -P {ksk_time} -A {ksk_time} {zone}", cwd="ns3"
+        f"-f KSK -P {ksk_time} -A {ksk_time} -L 3600 {zone}", cwd="ns3"
+    ).out.strip()
+    zsk_name = keygen(
+        f"-P {zsk_time} -A {zsk_time} -L 3600 {zone}", cwd="ns3"
     ).out.strip()
-    zsk_name = keygen(f"-P {zsk_time} -A {zsk_time} {zone}", cwd="ns3").out.strip()
     # Key state timing metadata.
     setkeytimes(ksk_name, ksk_timings)
     setkeytimes(zsk_name, zsk_timings)
@@ -88,6 +92,19 @@ def bootstrap():
     )
     zones.append(setup_zone(zone, when, ksk_timings, when, zsk_timings))
 
+    # Copy files to ns4.
+    shutil.copyfile(f"ns3/{zone}.db", f"ns4/{zone}.db")
+    shutil.copyfile(f"ns3/{zone}.db.signed", f"ns4/{zone}.db.signed")
+
+    keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
+    for key in keys:
+        keyfile = f"{key.name}.key"
+        privatefile = f"{key.name}.private"
+        statefile = f"{key.name}.state"
+        shutil.copyfile(f"ns3/{keyfile}", f"ns4/{keyfile}")
+        shutil.copyfile(f"ns3/{privatefile}", f"ns4/{privatefile}")
+        shutil.copyfile(f"ns3/{statefile}", f"ns4/{statefile}")
+
     zone = "manual-rollover-zrrsig-rumoured.kasp"
     then = "now-2h"
     zsk_timings = SettimeOptions(
@@ -118,7 +135,7 @@ CONFIG = {
     "parent-propagation-delay": timedelta(hours=1),
     "publish-safety": timedelta(hours=1),
     "retire-safety": timedelta(hours=1),
-    "signatures-refresh": timedelta(days=7),
+    "signatures-refresh": timedelta(days=5),
     "signatures-validity": timedelta(days=14),
     "zone-propagation-delay": timedelta(minutes=5),
 }
@@ -126,35 +143,59 @@ CONFIG = {
 POLICY = "manual-rollover"
 
 
-def test_rollover_manual(ns3, default_algorithm):
-    ttl = int(CONFIG["dnskey-ttl"].total_seconds())
-    zone = "manual-rollover.kasp"
+def retire_key(expected, kid, now):
+    ksk = expected[kid].key.is_ksk()
+    zsk = expected[kid].key.is_zsk()
+    expected[kid].timing["Retired"] = now + Ipub(CONFIG)
+    expected[kid].timing["Removed"] = expected[kid].timing["Retired"] + Iret(
+        CONFIG, zsk=zsk, ksk=ksk
+    )
 
-    isctest.kasp.wait_keymgr_done(ns3, zone)
 
-    isctest.kasp.check_dnssec_verify(ns3, zone)
+def run_checks(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_keytimes(keys, expected)
+    isctest.kasp.check_dnssecstatus(server, zone, keys, policy=POLICY)
+    isctest.kasp.check_apex(server, zone, ksks, zsks)
+    isctest.kasp.check_subdomain(server, zone, ksks, zsks)
+
+
+def test_rollover_manual(servers):
+    ns3 = servers["ns3"]
+    ns4 = servers["ns4"]
+    ns = [ns4, ns3]
+
+    default_algorithm = Algorithm.default()
+    ttl = int(CONFIG["dnskey-ttl"].total_seconds())
+    zone = "manual-rollover.kasp"
 
     key_properties = [
         f"ksk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
         f"zsk unlimited {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
     ]
+
     expected = isctest.kasp.policy_to_properties(ttl, key_properties)
-    keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier)
-    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 server in ns:
+        isctest.kasp.wait_keymgr_done(server, zone)
+        isctest.kasp.check_dnssec_verify(server, zone)
 
-    offset = -timedelta(days=7)
-    for kp in expected:
-        kp.set_expected_keytimes(CONFIG, offset=offset)
+        keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
+        isctest.kasp.check_keys(zone, keys, expected)
 
-    isctest.kasp.check_keytimes(keys, expected)
-    isctest.kasp.check_dnssecstatus(ns3, zone, keys, policy=POLICY)
-    isctest.kasp.check_apex(ns3, zone, ksks, zsks)
-    isctest.kasp.check_subdomain(ns3, zone, ksks, zsks)
+        offset = -timedelta(days=7)
+        for kp in expected:
+            kp.set_expected_keytimes(CONFIG, offset=offset)
+
+        run_checks(server, zone, keys, expected)
 
     # Schedule KSK rollover in six months.
+    keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier)
+    ksks = [k for k in keys if k.is_ksk()]
+    zsks = [k for k in keys if not k.is_ksk()]
+
     assert len(ksks) == 1
     ksk = ksks[0]
     startroll = expected[0].timing["Active"] + timedelta(days=30 * 6)
@@ -169,15 +210,12 @@ def test_rollover_manual(ns3, default_algorithm):
 
     isctest.kasp.check_dnssec_verify(ns3, zone)
     isctest.kasp.check_keys(zone, keys, expected)
-    isctest.kasp.check_keytimes(keys, expected)
-    isctest.kasp.check_dnssecstatus(ns3, zone, keys, policy=POLICY)
-    isctest.kasp.check_apex(ns3, zone, ksks, zsks)
-    isctest.kasp.check_subdomain(ns3, zone, ksks, zsks)
+    run_checks(ns3, zone, keys, expected)
 
     # Schedule KSK rollover now.
-    now = KeyTimingMetadata.now()
+    ksknow = KeyTimingMetadata.now()
     with ns3.watch_log_from_here() as watcher:
-        ns3.rndc(f"dnssec -rollover -key {ksk.tag} -when {now} {zone}")
+        ns3.rndc(f"dnssec -rollover -key {ksk.tag} -when {ksknow} {zone}")
         watcher.wait_for_line(f"keymgr: {zone} done")
 
     isctest.kasp.check_dnssec_verify(ns3, zone)
@@ -204,22 +242,16 @@ def test_rollover_manual(ns3, default_algorithm):
             off = 0
         kp.set_expected_keytimes(CONFIG, offset=off)
 
-    expected[0].timing["Retired"] = now + Ipub(CONFIG)
-    expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret(
-        CONFIG, zsk=False, ksk=True
-    )
+    retire_key(expected, 0, ksknow)
 
-    isctest.kasp.check_keytimes(keys, expected)
-    isctest.kasp.check_dnssecstatus(ns3, zone, keys, policy=POLICY)
-    isctest.kasp.check_apex(ns3, zone, ksks, zsks)
-    isctest.kasp.check_subdomain(ns3, zone, ksks, zsks)
+    run_checks(ns3, zone, keys, expected)
 
     # Schedule ZSK rollover now.
     assert len(zsks) == 1
     zsk = zsks[0]
-    now = KeyTimingMetadata.now()
+    zsknow = KeyTimingMetadata.now()
     with ns3.watch_log_from_here() as watcher:
-        ns3.rndc(f"dnssec -rollover -key {zsk.tag} -when {now} {zone}")
+        ns3.rndc(f"dnssec -rollover -key {zsk.tag} -when {zsknow} {zone}")
         watcher.wait_for_line(f"keymgr: {zone} done")
 
     isctest.kasp.check_dnssec_verify(ns3, zone)
@@ -243,11 +275,50 @@ def test_rollover_manual(ns3, default_algorithm):
     expected[3].metadata["Predecessor"] = expected[2].key.tag
     isctest.kasp.check_keyrelationships(keys, expected)
 
+    for kp in expected:
+        off = offset
+        if "Predecessor" in kp.metadata:
+            off = 0
+        kp.set_expected_keytimes(CONFIG, offset=off)
+
+    retire_key(expected, 0, ksknow)
+    retire_key(expected, 2, zsknow)
+
+    run_checks(ns3, zone, keys, expected)
+
     # Try to schedule a ZSK rollover for an inactive key (should fail).
     zsk = expected[3].key
     response = ns3.rndc(f"dnssec -rollover -key {zsk.tag} {zone}")
     assert "key is not actively signing" in response.out
 
+    # Copy files to ns4, check if the state is the same after all manual rollovers issued at ns3.
+    for key in keys:
+        keyfile = f"{key.name}.key"
+        privatefile = f"{key.name}.private"
+        statefile = f"{key.name}.state"
+        shutil.copyfile(f"{ns3.identifier}/{keyfile}", f"{ns4.identifier}/{keyfile}")
+        shutil.copyfile(
+            f"{ns3.identifier}/{privatefile}", f"{ns4.identifier}/{privatefile}"
+        )
+        shutil.copyfile(
+            f"{ns3.identifier}/{statefile}", f"{ns4.identifier}/{statefile}"
+        )
+
+    with ns4.watch_log_from_here() as watcher:
+        ns4.rndc(f"loadkeys {zone}")
+        watcher.wait_for_line(f"keymgr: {zone} done")
+
+    isctest.kasp.check_dnssec_verify(ns4, zone)
+
+    keys = isctest.kasp.keydir_to_keylist(zone, ns4.identifier)
+    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)
+    isctest.kasp.check_keyrelationships(keys, expected)
+
+    run_checks(ns4, zone, keys, expected)
+
 
 def test_rollover_manual_zrrsig_rumoured(ns3, default_algorithm):
     ttl = int(CONFIG["dnskey-ttl"].total_seconds())
@@ -265,7 +336,6 @@ def test_rollover_manual_zrrsig_rumoured(ns3, default_algorithm):
     ]
     expected = isctest.kasp.policy_to_properties(ttl, key_properties)
     keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier)
-    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)
@@ -273,10 +343,7 @@ def test_rollover_manual_zrrsig_rumoured(ns3, default_algorithm):
     for kp in expected:
         kp.set_expected_keytimes(CONFIG)
 
-    isctest.kasp.check_keytimes(keys, expected)
-    isctest.kasp.check_dnssecstatus(ns3, zone, keys, policy=POLICY)
-    isctest.kasp.check_apex(ns3, zone, ksks, zsks)
-    isctest.kasp.check_subdomain(ns3, zone, ksks, zsks)
+    run_checks(ns3, zone, keys, expected)
 
     # Schedule ZSK rollover now.
     assert len(zsks) == 1
@@ -296,8 +363,6 @@ def test_rollover_manual_zrrsig_rumoured(ns3, default_algorithm):
     ]
     expected = isctest.kasp.policy_to_properties(ttl, key_properties)
     keys = isctest.kasp.keydir_to_keylist(zone, ns3.identifier)
-    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)