Create trust anchors from isctest.kasp.Key

Add isctest.kasp.Key and the minimal methods which are required tp
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.

(manually picked from 0bf20f8d and f6cb154b)
This commit is contained in:
Nicki Křížek 2026-03-10 15:19:31 +01:00
parent e1aff4b8eb
commit 4cca73b037
4 changed files with 118 additions and 0 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

@ -13,6 +13,7 @@ from . import check
from . import instance
from . import hypothesis
from . import query
from . import kasp
from . import run
from . import template
from . import log

View file

@ -0,0 +1,91 @@
# 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 functools import total_ordering
from pathlib import Path
import dns.dnssec
import dns.exception
import dns.message
import dns.name
import dns.rcode
import dns.rdataclass
import dns.rdatatype
import dns.rrset
import dns.tsig
import dns.zone
import dns.zonefile
from isctest.template import TrustAnchor
DEFAULT_TTL = 300
@total_ordering
class Key:
"""
Represent a key from a keyfile.
This object keeps track of its origin (keydir + name), can be used to
retrieve metadata from the underlying files and supports convenience
operations for KASP tests.
"""
def __init__(self, name: str, keydir: str | Path | None = None):
self.name = name
if keydir is None:
self.keydir = Path()
else:
self.keydir = Path(keydir)
self.path = str(self.keydir / name)
self.privatefile = f"{self.path}.private"
self.keyfile = f"{self.path}.key"
self.statefile = f"{self.path}.state"
self.tag = int(self.name[-5:])
self.external = False
@property
def dnskey(self) -> dns.rrset.RRset:
with open(self.keyfile, "r", encoding="utf-8") as file:
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=dns.dnssec.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 __lt__(self, other: "Key"):
return self.name < other.name
def __eq__(self, other: object):
return isinstance(other, Key) and self.path == other.path
def __repr__(self):
return self.path

View file

@ -11,6 +11,7 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
from dataclasses import dataclass
import os
from pathlib import Path
from typing import Any, Dict, Optional, Union
@ -98,3 +99,10 @@ class TemplateEngine:
]
for template in templates:
self.render(template[:-3], data)
@dataclass
class TrustAnchor:
domain: str
type: str
contents: str