]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
rollover: From setup.sh to pytest bootstrap
authorMatthijs Mekking <matthijs@isc.org>
Tue, 25 Nov 2025 10:17:40 +0000 (11:17 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Fri, 19 Dec 2025 10:47:49 +0000 (11:47 +0100)
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.

13 files changed:
bin/tests/system/isctest/kasp.py
bin/tests/system/isctest/template.py
bin/tests/system/rollover/common.py
bin/tests/system/rollover/ns1/named.conf.j2 [new file with mode: 0644]
bin/tests/system/rollover/ns1/root.db.j2.manual [new file with mode: 0644]
bin/tests/system/rollover/ns2/named.conf.j2 [new file with mode: 0644]
bin/tests/system/rollover/ns2/template.db.j2.manual [new file with mode: 0644]
bin/tests/system/rollover/ns3/named.common.conf.j2
bin/tests/system/rollover/ns3/template.db.j2.manual [moved from bin/tests/system/rollover/ns3/template.db.in with 82% similarity]
bin/tests/system/rollover/ns3/trusted.conf.j2 [new symlink]
bin/tests/system/rollover/setup.py [new file with mode: 0644]
bin/tests/system/rollover/setup.sh [deleted file]
bin/tests/system/rollover/tests_rollover_manual.py

index a3b71577d987d698255647c72f0fc7c9128d6c17..2c7ebcdc249a551389519979f132286a71404dbd 100644 (file)
@@ -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}"
index 4e27a219d147fb25e416fa55afbcfdb68873d952..f454dca7d03323ffa9132fb9bea34dae7462919f 100644 (file)
@@ -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
index ff2cf2b781032f359eb4698335edfae42b11ded6..23612234e44bfe6b00e5eea00793fbc660093d2a 100644 (file)
@@ -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 (file)
index 0000000..78aa378
--- /dev/null
@@ -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 (file)
index 0000000..29a23bc
--- /dev/null
@@ -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 (file)
index 0000000..e1f7bff
--- /dev/null
@@ -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 (file)
index 0000000..446a918
--- /dev/null
@@ -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 %}
index d1c3a054f1309de1be01e848526f5b6ce2d187fa..813aa919e18fd362da1a32bdfc0541482c8a918c 100644 (file)
  * 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";
 };
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 010b05b3cb37696db709be8c404041f941f9c6e0..3903587ba9f22a11b7671b597657b0bd367230ad 100644 (file)
@@ -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 (symlink)
index 0000000..cb0be77
--- /dev/null
@@ -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 (file)
index 0000000..c924eb4
--- /dev/null
@@ -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 (file)
index f465ddf..0000000
+++ /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
index 75ae7d86c7a806df8a159396db70cf4b2ba5061f..75a705fca1d36c3f4791a29d97acc9a1b5048006 100644 (file)
@@ -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),