From: Matthijs Mekking Date: Mon, 24 Nov 2025 08:45:51 +0000 (+0100) Subject: Test multi-master dnssec-policy setup X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=affe84e55bafbb53dce30ad15e6dacb107f2452c;p=thirdparty%2Fbind9.git Test multi-master dnssec-policy setup 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. --- diff --git a/bin/tests/system/rollover/ns3/named.conf.j2 b/bin/tests/system/rollover/ns3/named.conf.j2 index 528376f386a..09f895f2e04 100644 --- a/bin/tests/system/rollover/ns3/named.conf.j2 +++ b/bin/tests/system/rollover/ns3/named.conf.j2 @@ -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 index 00000000000..bbecf098a8f --- /dev/null +++ b/bin/tests/system/rollover/ns4/kasp.conf.j2 @@ -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 index 00000000000..9fd3520d37a --- /dev/null +++ b/bin/tests/system/rollover/ns4/named.common.conf.j2 @@ -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 index 00000000000..22d79cd0c9a --- /dev/null +++ b/bin/tests/system/rollover/ns4/named.conf.j2 @@ -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; +}; diff --git a/bin/tests/system/rollover/tests_rollover_manual.py b/bin/tests/system/rollover/tests_rollover_manual.py index d7e326db9c2..004cc1c9013 100644 --- a/bin/tests/system/rollover/tests_rollover_manual.py +++ b/bin/tests/system/rollover/tests_rollover_manual.py @@ -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)