new: test: Create trust anchors from isctest.kasp.Key

Add isctest.kasp.Key.into_ta() method which convert the key into DS /
DNSKEY trust anchor for BIND config. Add a shared template
trusted.conf.j2 which can be linked to in tests to create the trust
anchor configuration from trust anchor data returned from bootstrap()
function.

This is basically a python replacement for the keyfile_to_static_ds (and
friends) from the conf.sh shell framework.

Merge branch 'nicki/pytest-add-trust-anchor-template' into 'main'

See merge request isc-projects/bind9!11201
This commit is contained in:
Nicki Křížek 2025-11-27 14:49:01 +01:00
commit d2777a6e78
5 changed files with 80 additions and 12 deletions

View file

@ -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 %}
};

View file

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

View file

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

View file

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

View file

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