Add extra expired RRSIGs test to dnssec_py

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
This commit is contained in:
Nicki Křížek 2026-04-03 15:57:01 +02:00 committed by Ondřej Surý
parent c2723d9162
commit fe2fea73a4

View file

@ -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)