diff --git a/bin/tests/system/_common/trusted.conf.j2 b/bin/tests/system/_common/trusted.conf.j2 new file mode 100644 index 0000000000..fef3a774e7 --- /dev/null +++ b/bin/tests/system/_common/trusted.conf.j2 @@ -0,0 +1,18 @@ +/* + * 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. + */ + +trust-anchors { +{% for ta in trust_anchors %} + "@ta.domain@" @ta.type@ @ta.contents@; +{% endfor %} +}; diff --git a/bin/tests/system/isctest/compat.py b/bin/tests/system/isctest/compat.py index 3dc5810745..ae6b157c0d 100644 --- a/bin/tests/system/isctest/compat.py +++ b/bin/tests/system/isctest/compat.py @@ -54,3 +54,17 @@ else: class EDEOption: def __new__(cls, *args, **kwargs): raise RuntimeError("Using EDEOption requires dnspython>=2.2.0") + + +# pylint: disable=unused-import +try: + from dns.dnssec import DSDigest +except ImportError: # dnspython<2.0.0 + import enum + + class DSDigest(enum.IntEnum): # type: ignore + """DNSSEC Delgation Signer Digest Algorithm""" + + SHA1 = 1 + SHA256 = 2 + SHA384 = 4 diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 604a4f8669..486b206315 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -20,12 +20,19 @@ import time from typing import Dict, List, Optional, Tuple, Union import dns +import dns.dnssec +import dns.rdatatype +import dns.rrset import dns.tsig +import pytest + import isctest.log import isctest.query import isctest.util +from isctest.compat import DSDigest from isctest.instance import NamedInstance +from isctest.template import TrustAnchor from isctest.vars.algorithms import Algorithm, ALL_ALGORITHMS_BY_NUM DEFAULT_TTL = 300 @@ -443,12 +450,35 @@ class Key: return int(line.split()[1]) return 0 - def dnskey(self): + @property + def dnskey(self) -> dns.rrset.RRset: + pytest.importorskip("dns", minversion="2.2.0") # dns.zonefile.read_rrsets with open(self.keyfile, "r", encoding="utf-8") as file: - for line in file: - if "DNSKEY" in line: - return line.strip() - return "undefined" + rrsets = dns.zonefile.read_rrsets( + file.read(), + rdclass=None, # read rdclass from the file + default_ttl=DEFAULT_TTL, # use this TTL if not present + ) + assert len(rrsets) == 1, f"{self.keyfile} has multiple RRsets" + dnskey_rr = rrsets[0] + assert len(dnskey_rr) == 1, f"{self.keyfile} has multiple RRs" + assert ( + dnskey_rr.rdtype == dns.rdatatype.DNSKEY + ), f"DNSKEY not found in {self.keyfile}" + return dnskey_rr + + def into_ta(self, ta_type: str, dsdigest=DSDigest.SHA256) -> TrustAnchor: + dnskey = self.dnskey + if ta_type in ["static-ds", "initial-ds"]: + ds = dns.dnssec.make_ds(dnskey.name, dnskey[0], dsdigest) + parts = str(ds).split() + contents = " ".join(parts[:3]) + f' "{parts[3]}"' + elif ta_type in ["static-key", "initial-key"]: + parts = str(dnskey).split() + contents = " ".join(parts[4:7]) + f' "{"".join(parts[7:])}"' + else: + raise ValueError(f"invalid trust anchor type: {ta_type}") + return TrustAnchor(str(dnskey.name), ta_type, contents) def is_ksk(self) -> bool: return self.get_metadata("KSK") == "yes" diff --git a/bin/tests/system/isctest/template.py b/bin/tests/system/isctest/template.py index 7ac7c8b4f0..4e27a219d1 100644 --- a/bin/tests/system/isctest/template.py +++ b/bin/tests/system/isctest/template.py @@ -11,6 +11,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +from dataclasses import dataclass from pathlib import Path from typing import Any, Dict, Optional, Union @@ -79,3 +80,10 @@ class TemplateEngine: ] for template in templates: self.render(template[:-3], data) + + +@dataclass +class TrustAnchor: + domain: str + type: str + contents: str diff --git a/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py b/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py index 6b5a624403..7aad2d98cd 100644 --- a/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py +++ b/bin/tests/system/rollover-multisigner/tests_rollover_multisigner.py @@ -102,11 +102,10 @@ def test_rollover_multisigner(ns3, alg, size): expected2[0].legacy = True # noqa expected = expected + expected2 - dnskey = newkeys[0].dnskey().split() - rdata = " ".join(dnskey[4:]) + dnskey = newkeys[0].dnskey update_msg = dns.update.UpdateMessage(zone) - update_msg.add(f"{dnskey[0]}", 3600, "DNSKEY", rdata) + update_msg.add(dnskey.name, dnskey.ttl, dnskey[0]) ns3.nsupdate(update_msg) isctest.kasp.check_dnssec_verify(ns3, zone) @@ -118,11 +117,10 @@ def test_rollover_multisigner(ns3, alg, size): isctest.kasp.check_subdomain(ns3, zone, ksks, zsks) # Remove ZSKs from the other providers for zone. - dnskey2 = extkeys[0].dnskey().split() - rdata2 = " ".join(dnskey2[4:]) + dnskey2 = extkeys[0].dnskey update_msg = dns.update.UpdateMessage(zone) - update_msg.delete(f"{dnskey[0]}", "DNSKEY", rdata) - update_msg.delete(f"{dnskey2[0]}", "DNSKEY", rdata2) + update_msg.delete(dnskey.name, dnskey[0]) + update_msg.delete(dnskey2.name, dnskey2[0]) ns3.nsupdate(update_msg) isctest.kasp.check_dnssec_verify(ns3, zone)