chg: test: Add isctest.kasp.Key.SettimeOptions

Merge branch 'matthijs-pytest-settime' into 'main'

See merge request isc-projects/bind9!11388
This commit is contained in:
Matthijs Mekking 2025-12-22 09:06:14 +00:00
commit ab9899455b
6 changed files with 895 additions and 426 deletions

View file

@ -21,6 +21,7 @@ from dns import dnssec, name, rdataclass, rdatatype, update
import pytest
pytest.importorskip("dns", minversion="2.0.0")
from isctest.kasp import SettimeOptions
import isctest
@ -60,10 +61,9 @@ def keygen(*args):
# run dnssec-settime
def settime(*args):
settime_cmd = [os.environ.get("SETTIME")]
settime_cmd.extend(args)
return isctest.run.cmd(settime_cmd).out.strip()
def setkeytimes(key_name: str, options: SettimeOptions, keydir=None):
key = isctest.kasp.Key(key_name, keydir=keydir)
key.settime(options)
@pytest.mark.parametrize(
@ -482,7 +482,8 @@ def test_offline_ksk_signing(ns2):
# set key state for KSK. the ZSK rollovers below assume that there is a
# chain of trust established, so we tell named that the DS is in
# omnipresent state.
settime("-s", "-d", "OMNIPRESENT", "now", "-Kns2", KSK)
timings = SettimeOptions(d="OMNIPRESENT now")
setkeytimes(KSK, timings, keydir="ns2")
isctest.log.info("check state before KSK is made offline")
isctest.log.info("make sure certain types are signed with KSK only")
@ -509,8 +510,15 @@ def test_offline_ksk_signing(ns2):
isctest.run.retry_with_timeout(check_zskcount, 5)
isctest.log.info("make the new ZSK active")
settime("-sKns2", "-Inow", ZSK)
settime("-sKns2", "-Anow", "-k", "OMNIPRESENT", "now", ZSK2)
timings = SettimeOptions(I="now")
setkeytimes(ZSK, timings, keydir="ns2")
timings = SettimeOptions(
A="now",
k="OMNIPRESENT now",
)
setkeytimes(ZSK2, timings, keydir="ns2")
loadkeys()
with ns2.watch_log_from_start() as watcher:
@ -557,8 +565,19 @@ def test_offline_ksk_signing(ns2):
ZSKID3 = getkeyid(ZSK3)
isctest.log.info("delete old ZSK, schedule ZSK2 inactive, pre-publish ZSK3")
settime("-sKns2", "-k", "HIDDEN", "now", "-z", "HIDDEN", "now", "-Dnow", ZSK)
settime("-sKns2", "-k", "OMNIPRESENT", "now", "-z", "OMNIPRESENT", "now", ZSK2)
timings = SettimeOptions(
k="HIDDEN now",
z="HIDDEN now",
D="now",
)
setkeytimes(ZSK, timings, keydir="ns2")
timings = SettimeOptions(
k="OMNIPRESENT now",
z="OMNIPRESENT now",
)
setkeytimes(ZSK2, timings, keydir="ns2")
loadkeys()
ns2.rndc(f"dnssec -rollover -key {ZSKID2} {zone}")
@ -591,8 +610,15 @@ def test_offline_ksk_signing(ns2):
ksk_recover()
isctest.log.info("make ZSK3 active")
settime("-sKns2", "-Inow", ZSK2)
settime("-sKns2", "-k", "OMNIPRESENT", "now", "-Anow", ZSK3)
timings = SettimeOptions(I="now")
setkeytimes(ZSK2, timings, keydir="ns2")
timings = SettimeOptions(
k="OMNIPRESENT now",
A="now",
)
setkeytimes(ZSK3, timings, keydir="ns2")
loadkeys()
with ns2.watch_log_from_start() as watcher:

View file

@ -9,6 +9,7 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from functools import total_ordering
import glob
@ -33,6 +34,7 @@ import isctest.util
from isctest.compat import DSDigest
from isctest.instance import NamedInstance
from isctest.template import TrustAnchor
from isctest.run import EnvCmd
from isctest.vars.algorithms import Algorithm, ALL_ALGORITHMS_BY_NUM
DEFAULT_TTL = 300
@ -315,6 +317,63 @@ class KeyProperties:
self.timing["ZRRSIGChange"] = None
@dataclass
class SettimeOptions:
P: Optional[str] = None
"""-P date/[+-]offset/none: set/unset key publication date"""
P_ds: Optional[str] = None
"""-P ds date/[+-]offset/none: set/unset DS publication date"""
P_sync: Optional[str] = None
"""-P sync date/[+-]offset/none: set/unset CDS and CDNSKEY publication date"""
A: Optional[str] = None
"""-A date/[+-]offset/none: set/unset key activation date"""
R: Optional[str] = None
"""-R date/[+-]offset/none: set/unset key revocation date"""
I: Optional[str] = None
"""-I date/[+-]offset/none: set/unset key inactivation date"""
D: Optional[str] = None
"""-D date/[+-]offset/none: set/unset key deletion date"""
D_ds: Optional[str] = None
"""-D ds date/[+-]offset/none: set/unset DS deletion date"""
D_sync: Optional[str] = None
"""-D sync date/[+-]offset/none: set/unset CDS and CDNSKEY deletion date"""
g: Optional[str] = None
"""-g state: set the goal state for this key"""
d: Optional[str] = None
"""-d state date/[+-]offset: set the DS state"""
k: Optional[str] = None
"""-k state date/[+-]offset: set the DNSKEY state"""
r: Optional[str] = None
"""-r state date/[+-]offset: set the RRSIG (KSK) state"""
z: Optional[str] = None
"""-z state date/[+-]offset: set the RRSIG (ZSK) state"""
def __str__(self):
args = []
for opt, value in self.__dict__.items():
if value is None:
continue
if not isinstance(value, str):
raise ValueError(f"{opt}: invalid option value, only string supported")
opt_str = opt.replace("_", " ")
args.append(f"-{opt_str} {value}")
return " ".join(args)
@total_ordering
class Key:
"""
@ -700,6 +759,14 @@ class Key:
return True
def settime(self, options: SettimeOptions, with_state=True):
if with_state:
settime_cmd = EnvCmd("SETTIME", "-s")
else:
settime_cmd = EnvCmd("SETTIME")
settime_cmd(f"{options} {self.path}")
def __lt__(self, other: "Key"):
return self.name < other.name

View file

@ -26,6 +26,7 @@ import isctest.mark
from isctest.kasp import (
KeyProperties,
KeyTimingMetadata,
SettimeOptions,
)
from isctest.util import param
from isctest.vars.algorithms import ECDSAP256SHA256, ECDSAP384SHA384
@ -1322,13 +1323,8 @@ def test_kasp_dnssec_keygen():
created = key.get_timing("Created")
publish = key.get_timing("Publish") + timedelta(hours=1)
settime = [
os.environ.get("SETTIME"),
"-P",
str(publish),
key.path,
]
isctest.run.cmd(settime)
timings = SettimeOptions(P=f"{publish}")
key.settime(timings, with_state=False)
isctest.check.file_contents_equal(f"{key.statefile}", f"{key.statefile}.backup")
assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
@ -1358,28 +1354,16 @@ def test_kasp_dnssec_keygen():
"DSChange": now,
}
settime = [
os.environ.get("SETTIME"),
"-s",
"-P",
str(now),
"-g",
goal,
"-k",
dnskey,
str(now),
"-r",
krrsig,
str(now),
"-z",
zrrsig,
str(now),
"-d",
ds,
str(now),
key.path,
]
isctest.run.cmd(settime)
timings = SettimeOptions(
P=f"{now}",
g=f"{goal}",
k=f"{dnskey} {now}",
r=f"{krrsig} {now}",
z=f"{zrrsig} {now}",
d=f"{ds} {now}",
)
key.settime(timings)
isctest.kasp.check_keys("kasp", keys, expected)
isctest.kasp.check_keytimes(keys, expected)
@ -1395,28 +1379,16 @@ def test_kasp_dnssec_keygen():
"Active": created,
}
settime = [
os.environ.get("SETTIME"),
"-s",
"-P",
"none",
"-g",
"none",
"-k",
"none",
str(now),
"-z",
"none",
str(now),
"-r",
"none",
str(now),
"-d",
"none",
str(now),
key.path,
]
isctest.run.cmd(settime)
timings = SettimeOptions(
P="none",
g="none",
k=f"none {now}",
r=f"none {now}",
z=f"none {now}",
d=f"none {now}",
)
key.settime(timings)
isctest.kasp.check_keys("kasp", keys, expected)
isctest.kasp.check_keytimes(keys, expected)
@ -1443,28 +1415,16 @@ def test_kasp_dnssec_keygen():
"DSChange": soon,
}
settime = [
os.environ.get("SETTIME"),
"-s",
"-A",
str(soon),
"-g",
"HIDDEN",
"-k",
"UNRETENTIVE",
str(soon),
"-z",
"UNRETENTIVE",
str(soon),
"-r",
"OMNIPRESENT",
str(soon),
"-d",
"OMNIPRESENT",
str(soon),
key.path,
]
isctest.run.cmd(settime)
timings = SettimeOptions(
A=f"{soon}",
g="HIDDEN",
k=f"UNRETENTIVE {soon}",
z=f"UNRETENTIVE {soon}",
r=f"OMNIPRESENT {soon}",
d=f"OMNIPRESENT {soon}",
)
key.settime(timings)
isctest.kasp.check_keys("kasp", keys, expected)
isctest.kasp.check_keytimes(keys, expected)

View file

@ -20,14 +20,14 @@ pytest.importorskip("dns", minversion="2.0.0")
import dns.update
import isctest
from isctest.kasp import Iret
from isctest.kasp import Iret, SettimeOptions
from isctest.run import EnvCmd
from rollover.common import (
pytestmark,
alg,
size,
)
from rollover.setup import fake_lifetime, render_and_sign_zone
from rollover.setup import fake_lifetime, render_and_sign_zone, setkeytimes
def bootstrap():
@ -35,7 +35,6 @@ def bootstrap():
# Multi-signer zones.
keygen = EnvCmd("KEYGEN", "-a ECDSA256 -L 3600")
settime = EnvCmd("SETTIME", "-s")
# Model 2.
zonename = "multisigner-model2.kasp"
@ -77,14 +76,21 @@ def bootstrap():
f"-M 0:32767 -f KSK {keytimes} {cdstimes} {zonename}", cwd="ns3"
).out.strip()
zsk_name = keygen(f"-M 0:32767 {keytimes} {zonename}", cwd="ns3").out.strip()
settime(
f"-g OMNIPRESENT -d OMNIPRESENT {TpubN} -k OMNIPRESENT {TpubN} -r OMNIPRESENT {TpubN} {ksk_name}",
cwd="ns3",
# Key state timing metadata.
timings = SettimeOptions(
g="OMNIPRESENT",
k=f"OMNIPRESENT {TpubN}",
r=f"OMNIPRESENT {TpubN}",
d=f"OMNIPRESENT {TpubN}",
)
settime(
f"-g OMNIPRESENT -k OMNIPRESENT {TpubN} -z OMNIPRESENT {TpubN} {zsk_name}",
cwd="ns3",
setkeytimes(ksk_name, timings)
timings = SettimeOptions(
g="OMNIPRESENT",
k=f"OMNIPRESENT {TpubN}",
z=f"OMNIPRESENT {TpubN}",
)
setkeytimes(zsk_name, timings)
# Signing.
fake_lifetime(ksk_name, 0)
fake_lifetime(zsk_name, 0)

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,13 @@ from datetime import timedelta
import os
import isctest
from isctest.kasp import KeyTimingMetadata, Ipub, Iret, private_type_record
from isctest.kasp import (
KeyTimingMetadata,
Ipub,
Iret,
private_type_record,
SettimeOptions,
)
from isctest.template import Nameserver, Zone
from isctest.run import EnvCmd
@ -21,15 +27,15 @@ from rollover.common import default_algorithm
from rollover.setup import (
configure_root,
configure_tld,
setkeytimes,
)
def setup_zone(zone, ksk_time, ksk_settime, zsk_time, zsk_settime) -> Zone:
def setup_zone(zone, ksk_time, ksk_timings, zsk_time, zsk_timings) -> Zone:
templates = isctest.template.TemplateEngine(".")
alg = default_algorithm()
keygen = EnvCmd("KEYGEN", f"-q -a {alg.number} -b {alg.bits} -L 3600")
signer = EnvCmd("SIGNER", "-S -g")
settime = EnvCmd("SETTIME", "-s")
isctest.log.info(f"setup {zone}")
template = "template.db.j2.manual"
@ -44,8 +50,9 @@ def setup_zone(zone, ksk_time, ksk_settime, zsk_time, zsk_settime) -> Zone:
f"-f KSK -P {ksk_time} -A {ksk_time} {zone}", cwd="ns3"
).out.strip()
zsk_name = keygen(f"-P {zsk_time} -A {zsk_time} {zone}", cwd="ns3").out.strip()
settime(f"{ksk_settime} {ksk_name}", cwd="ns3")
settime(f"{zsk_settime} {zsk_name}", cwd="ns3")
# Key state timing metadata.
setkeytimes(ksk_name, ksk_timings)
setkeytimes(zsk_name, zsk_timings)
# Signing.
ksk = isctest.kasp.Key(ksk_name, keydir="ns3")
zsk = isctest.kasp.Key(zsk_name, keydir="ns3")
@ -70,15 +77,27 @@ def bootstrap():
zone = "manual-rollover.kasp"
when = "now-7d"
ksk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {when} -r OMNIPRESENT {when} -d OMNIPRESENT {when}"
zsk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {when} -z OMNIPRESENT {when}"
zones.append(setup_zone(zone, when, ksk_settime, when, zsk_settime))
ksk_timings = SettimeOptions(
g="OMNIPRESENT",
k=f"OMNIPRESENT {when}",
r=f"OMNIPRESENT {when}",
d=f"OMNIPRESENT {when}",
)
zsk_timings = SettimeOptions(
g="OMNIPRESENT",
k=f"OMNIPRESENT {when}",
z=f"OMNIPRESENT {when}",
)
zones.append(setup_zone(zone, when, ksk_timings, when, zsk_timings))
zone = "manual-rollover-zrrsig-rumoured.kasp"
then = "now-2h"
ksk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {when} -r OMNIPRESENT {when} -d OMNIPRESENT {when}"
zsk_settime = f"-g OMNIPRESENT -k OMNIPRESENT {then} -z RUMOURED {then}"
zones.append(setup_zone(zone, when, ksk_settime, then, zsk_settime))
zsk_timings = SettimeOptions(
g="OMNIPRESENT",
k=f"OMNIPRESENT {then}",
z=f"RUMOURED {then}",
)
zones.append(setup_zone(zone, when, ksk_timings, then, zsk_timings))
# Chain of trust.
data = {