From: Matthijs Mekking Date: Tue, 25 Nov 2025 10:17:40 +0000 (+0100) Subject: rollover: From setup.sh to pytest bootstrap X-Git-Tag: v9.21.17~22^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f31514e65855239ad114c21d82c5e17e018dc37a;p=thirdparty%2Fbind9.git rollover: From setup.sh to pytest bootstrap Introduce rollover/setup.py for all setup related test code. Introduce rollover/ns1 and rollover/ns2 to create a chain of trust to all rollover related test zones. The tld zones in rollover/ns2 contain a DSYNC record that at a later time will be used for testing Generalized DNS Notifications. Write a python version of private_type_record so we can put such records in the zone via jinja2 templating. --- diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index a3b71577d98..2c7ebcdc249 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -1650,3 +1650,19 @@ def wait_keymgr_done(server: NamedInstance, zone: str, reconfig: bool = False) - messages.append(f"keymgr: {zone} done") with server.watch_log_from_start(timeout=60) as watcher: watcher.wait_for_sequence(messages) + + +def private_type_record(zone: str, key: Key, rrtype: int = 65534) -> str: + """ + Write a private type record recording the state of the signing process for + a given zone and key, print the private type record with given RRtype, + indicating that the signing process for this key is completed. + """ + keyid = key.tag + algorithm = key.get_dnsalg() + secalg = int(key.get_metadata("Algorithm")) + + if algorithm < 256: + return f"{zone}. 0 IN TYPE{rrtype} \\# 5 {secalg:02x}{keyid:04x}0000" + + return f"{zone}. 0 IN TYPE{rrtype} \\# 7 {secalg:02x}{keyid:04x}0000{algorithm:04x}" diff --git a/bin/tests/system/isctest/template.py b/bin/tests/system/isctest/template.py index 4e27a219d14..f454dca7d03 100644 --- a/bin/tests/system/isctest/template.py +++ b/bin/tests/system/isctest/template.py @@ -82,6 +82,20 @@ class TemplateEngine: self.render(template[:-3], data) +@dataclass +class Nameserver: + name: str + ip: str + + +@dataclass +class Zone: + name: str + filename: str + ns: Nameserver + type: str = "primary" + + @dataclass class TrustAnchor: domain: str diff --git a/bin/tests/system/rollover/common.py b/bin/tests/system/rollover/common.py index ff2cf2b7810..23612234e44 100644 --- a/bin/tests/system/rollover/common.py +++ b/bin/tests/system/rollover/common.py @@ -15,6 +15,7 @@ import os import pytest from isctest.kasp import Ipub, IpubC, Iret +from isctest.vars.algorithms import Algorithm pytestmark = pytest.mark.extra_artifacts( [ @@ -34,9 +35,11 @@ pytestmark = pytest.mark.extra_artifacts( "ns*/K*.private", "ns*/K*.state", "ns*/keygen.out.*", + "ns*/managed-keys.**", "ns*/settime.out.*", "ns*/signer.out.*", "ns*/zones", + "ns1/root.db.in", ] ) @@ -137,3 +140,12 @@ def alg(): @pytest.fixture def size(): return os.environ["DEFAULT_BITS"] + + +def default_algorithm(): + return Algorithm( + os.environ["DEFAULT_ALGORITHM"], + int(os.environ["DEFAULT_ALGORITHM_NUMBER"]), + int(os.environ["DEFAULT_ALGORITHM_DST_NUMBER"]), + int(os.environ["DEFAULT_BITS"]), + ) diff --git a/bin/tests/system/rollover/ns1/named.conf.j2 b/bin/tests/system/rollover/ns1/named.conf.j2 new file mode 100644 index 00000000000..78aa3781b59 --- /dev/null +++ b/bin/tests/system/rollover/ns1/named.conf.j2 @@ -0,0 +1,31 @@ +/* + * 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. + */ + +// NS1 + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +zone "." { + type primary; + file "root.db.signed"; +}; diff --git a/bin/tests/system/rollover/ns1/root.db.j2.manual b/bin/tests/system/rollover/ns1/root.db.j2.manual new file mode 100644 index 00000000000..29a23bc02cf --- /dev/null +++ b/bin/tests/system/rollover/ns1/root.db.j2.manual @@ -0,0 +1,31 @@ +; 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. + +$TTL 300 +. IN SOA . a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +{% for dnskey in dnskeys %} +@dnskey@ +{% endfor %} + +{% for zone in delegations %} +{% set ns_name = zone.ns.name + "." + zone.name %} +@zone.name@ NS @ns_name@ +@ns_name@ A @zone.ns.ip@ +{% endfor %} diff --git a/bin/tests/system/rollover/ns2/named.conf.j2 b/bin/tests/system/rollover/ns2/named.conf.j2 new file mode 100644 index 00000000000..e1f7bffdd03 --- /dev/null +++ b/bin/tests/system/rollover/ns2/named.conf.j2 @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + allow-notify { 10.53.0.3; }; + recursion no; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +{% for zone in tlds %} +zone "@zone@" { + type primary; + file "@zone@.db.signed"; +}; +{% endfor %} diff --git a/bin/tests/system/rollover/ns2/template.db.j2.manual b/bin/tests/system/rollover/ns2/template.db.j2.manual new file mode 100644 index 00000000000..446a918a447 --- /dev/null +++ b/bin/tests/system/rollover/ns2/template.db.j2.manual @@ -0,0 +1,40 @@ +; 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. + +$TTL 300 +$ORIGIN @fqdn@ + +@fqdn@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 + +ns2 A 10.53.0.2 +ns3 A 10.53.0.3 + +scanner A 10.53.0.2 + +*._dsync DSYNC CDS NOTIFY @PORT@ scanner + +{% for dnskey in dnskeys %} +@dnskey@ +{% endfor %} + +{% for zone in delegations %} +{% set ns_name = zone.ns.name + "." + zone.name %} +@zone.name@. NS @ns_name@. +@ns_name@. A @zone.ns.ip@ +{% endfor %} diff --git a/bin/tests/system/rollover/ns3/named.common.conf.j2 b/bin/tests/system/rollover/ns3/named.common.conf.j2 index d1c3a054f13..813aa919e18 100644 --- a/bin/tests/system/rollover/ns3/named.common.conf.j2 +++ b/bin/tests/system/rollover/ns3/named.common.conf.j2 @@ -11,6 +11,14 @@ * information regarding copyright ownership. */ +{% if trust_anchors is defined %} +include "trusted.conf"; +{% set dnssec_validation = "yes" %} +{% else %} +{% set dnssec_validation = "auto" %} +{% endif %} + + options { query-source address 10.53.0.3; notify-source 10.53.0.3; @@ -20,8 +28,8 @@ options { listen-on { 10.53.0.3; }; listen-on-v6 { none; }; allow-transfer { any; }; - recursion no; - dnssec-validation no; + recursion yes; + dnssec-validation @dnssec_validation@; }; key rndc_key { @@ -35,5 +43,5 @@ controls { zone "." { type hint; - file "../../_common/root.hint.blackhole"; + file "../../_common/root.hint"; }; diff --git a/bin/tests/system/rollover/ns3/template.db.in b/bin/tests/system/rollover/ns3/template.db.j2.manual similarity index 82% rename from bin/tests/system/rollover/ns3/template.db.in rename to bin/tests/system/rollover/ns3/template.db.j2.manual index 010b05b3cb3..3903587ba9f 100644 --- a/bin/tests/system/rollover/ns3/template.db.in +++ b/bin/tests/system/rollover/ns3/template.db.j2.manual @@ -10,7 +10,7 @@ ; information regarding copyright ownership. $TTL 300 -@ IN SOA mname1. . ( +@fqdn@ IN SOA mname1. . ( 1 ; serial 20 ; refresh (20 seconds) 20 ; retry (20 seconds) @@ -25,3 +25,10 @@ a A 10.0.0.1 b A 10.0.0.2 c A 10.0.0.3 +{% for dnskey in dnskeys %} +@dnskey@ +{% endfor %} + +{% for privaterr in privaterrs %} +@privaterr@ +{% endfor %} diff --git a/bin/tests/system/rollover/ns3/trusted.conf.j2 b/bin/tests/system/rollover/ns3/trusted.conf.j2 new file mode 120000 index 00000000000..cb0be77b220 --- /dev/null +++ b/bin/tests/system/rollover/ns3/trusted.conf.j2 @@ -0,0 +1 @@ +../../_common/trusted.conf.j2 \ No newline at end of file diff --git a/bin/tests/system/rollover/setup.py b/bin/tests/system/rollover/setup.py new file mode 100644 index 00000000000..c924eb497ce --- /dev/null +++ b/bin/tests/system/rollover/setup.py @@ -0,0 +1,90 @@ +# 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. + +import shutil +from typing import List + +import isctest +from isctest.kasp import private_type_record +from isctest.template import Nameserver, TrustAnchor, Zone +from rollover.common import default_algorithm + + +class CmdHelper: + def __init__(self, env_name: str, base_params: str = ""): + self.bin_path = os.environ[env_name] + self.base_params = base_params + + def __call__(self, params: str, **kwargs): + args = f"{self.base_params} {params}".split() + return isctest.run.cmd([self.bin_path] + args, **kwargs).stdout.decode("utf-8") + + +def configure_tld(zonename: str, delegations: List[Zone]) -> Zone: + templates = isctest.template.TemplateEngine(".") + alg = default_algorithm() + keygen = CmdHelper("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600") + signer = CmdHelper("SIGNER", "-S -g") + + isctest.log.info(f"create {zonename} zone with delegations and sign") + + for zone in delegations: + shutil.copy(f"{zone.ns.name}/dsset-{zone.name}.", "ns2/") + + ksk_name = keygen(f"-f KSK {zonename}", cwd="ns2").strip() + zsk_name = keygen(f"{zonename}", cwd="ns2").strip() + ksk = isctest.kasp.Key(ksk_name, keydir="ns2") + zsk = isctest.kasp.Key(zsk_name, keydir="ns2") + dnskeys = [ksk.dnskey, zsk.dnskey] + + template = "template.db.j2.manual" + outfile = f"{zonename}.db" + tdata = { + "fqdn": f"{zonename}.", + "delegations": delegations, + "dnskeys": dnskeys, + } + templates.render(f"ns2/{outfile}", tdata, template=f"ns2/{template}") + signer(f"-P -x -O full -o {zonename} -f {outfile}.signed {outfile}", cwd="ns2") + + return Zone(zonename, f"{outfile}.signed", Nameserver("ns2", "10.53.0.2")) + + +def configure_root(delegations: List[Zone]) -> TrustAnchor: + templates = isctest.template.TemplateEngine(".") + alg = default_algorithm() + keygen = CmdHelper("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600") + signer = CmdHelper("SIGNER", "-S -g") + + zonename = "." + isctest.log.info("create root zone with delegations and sign") + + for zone in delegations: + shutil.copy(f"{zone.ns.name}/dsset-{zone.name}.", "ns1/") + + ksk_name = keygen(f"-f KSK {zonename}", cwd="ns1").strip() + zsk_name = keygen(f"{zonename}", cwd="ns1").strip() + ksk = isctest.kasp.Key(ksk_name, keydir="ns1") + zsk = isctest.kasp.Key(zsk_name, keydir="ns1") + dnskeys = [ksk.dnskey, zsk.dnskey] + + template = "root.db.j2.manual" + infile = "root.db.in" + outfile = "root.db.signed" + tdata = { + "fdqn": f"{zonename}.", + "delegations": delegations, + "dnskeys": dnskeys, + } + templates.render(f"ns1/{infile}", tdata, template=f"ns1/{template}") + signer(f"-P -x -O full -o {zonename} -f {outfile} {infile}", cwd="ns1") + + return ksk.into_ta("static-ds") diff --git a/bin/tests/system/rollover/setup.sh b/bin/tests/system/rollover/setup.sh deleted file mode 100644 index f465ddf46a7..00000000000 --- a/bin/tests/system/rollover/setup.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh -e - -# 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. - -# shellcheck source=conf.sh -. ../conf.sh - -cd "ns3" - -setup() { - zone="$1" - echo_i "setting up zone: $zone" - zonefile="${zone}.db" - infile="${zone}.db.infile" - echo "$zone" >>zones -} - -# Make lines shorter by storing key states in environment variables. -H="HIDDEN" -R="RUMOURED" -O="OMNIPRESENT" -U="UNRETENTIVE" - -# Zone to test manual rollover. -setup manual-rollover.kasp -T="now-7d" -keytimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $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 -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1 - -# Zone to test manual rollover. -setup manual-rollover-zrrsig-rumoured.kasp -T2="now-2h" -zsktimes="-P $T2 -A $T2" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 -f KSK $keytimes $zone 2>keygen.out.$zone.1) -ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $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 $T2 -z $R $T2 "$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 -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/rollover/tests_rollover_manual.py b/bin/tests/system/rollover/tests_rollover_manual.py index 75ae7d86c7a..75a705fca1d 100644 --- a/bin/tests/system/rollover/tests_rollover_manual.py +++ b/bin/tests/system/rollover/tests_rollover_manual.py @@ -13,9 +13,84 @@ from datetime import timedelta import os import isctest -from isctest.kasp import KeyTimingMetadata, Ipub, Iret +from isctest.kasp import KeyTimingMetadata, Ipub, Iret, private_type_record +from isctest.template import Nameserver, Zone + +from rollover.common import default_algorithm +from rollover.setup import ( + CmdHelper, + configure_root, + configure_tld, +) + + +def setup_zone(zone, ksk_time, ksk_settime, zsk_time, zsk_settime) -> Zone: + templates = isctest.template.TemplateEngine(".") + alg = default_algorithm() + keygen = CmdHelper("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600") + signer = CmdHelper("SIGNER", "-S -g") + settime = CmdHelper("SETTIME", "-s") + + isctest.log.info(f"setup {zone}") + template = "template.db.j2.manual" + outfile = f"{zone}.db" + + # Configuration. + isctest.log.info(f"setup {zone}") + template = "template.db.j2.manual" + outfile = f"{zone}.db" + # Key generation. + ksk_name = keygen(f"-f KSK -P {ksk_time} -A {ksk_time} {zone}", cwd="ns3").strip() + zsk_name = keygen(f"-P {zsk_time} -A {zsk_time} {zone}", cwd="ns3").strip() + settime(f"{ksk_settime} {ksk_name}", cwd="ns3") + settime(f"{zsk_settime} {zsk_name}", cwd="ns3") + # Signing. + ksk = isctest.kasp.Key(ksk_name, keydir="ns3") + zsk = isctest.kasp.Key(zsk_name, keydir="ns3") + dnskeys = [ksk.dnskey, zsk.dnskey] + privaterrs = [ + private_type_record(zone, ksk), + private_type_record(zone, zsk), + ] + tdata = { + "fqdn": f"{zone}.", + "dnskeys": dnskeys, + "privaterrs": privaterrs, + } + templates.render(f"ns3/{outfile}", tdata, template=f"ns3/{template}") + signer(f"-P -x -O raw -o {zone} -f {outfile}.signed {outfile}", cwd="ns3") + + return Zone(zone, outfile, Nameserver("ns3", "10.53.0.3")) + + +def bootstrap(): + zones = [] + + zone = "manual-rollover.kasp" + when = "now-7d" + ksk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {when} -r OMNIPRESENT {when} -d OMNIPRESENT {when}" + zsk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {when} -z OMNIPRESENT {when}" + zones.append(setup_zone(zone, when, ksk_settime, when, zsk_settime)) + + zone = "manual-rollover-zrrsig-rumoured.kasp" + then = "now-2h" + ksk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {when} -r OMNIPRESENT {when} -d OMNIPRESENT {when}" + zsk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {then} -z RUMOURED {then}" + zones.append(setup_zone(zone, when, ksk_settime, then, zsk_settime)) + + # Chain of trust. + data = { + "tlds": [], + "trust_anchors": [], + } + tld = configure_tld("kasp", zones) + data["tlds"].append("kasp") + + ta = configure_root([tld]) + data["trust_anchors"].append(ta) + + return data -from rollover.common import pytestmark # pylint: disable=unused-import CONFIG = { "dnskey-ttl": timedelta(hours=1),