From ca890789cd313e25dc3f567eb996bc9f596de512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Mon, 8 Jun 2026 15:26:02 +0000 Subject: [PATCH] Add NSEC3 answer correctness test to dnssec_py Rewrite nsec3_answer/tests_nsec3.py as dnssec_py/tests_nsec3_answer.py using the isctest.zone helpers for zone setup. ns1 (auth) and ns2 (resolver) were renumbered to ns2 and ns9 respectively to fit the existing dnssec_py server infrastructure. Assisted-by: Claude:claude-opus-4-8 --- .../dnssec_py/ns2/zones/nsec3-answer.db | 47 +++++++++++++++ .../tests_nsec3_answer.py} | 57 +++++++++++++------ .../system/nsec3_answer/ns1/named.conf.j2 | 18 ------ bin/tests/system/nsec3_answer/ns1/root.db.in | 40 ------------- bin/tests/system/nsec3_answer/ns1/sign.sh | 34 ----------- .../system/nsec3_answer/ns2/named.conf.j2 | 26 --------- bin/tests/system/nsec3_answer/setup.sh | 22 ------- 7 files changed, 88 insertions(+), 156 deletions(-) create mode 100644 bin/tests/system/dnssec_py/ns2/zones/nsec3-answer.db rename bin/tests/system/{nsec3_answer/tests_nsec3.py => dnssec_py/tests_nsec3_answer.py} (90%) mode change 100755 => 100644 delete mode 100644 bin/tests/system/nsec3_answer/ns1/named.conf.j2 delete mode 100644 bin/tests/system/nsec3_answer/ns1/root.db.in delete mode 100644 bin/tests/system/nsec3_answer/ns1/sign.sh delete mode 100644 bin/tests/system/nsec3_answer/ns2/named.conf.j2 delete mode 100644 bin/tests/system/nsec3_answer/setup.sh diff --git a/bin/tests/system/dnssec_py/ns2/zones/nsec3-answer.db b/bin/tests/system/dnssec_py/ns2/zones/nsec3-answer.db new file mode 100644 index 0000000000..b5318bebb0 --- /dev/null +++ b/bin/tests/system/dnssec_py/ns2/zones/nsec3-answer.db @@ -0,0 +1,47 @@ +; This zone file intentionally isn't a jinja2 template. +; +; It needs to be read before the templates are rended, to generate hypothesis +; test cases. +$ORIGIN nsec3-answer. +$TTL 300 +nsec3-answer. IN SOA . . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) +) + +nsec3-answer. NS ns2 +ns2 A 10.53.0.2 + +02hc3em7bdd011a0gms3hkkjt2if5vp8 A 10.0.0.0 +a A 10.0.0.1 +*.a.a A 10.0.0.6 +a.a.a.a A 10.0.0.3 +b A 10.0.0.2 +b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b A 10.0.0.2 +cname CNAME does-not-exist +cname.cname CNAME cname +cname.ent.cname CNAME cname.cname +d A 10.0.0.4 +dname-to-nowhere DNAME does-not-exist +; DNAME owner longer than target to avoid YXDOMAIN dependent on QNAME +insecure NS a.root-servers.nil +ns.insecure A 10.53.0.3 +a.root-servers.nil A 10.53.0.1 +secure NS a.root-servers.nil +secure DS 11111 13 255 00 +occluded.secure A 0.0.0.0 +*.wild A 10.0.0.6 +explicit.wild A 192.0.2.66 +z A 10.0.0.26 + +; randomly generated subtree to excercise unknown corner cases +; intentionally small, to not blow up algorithms with quadratic complexity in ZoneAnalyzer and name generator +a.a.a.b.a.a.a.b.a.a.b.b.a.random TXT "r" +b.b.a.a.b.b.a.a.a.b.b.a.b.a.a.a.a.a.b.a.a.b.a.b.a.b.b.b.b.b.a.a.a.a.b.a.a.a.b.a.a.b.b.a.random TXT "r" +a.a.a.b.b.a.b.b.a.b.a.b.a.b.a.b.b.b.a.random TXT "r" +b.b.a.b.a.b.a.a.a.b.a.a.b.a.a.a.a.b.b.a.b.b.a.b.a.b.a.b.a.b.b.b.a.random TXT "r" +a.b.a.a.b.a.b.a.b.a.a.b.a.b.a.a.a.b.b.a.b.b.a.a.b.b.a.a.b.a.b.a.b.b.b.b.a.a.a.a.a.a.a.a.b.a.b.a.b.b.a.b.a.b.a.a.a.b.a.a.b.a.a.a.a.b.b.a.b.b.a.b.a.b.a.b.a.b.b.b.a.random TXT "r" +a.a.a.a.a.b.b.a.a.b.a.a.b.a.a.b.b.a.a.a.b.a.a.a.b.b.b.b.b.a.a.a.b.b.b.b.b.b.a.b.b.b.a.a.b.b.b.b.a.a.a.a.b.a.b.b.a.b.a.a.b.b.b.b.b.b.b.a.b.b.a.b.a.b.a.a.a.b.b.a.a.b.b.a.b.a.b.b.a.b.b.b.a.b.b.b.b.b.a.a.b.a.a.a.b.b.a.a.a.b.b.b.b.b.a.random TXT "r" diff --git a/bin/tests/system/nsec3_answer/tests_nsec3.py b/bin/tests/system/dnssec_py/tests_nsec3_answer.py old mode 100755 new mode 100644 similarity index 90% rename from bin/tests/system/nsec3_answer/tests_nsec3.py rename to bin/tests/system/dnssec_py/tests_nsec3_answer.py index f510f9ec08..53bfbba1eb --- a/bin/tests/system/nsec3_answer/tests_nsec3.py +++ b/bin/tests/system/dnssec_py/tests_nsec3_answer.py @@ -1,5 +1,3 @@ -#!/usr/bin/python3 - # Copyright (C) Internet Systems Consortium, Inc. ("ISC") # # SPDX-License-Identifier: MPL-2.0 @@ -11,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -# Silence incorrect warnings cause by hypothesis.assume() +# Silence incorrect warnings caused by hypothesis.assume() # https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 # pylint: disable=unreachable @@ -20,6 +18,7 @@ from dataclasses import dataclass from pathlib import Path import os +import time from hypothesis import assume, given @@ -34,17 +33,43 @@ import dns.rdtypes.ANY.RRSIG import dns.rrset import pytest +from dnssec_py.common import DNSSEC_PY_MARK from isctest.hypothesis.strategies import dns_names, sampled_from +from isctest.template import NS2, zones +from isctest.zone import Zone, configure_root import isctest import isctest.name -SUFFIX = dns.name.from_text(".") -AUTH = "10.53.0.1" -RESOLVER = "10.53.0.2" +pytestmark = DNSSEC_PY_MARK + + +def bootstrap(): + zone = Zone( + "nsec3-answer", + NS2, + signed=True, + ) + zone.add_keys() + salt = int(time.time()) // 3600 % 65536 + salt_hex = f"{salt:04X}" + isctest.log.info(f"NSEC3 salt for this hour: {salt_hex}") + zone.sign(f"-3 {salt_hex}") + + root = configure_root([zone]) + return { + "trust_anchors": root.trust_anchors(), + "zones": zones([root, zone]), + } + + +SUFFIX = dns.name.from_text("nsec3-answer.") +AUTH = "10.53.0.2" +RESOLVER = "10.53.0.9" TIMEOUT = 5 ZONE = isctest.name.ZoneAnalyzer.read_path( - Path(os.environ["srcdir"]) / "nsec3_answer/ns1/root.db.in", origin=SUFFIX + Path(os.environ["srcdir"]) / "dnssec_py/ns2/zones/nsec3-answer.db", + origin=SUFFIX, ) @@ -71,7 +96,7 @@ def do_test_query( @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given( qname=sampled_from( @@ -84,7 +109,7 @@ def test_nodata(server: str, qname: dns.name.Name, named_port: int) -> None: check_nodata(qname, nsec3check) -@pytest.mark.parametrize("server", [pytest.param(AUTH, id="ns1")]) +@pytest.mark.parametrize("server", [pytest.param(AUTH, id="ns2")]) @given( qname=dns_names( suffix=(ZONE.delegations - ZONE.get_names_with_type(dns.rdatatype.DS)) @@ -116,7 +141,7 @@ def assume_nx_and_no_delegation(qname: dns.name.Name) -> None: assume(qname not in ZONE.all_existing_names) # name must not be under a delegation or DNAME: - # it would not work with resolver ns2 + # it would not work with resolver ns9 assume( not is_related_to_any( qname, @@ -127,7 +152,7 @@ def assume_nx_and_no_delegation(qname: dns.name.Name) -> None: @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given(qname=dns_names(suffix=SUFFIX)) def test_nxdomain(server: str, qname: dns.name.Name, named_port: int) -> None: @@ -141,7 +166,7 @@ def test_nxdomain(server: str, qname: dns.name.Name, named_port: int) -> None: @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given(qname=sampled_from(sorted(ZONE.get_names_with_type(dns.rdatatype.CNAME)))) def test_cname_nxdomain(server: str, qname: dns.name.Name, named_port: int) -> None: @@ -157,7 +182,7 @@ def test_cname_nxdomain(server: str, qname: dns.name.Name, named_port: int) -> N @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given(qname=dns_names(suffix=ZONE.get_names_with_type(dns.rdatatype.DNAME))) def test_dname_nxdomain(server: str, qname: dns.name.Name, named_port: int) -> None: @@ -175,7 +200,7 @@ def test_dname_nxdomain(server: str, qname: dns.name.Name, named_port: int) -> N @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given(qname=dns_names(suffix=ZONE.ents)) def test_ents(server: str, qname: dns.name.Name, named_port: int) -> None: @@ -193,7 +218,7 @@ def test_ents(server: str, qname: dns.name.Name, named_port: int) -> None: @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given(qname=dns_names(suffix=ZONE.reachable_wildcard_parents)) def test_wildcard_synthesis(server: str, qname: dns.name.Name, named_port: int) -> None: @@ -207,7 +232,7 @@ def test_wildcard_synthesis(server: str, qname: dns.name.Name, named_port: int) @pytest.mark.parametrize( - "server", [pytest.param(AUTH, id="ns1"), pytest.param(RESOLVER, id="ns2")] + "server", [pytest.param(AUTH, id="ns2"), pytest.param(RESOLVER, id="ns9")] ) @given(qname=dns_names(suffix=ZONE.reachable_wildcard_parents)) def test_wildcard_nodata(server: str, qname: dns.name.Name, named_port: int) -> None: diff --git a/bin/tests/system/nsec3_answer/ns1/named.conf.j2 b/bin/tests/system/nsec3_answer/ns1/named.conf.j2 deleted file mode 100644 index acf4ae910d..0000000000 --- a/bin/tests/system/nsec3_answer/ns1/named.conf.j2 +++ /dev/null @@ -1,18 +0,0 @@ -// 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; - dnssec-validation no; -}; - -zone "." { - type primary; - file "root.db.signed"; -}; diff --git a/bin/tests/system/nsec3_answer/ns1/root.db.in b/bin/tests/system/nsec3_answer/ns1/root.db.in deleted file mode 100644 index c3b0c84ba0..0000000000 --- a/bin/tests/system/nsec3_answer/ns1/root.db.in +++ /dev/null @@ -1,40 +0,0 @@ -$TTL 300 -. IN SOA . . ( - 2025063000 ; serial - 600 ; refresh - 600 ; retry - 1200 ; expire - 600 ; minimum - ) -. NS a.root-servers.nil. - -02hc3em7bdd011a0gms3hkkjt2if5vp8. A 10.0.0.0 -a. A 10.0.0.1 -*.a.a. A 10.0.0.6 -a.a.a.a. A 10.0.0.3 -b. A 10.0.0.2 -b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b. A 10.0.0.2 -cname. CNAME does-not-exist. -cname.cname. CNAME cname. -cname.ent.cname. CNAME cname.cname. -d. A 10.0.0.4 -dname-to-nowhere. DNAME does-not-exist. -; DNAME owner longer than target to avoid YXDOMAIN dependent on QNAME -insecure. NS a.root-servers.nil. -ns.insecure. A 10.53.0.3 -a.root-servers.nil. A 10.53.0.1 -secure. NS a.root-servers.nil. -secure. DS 11111 13 255 00 -occluded.secure. A 0.0.0.0 -*.wild. A 10.0.0.6 -explicit.wild. A 192.0.2.66 -z. A 10.0.0.26 - -; randomly generated subtree to excercise unknown corner cases -; intentionally small, to not blow up algorithms with quadratic complexity in ZoneAnalyzer and name generator -a.a.a.b.a.a.a.b.a.a.b.b.a.random. TXT "r" -b.b.a.a.b.b.a.a.a.b.b.a.b.a.a.a.a.a.b.a.a.b.a.b.a.b.b.b.b.b.a.a.a.a.b.a.a.a.b.a.a.b.b.a.random. TXT "r" -a.a.a.b.b.a.b.b.a.b.a.b.a.b.a.b.b.b.a.random. TXT "r" -b.b.a.b.a.b.a.a.a.b.a.a.b.a.a.a.a.b.b.a.b.b.a.b.a.b.a.b.a.b.b.b.a.random. TXT "r" -a.b.a.a.b.a.b.a.b.a.a.b.a.b.a.a.a.b.b.a.b.b.a.a.b.b.a.a.b.a.b.a.b.b.b.b.a.a.a.a.a.a.a.a.b.a.b.a.b.b.a.b.a.b.a.a.a.b.a.a.b.a.a.a.a.b.b.a.b.b.a.b.a.b.a.b.a.b.b.b.a.random. TXT "r" -a.a.a.a.a.b.b.a.a.a.a.a.b.b.a.a.b.a.a.b.a.a.b.b.a.a.a.b.a.a.a.b.b.b.b.b.a.a.a.b.b.b.b.b.b.a.b.b.b.a.a.b.b.b.b.a.a.a.a.b.a.b.b.a.b.a.a.b.b.b.b.b.b.b.a.b.b.a.b.a.b.a.a.a.b.b.a.a.b.b.a.b.a.b.b.a.b.b.b.a.b.b.b.b.b.a.a.b.a.a.a.b.b.a.a.a.b.b.b.b.b.a.random. TXT "r" diff --git a/bin/tests/system/nsec3_answer/ns1/sign.sh b/bin/tests/system/nsec3_answer/ns1/sign.sh deleted file mode 100644 index 78e33119f6..0000000000 --- a/bin/tests/system/nsec3_answer/ns1/sign.sh +++ /dev/null @@ -1,34 +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 - -set -e - -zone=. -infile=root.db.in -zonefile=root.db - -echo_i "ns1/sign.sh" - -ksk=$("$KEYGEN" -q -fk -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") -zsk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" "$zone") - -cat "$infile" "$ksk.key" "$zsk.key" >"$zonefile" - -SALT="$(printf "%04x" "$(($(date +%s) / 3600 % 65536))")" -echo_ic "NSEC3 salt for this hour: $SALT" -"$SIGNER" -3 "$SALT" -o "$zone" "$zonefile" 2>&1 >"$zonefile.sign.log" - -keyfile_to_initial_ds "$ksk" >managed-keys.conf diff --git a/bin/tests/system/nsec3_answer/ns2/named.conf.j2 b/bin/tests/system/nsec3_answer/ns2/named.conf.j2 deleted file mode 100644 index 174bd7dd9f..0000000000 --- a/bin/tests/system/nsec3_answer/ns2/named.conf.j2 +++ /dev/null @@ -1,26 +0,0 @@ -// validating resolver - -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; }; - recursion yes; - dnssec-validation yes; -}; - -controls { - inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; -}; - -include "../../_common/rndc.key"; - -zone "." { - type hint; - file "../../_common/root.hint"; -}; - -include "../ns1/managed-keys.conf"; diff --git a/bin/tests/system/nsec3_answer/setup.sh b/bin/tests/system/nsec3_answer/setup.sh deleted file mode 100644 index 4a4db2dd0d..0000000000 --- a/bin/tests/system/nsec3_answer/setup.sh +++ /dev/null @@ -1,22 +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 - -set -e - -( - cd ns1 - $SHELL sign.sh -)