diff --git a/bin/tests/system/dnssec_py/ans4/ans.py b/bin/tests/system/dnssec_py/ans4/ans.py new file mode 100644 index 0000000000..47c4c0f87e --- /dev/null +++ b/bin/tests/system/dnssec_py/ans4/ans.py @@ -0,0 +1,65 @@ +# 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. + +""" +Custom authoritative server for the sibling-ds test. + +When returning a referral for child.sibling-ds, this server injects a DS +record for sibling.sibling-ds into the authority section. The resolver +should reject this because the DS owner name does not match the +delegation (NS) name. +""" + +from collections.abc import AsyncGenerator + +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsResponseSend, + DomainHandler, + QueryContext, + ResponseAction, +) + + +class SiblingDsInjectionHandler(DomainHandler): + """Inject a DS record for sibling.sibling-ds into child.sibling-ds referrals.""" + + domains = ["child.sibling-ds."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[ResponseAction, None]: + # The default zone-data response already has the NS delegation for + # child.sibling-ds. and glue. Add a DS record for the *sibling* zone + # (wrong name for this referral). + sibling_ds = dns.rrset.from_text( + "sibling.sibling-ds.", + 300, + qctx.qclass, + dns.rdatatype.DS, + "12345 8 2 " + "49FD46E6C4B45C55D4AC69CBD3CD34AC1AFE51DE7B2B585ABCDEABCDEABCDEAB", + ) + qctx.response.authority.append(sibling_ds) + yield DnsResponseSend(qctx.response) + + +def main() -> None: + server = AsyncDnsServer() + server.install_response_handler(SiblingDsInjectionHandler()) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/dnssec_py/common.py b/bin/tests/system/dnssec_py/common.py index 8a9fbca4d2..2d09eda4da 100644 --- a/bin/tests/system/dnssec_py/common.py +++ b/bin/tests/system/dnssec_py/common.py @@ -13,6 +13,8 @@ import pytest DNSSEC_PY_MARK = pytest.mark.extra_artifacts( [ + "ans*/*.db", + "ans*/*.run", "ns*/dsset-*", "ns*/trusted.conf", "ns*/zones/*.db", diff --git a/bin/tests/system/dnssec_py/tests_sibling_ds.py b/bin/tests/system/dnssec_py/tests_sibling_ds.py new file mode 100644 index 0000000000..71711942d5 --- /dev/null +++ b/bin/tests/system/dnssec_py/tests_sibling_ds.py @@ -0,0 +1,70 @@ +# 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. + +""" +Test that the resolver rejects DS records for sibling zones in referrals. + +A custom authoritative server (ans4) returns a referral for +child.sibling-ds that includes a DS record for sibling.sibling-ds. The +resolver must detect that the DS owner does not match the delegation NS +name and treat the response as a form error. +""" + +from re import compile as Re + +from dnssec_py.common import DNSSEC_PY_MARK +from isctest.template import NS2, Nameserver, zones +from isctest.zone import Zone, configure_root + +import isctest + +pytestmark = DNSSEC_PY_MARK + +ANS4 = Nameserver("ans4") + + +def bootstrap(): + # Child zone on ns2 — the test queries a.child.sibling-ds which + # resolves to the default template A record (10.0.0.1). + child = Zone("child.sibling-ds", NS2) + child.configure() + + # Sibling zone on ns2 — exists so the sibling DS in the referral + # refers to a real delegation. + sibling = Zone("sibling.sibling-ds", NS2) + sibling.configure() + + # Parent zone rendered into ans4/ (subdir=None puts the .db file + # directly in the ans4 directory where AsyncDnsServer loads it). + parent = Zone("sibling-ds", ANS4, subdir=None) + parent.delegations = [child, sibling] + parent.configure() + + # Root zone delegates sibling-ds. to ans4. + root = configure_root([parent]) + + return { + "trust_anchors": root.trust_anchors(), + "zones": zones([root, child, sibling]), + } + + +def test_sibling_ds_rejected(ns9): + """Resolver must reject a referral that contains DS for a sibling zone.""" + log_ds_mismatch = Re(r"DS doesn't match the delegation owner name") + + msg = isctest.query.create("a.child.sibling-ds.", "A") + + with ns9.watch_log_from_here() as watcher: + res = isctest.query.tcp(msg, ns9.ip) + watcher.wait_for_line(log_ds_mismatch) + + isctest.check.servfail(res)