]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add extra expired RRSIGs test to dnssec_py 11808/head
authorNicki Křížek <nicki@isc.org>
Fri, 3 Apr 2026 13:57:01 +0000 (15:57 +0200)
committerOndřej Surý <ondrej@isc.org>
Wed, 24 Jun 2026 13:09:38 +0000 (15:09 +0200)
The test verifies that a validating resolver enforces the
max-validations-per-fetch limit when encountering a record with multiple
expired RRSIGs followed by a valid one. One of the records is signed
three times: twice with expired timestamps (to produce two expired
RRSIGs for a.rrsigs-extra-expired/A) and once with valid timestamps,
after which the expired RRSIGs are injected back into the signed zone
file. max-validations-per-fetch is set to 2 via the template variable so
that the third (valid) RRSIG is never reached, causing SERVFAIL.

Assisted-by: Claude:claude-opus-4-8
bin/tests/system/dnssec_py/tests_rrsigs_extra_expired.py [new file with mode: 0644]

diff --git a/bin/tests/system/dnssec_py/tests_rrsigs_extra_expired.py b/bin/tests/system/dnssec_py/tests_rrsigs_extra_expired.py
new file mode 100644 (file)
index 0000000..7df270c
--- /dev/null
@@ -0,0 +1,107 @@
+# 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.
+
+from pathlib import Path
+from re import compile as Re
+
+import time
+
+import dns.name
+import dns.rdataclass
+import dns.rdatatype
+import dns.zone
+import pytest
+
+from dnssec_py.common import DNSSEC_PY_MARK
+from isctest.template import NS2, zones
+from isctest.zone import Zone, configure_root
+
+import isctest
+
+pytestmark = DNSSEC_PY_MARK
+
+
+def bootstrap():
+    zone = Zone("rrsigs-extra-expired", NS2, signed=True)
+    zone.add_keys()
+    zone.render()
+
+    signed_path = Path(zone.ns.name) / zone.filepath_signed
+
+    # create valid but expired signatures
+    expired_rdata = set()
+    now = int(time.time())
+    start = now - 20000
+    end = now - 10000
+    for i in range(2):
+        zone.sign(f"-s {start - i} -e {end - i}")
+        expired = dns.zone.from_file(str(signed_path), origin="rrsigs-extra-expired.")
+        rdataset = expired.get_rdataset("a", "RRSIG", "A")
+        expired_rdata.add(rdataset.pop())
+
+    # sign zone with valid sigs
+    zone.sign()
+    valid = dns.zone.from_file(str(signed_path), origin="rrsigs-extra-expired.")
+    rdataset = valid.find_rdataset("a", "RRSIG", "A")
+
+    # add the expired RRSIGs for a.rrsigs-extra-expired
+    for rd in expired_rdata:
+        rdataset.add(rd)
+    valid.to_file(str(signed_path))
+
+    root = configure_root([zone])
+    return {
+        "max_validations_per_fetch": 2,
+        "rrset_order_none": [zone.name],
+        "trust_anchors": root.trust_anchors(),
+        "zones": zones([root, zone]),
+    }
+
+
+@pytest.fixture(scope="module", autouse=True)
+def after_servers_start(ns2):
+    msg = isctest.query.create("a.rrsigs-extra-expired", "A")
+
+    # Check the order of returned RRSIGs from auth. Due to rrset-order none;
+    # this should remain constant for the remainder of the test.
+    # Ensure the first two RRSIGs are expired, otherwise skip the test.
+    res = isctest.query.tcp(msg, ns2.ip)
+    rrsigs = res.get_rrset(
+        res.answer,
+        dns.name.from_text("a.rrsigs-extra-expired."),
+        dns.rdataclass.IN,
+        dns.rdatatype.RRSIG,
+        dns.rdatatype.A,
+    )
+    now = time.time()
+    assert len(rrsigs) > 2
+    if rrsigs[0].expiration >= now or rrsigs[1].expiration >= now:
+        pytest.skip("valid RRSIG listed first in response, re-run test")
+
+
+def test_regular_query(ns9):
+    # sanity check - record with no extra sigs gets NOERROR
+    msg = isctest.query.create("b.rrsigs-extra-expired", "A")
+    res = isctest.query.tcp(msg, ns9.ip)
+    isctest.check.noerror(res)
+
+
+def test_extra_expired_rrsigs(ns9):
+    msg = isctest.query.create("a.rrsigs-extra-expired", "A")
+    with ns9.watch_log_from_here() as watcher:
+        res = isctest.query.tcp(msg, ns9.ip)
+        watcher.wait_for_sequence(
+            [
+                Re(r"a.rrsigs-extra-expired/A: verify failed.* RRSIG has expired"),
+                Re(r"a.rrsigs-extra-expired/A: maximum number of validations exceeded"),
+            ]
+        )
+    isctest.check.servfail(res)