rollover-algo-csk: From setup.sh to pytest bootstrap

Symlink ns1 and ns2 to rollover/ns1 and rollover/ns2.
Symlink ns3/template.db.j2.manual to rollover/ns3/template.db.j2.manual.

Since the bootstrapping is done before the templates are rendered
automatically, replace @DEFAULT_ALGORITHM@ in ns3/csk2.conf.j2 to
ecdsa256 and rename to ns3/csk2.conf.

(cherry picked from commit 3a6ed195fa)
This commit is contained in:
Matthijs Mekking 2025-11-27 10:37:22 +01:00
parent ef1ac155b0
commit 7732f07a63
11 changed files with 246 additions and 159 deletions

View file

@ -0,0 +1 @@
../rollover/ns1

View file

@ -0,0 +1 @@
../rollover/ns2

View file

@ -17,7 +17,7 @@ dnssec-policy "csk-algoroll-kasp" {
signatures-validity-dnskey 30d;
keys {
csk lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
csk lifetime unlimited algorithm ecdsa256;
};
dnskey-ttl 1h;
@ -37,7 +37,7 @@ dnssec-policy "csk-algoroll-manual" {
signatures-validity-dnskey 30d;
keys {
csk lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
csk lifetime unlimited algorithm ecdsa256;
};
dnskey-ttl 1h;

View file

@ -1 +0,0 @@
../../rollover/ns3/template.db.in

View file

@ -0,0 +1 @@
../../rollover/ns3/template.db.j2.manual

View file

@ -0,0 +1 @@
../../rollover/ns3/trusted.conf.j2

View file

@ -1,152 +0,0 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../conf.sh
cd "ns3"
setup() {
zone="$1"
echo_i "setting up zone: $zone"
zonefile="${zone}.db"
infile="${zone}.db.infile"
}
# Make lines shorter by storing key states in environment variables.
H="HIDDEN"
R="RUMOURED"
O="OMNIPRESENT"
U="UNRETENTIVE"
#
# The zones at csk-algorithm-roll.$tld represent the various steps of a CSK
# algorithm rollover.
#
for tld in kasp manual; do
# Step 1:
# Introduce the first key. This will immediately be active.
setup step1.csk-algorithm-roll.$tld
echo "$zone" >>zones
TactN="now-7d"
TsbmN="now-161h"
csktimes="-P ${TactN} -A ${TactN}"
CSK=$($KEYGEN -k csk-algoroll-$tld -l csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
$SETTIME -s -g $O -k $O $TactN -r $O $TactN -z $O $TactN -d $O $TactN "$CSK" >settime.out.$zone.1 2>&1
cat template.db.in "${CSK}.key" >"$infile"
private_type_record $zone 5 "$CSK" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 2:
# After the publication interval has passed the DNSKEY is OMNIPRESENT.
setup step2.csk-algorithm-roll.$tld
# The time passed since the new algorithm keys have been introduced is 3 hours.
TpubN1="now-3h"
# Tsbm(N+1) = TpubN1 + Ipub = now + TTLsig + Dprp = now - 3h + 6h + 1h = now + 4h
TsbmN1="now+4h"
csktimes="-P ${TactN} -A ${TactN} -P sync ${TsbmN} -I now"
newtimes="-P ${TpubN1} -A ${TpubN1}"
CSK1=$($KEYGEN -k csk-algoroll-$tld -l csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
CSK2=$($KEYGEN -k csk-algoroll-$tld -l csk2.conf $newtimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $O $TactN -r $O $TactN -z $O $TactN -d $O $TactN "$CSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $R $TpubN1 -r $R $TpubN1 -z $R $TpubN1 -d $H $TpubN1 "$CSK2" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${CSK1}.state"
cat template.db.in "${CSK1}.key" "${CSK2}.key" >"$infile"
private_type_record $zone 5 "$CSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK2" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 3:
# The zone signatures are also OMNIPRESENT.
setup step3.csk-algorithm-roll.$tld
# The time passed since the new algorithm keys have been introduced is 7 hours.
TpubN1="now-7h"
TsbmN1="now"
ckstimes="-P ${TactN} -A ${TactN} -P sync ${TsbmN} -I ${TsbmN1}"
newtimes="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
CSK1=$($KEYGEN -k csk-algoroll-$tld -l csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
CSK2=$($KEYGEN -k csk-algoroll-$tld -l csk2.conf $newtimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $O $TactN -r $O $TactN -z $O $TactN -d $O $TactN "$CSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -z $R $TpubN1 -d $H $TpubN1 "$CSK2" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${CSK1}.state"
cat template.db.in "${CSK1}.key" "${CSK2}.key" >"$infile"
private_type_record $zone 5 "$CSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK2" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 4:
# The DS is swapped and can become OMNIPRESENT.
setup step4.csk-algorithm-roll.$tld
# The time passed since the DS has been swapped is 3 hours.
TpubN1="now-10h"
TsbmN1="now-3h"
csktimes="-P ${TactN} -A ${TactN} -P sync ${TsbmN} -I ${TsbmN1}"
newtimes="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
CSK1=$($KEYGEN -k csk-algoroll-$tld -l csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
CSK2=$($KEYGEN -k csk-algoroll-$tld -l csk2.conf $newtimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $O $TactN -r $O $TactN -z $O $TsbmN1 -d $U $TsbmN1 -D ds $TsbmN1 "$CSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -z $O $TsbmN1 -d $R $TsbmN1 -P ds $TsbmN1 "$CSK2" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${CSK1}.state"
cat template.db.in "${CSK1}.key" "${CSK2}.key" >"$infile"
private_type_record $zone 5 "$CSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK2" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 5:
# The DNSKEY is removed long enough to be HIDDEN.
setup step5.csk-algorithm-roll.$tld
# The time passed since the DNSKEY has been removed is 2 hours.
TpubN1="now-12h"
TsbmN1="now-5h"
csktimes="-P ${TactN} -A ${TactN} -P sync ${TsbmN} -I ${TsbmN1}"
newtimes="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
CSK1=$($KEYGEN -k csk-algoroll-$tld -l csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
CSK2=$($KEYGEN -k csk-algoroll-$tld -l csk2.conf $newtimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $U $TactN -r $U $TactN -z $U $TsbmN1 -d $H $TsbmN1 "$CSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $O $TpubN1 -r $O $TpubN1 -z $O $TsbmN1 -d $O $TsbmN1 "$CSK2" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${CSK1}.state"
cat template.db.in "${CSK1}.key" "${CSK2}.key" >"$infile"
private_type_record $zone 5 "$CSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK2" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Step 6:
# The RRSIGs have been removed long enough to be HIDDEN.
setup step6.csk-algorithm-roll.$tld
# Additional time passed: 7h.
TpubN1="now-19h"
TsbmN1="now-12h"
csktimes="-P ${TactN} -A ${TactN} -P sync ${TsbmN} -I ${TsbmN1}"
newtimes="-P ${TpubN1} -A ${TpubN1} -P sync ${TsbmN1}"
CSK1=$($KEYGEN -k csk-algoroll-$tld -l csk1.conf $csktimes $zone 2>keygen.out.$zone.1)
CSK2=$($KEYGEN -k csk-algoroll-$tld -l csk2.conf $newtimes $zone 2>keygen.out.$zone.2)
$SETTIME -s -g $H -k $H $TactN -r $U $TactN -z $U $TactN -d $H $TsbmN1 "$CSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $O $TactN1 -r $O $TactN1 -z $O $TsubN1 -d $O $TsbmN1 "$CSK2" >settime.out.$zone.2 2>&1
# Fake lifetime of old algorithm keys.
echo "Lifetime: 0" >>"${CSK1}.state"
cat template.db.in "${CSK1}.key" "${CSK2}.key" >"$infile"
private_type_record $zone 5 "$CSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK2" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -z -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
done

View file

@ -22,18 +22,52 @@ from rollover.common import (
TIMEDELTA,
ALGOROLL_CONFIG,
)
from rollover.setup import (
configure_root,
configure_tld,
configure_algo_csk,
)
POLICY = "csk-algoroll"
def bootstrap():
data = {
"tlds": [],
"trust_anchors": [],
}
tlds = []
for tld_name in [
"kasp",
"manual",
]:
delegations = configure_algo_csk(
tld_name, f"{POLICY}-{tld_name}", reconfig=False
)
tld = configure_tld(tld_name, delegations)
tlds.append(tld)
data["tlds"].append(tld_name)
ta = configure_root(tlds)
data["trust_anchors"].append(ta)
return data
@pytest.mark.parametrize(
"tld, policy",
"tld",
[
param("kasp", "csk-algoroll"),
param("manual", "csk-algoroll-manual"),
param("kasp"),
param("manual"),
],
)
def test_algoroll_csk_initial(ns3, tld, policy):
def test_algoroll_csk_initial(tld, ns3):
config = ALGOROLL_CONFIG
zone = f"step1.csk-algorithm-roll.{tld}"
policy = f"{POLICY}-{tld}"
isctest.kasp.wait_keymgr_done(ns3, zone)

View file

@ -32,12 +32,43 @@ from rollover.common import (
DURATION,
TIMEDELTA,
)
from rollover.setup import (
configure_root,
configure_tld,
configure_algo_csk,
)
CONFIG = ALGOROLL_CONFIG
POLICY = "csk-algoroll"
TIME_PASSED = 0 # set in reconfigure() fixture
def bootstrap():
data = {
"tlds": [],
"trust_anchors": [],
}
tlds = []
for tld_name in [
"kasp",
"manual",
]:
delegations = configure_algo_csk(
tld_name, f"{POLICY}-{tld_name}", reconfig=True
)
tld = configure_tld(tld_name, delegations)
tlds.append(tld)
data["tlds"].append(tld_name)
ta = configure_root(tlds)
data["trust_anchors"].append(ta)
return data
@pytest.fixture(scope="module", autouse=True)
def after_servers_start(ns3, templates):
global TIME_PASSED # pylint: disable=global-statement

View file

@ -88,3 +88,174 @@ def configure_root(delegations: List[Zone]) -> TrustAnchor:
signer(f"-P -x -O full -o {zonename} -f {outfile} {infile}", cwd="ns1")
return ksk.into_ta("static-ds")
def render_and_sign_zone(zonename: str, keys: List[str]):
dnskeys = []
privaterrs = []
for key_name in keys:
key = isctest.kasp.Key(key_name, keydir="ns3")
privaterr = private_type_record(zonename, key)
dnskeys.append(key.dnskey)
privaterrs.append(privaterr)
outfile = f"{zonename}.db"
templates = isctest.template.TemplateEngine(".")
template = "template.db.j2.manual"
tdata = {
"fqdn": f"{zonename}.",
"dnskeys": dnskeys,
"privaterrs": privaterrs,
}
templates.render(f"ns3/{outfile}", tdata, template=f"ns3/{template}")
signer = CmdHelper("SIGNER", "-S -g -x -z -s now-1h -e now+2w -O raw")
signer(f"-o {zonename} -f {outfile}.signed {outfile}", cwd="ns3")
def configure_algo_csk(tld: str, policy: str, reconfig: bool = False) -> List[Zone]:
# The zones at csk-algorithm-roll.$tld represent the various steps
# of a CSK algorithm rollover.
zones = []
zone = f"csk-algorithm-roll.{tld}"
keygen = CmdHelper("KEYGEN", f"-k {policy}")
settime = CmdHelper("SETTIME", "-s")
# Step 1:
# Introduce the first key. This will immediately be active.
zonename = f"step1.{zone}"
zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
isctest.log.info(f"setup {zonename}")
TactN = "now-7d"
TsbmN = "now-161h"
csktimes = f"-P {TactN} -A {TactN}"
# Key generation.
csk_name = keygen(f"-l csk1.conf {csktimes} {zonename}", cwd="ns3").strip()
settime(
f"-g OMNIPRESENT -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {csk_name}",
cwd="ns3",
)
# Signing.
render_and_sign_zone(zonename, [csk_name])
if reconfig:
# Step 2:
# After the publication interval has passed the DNSKEY is OMNIPRESENT.
zonename = f"step2.{zone}"
zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
isctest.log.info(f"setup {zonename}")
# The time passed since the new algorithm keys have been introduced is 3 hours.
TpubN1 = "now-3h"
csktimes = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I now"
newtimes = f"-P {TpubN1} -A {TpubN1}"
# Key generation.
csk1_name = keygen(f"-l csk1.conf {csktimes} {zonename}", cwd="ns3").strip()
csk2_name = keygen(f"-l csk2.conf {newtimes} {zonename}", cwd="ns3").strip()
settime(
f"-g HIDDEN -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {csk1_name}",
cwd="ns3",
)
settime(
f"-g OMNIPRESENT -k RUMOURED {TpubN1} -r RUMOURED {TpubN1} -z RUMOURED {TpubN1} -d HIDDEN {TpubN1} {csk2_name}",
cwd="ns3",
)
# Signing.
render_and_sign_zone(zonename, [csk1_name, csk2_name])
# Step 3:
# The zone signatures are also OMNIPRESENT.
zonename = f"step3.{zone}"
zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
isctest.log.info(f"setup {zonename}")
# The time passed since the new algorithm keys have been introduced is 7 hours.
TpubN1 = "now-7h"
TsbmN1 = "now"
csktimes = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
newtimes = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
# Key generation.
csk1_name = keygen(f"-l csk1.conf {csktimes} {zonename}", cwd="ns3").strip()
csk2_name = keygen(f"-l csk2.conf {newtimes} {zonename}", cwd="ns3").strip()
settime(
f"-g HIDDEN -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -z OMNIPRESENT {TactN} -d OMNIPRESENT {TactN} {csk1_name}",
cwd="ns3",
)
settime(
f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -z RUMOURED {TpubN1} -d HIDDEN {TpubN1} {csk2_name}",
cwd="ns3",
)
# Signing.
render_and_sign_zone(zonename, [csk1_name, csk2_name])
# Step 4:
# The DS is swapped and can become OMNIPRESENT.
zonename = f"step4.{zone}"
zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
isctest.log.info(f"setup {zonename}")
# The time passed since the DS has been swapped is 3 hours.
TpubN1 = "now-10h"
TsbmN1 = "now-3h"
csktimes = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
newtimes = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
# Key generation.
csk1_name = keygen(f"-l csk1.conf {csktimes} {zonename}", cwd="ns3").strip()
csk2_name = keygen(f"-l csk2.conf {newtimes} {zonename}", cwd="ns3").strip()
settime(
f"-g HIDDEN -k OMNIPRESENT {TactN} -r OMNIPRESENT {TactN} -z OMNIPRESENT {TsbmN1} -d UNRETENTIVE {TsbmN1} -D ds {TsbmN1} {csk1_name}",
cwd="ns3",
)
settime(
f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -z OMNIPRESENT {TsbmN1} -d RUMOURED {TsbmN1} -P ds {TsbmN1} {csk2_name}",
cwd="ns3",
)
# Signing.
render_and_sign_zone(zonename, [csk1_name, csk2_name])
# Step 5:
# The DNSKEY is removed long enough to be HIDDEN.
zonename = f"step5.{zone}"
zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
isctest.log.info(f"setup {zonename}")
# The time passed since the DNSKEY has been removed is 2 hours.
TpubN1 = "now-12h"
TsbmN1 = "now-5h"
csktimes = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
newtimes = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
# Key generation.
csk1_name = keygen(f"-l csk1.conf {csktimes} {zonename}", cwd="ns3").strip()
csk2_name = keygen(f"-l csk2.conf {newtimes} {zonename}", cwd="ns3").strip()
settime(
f"-g HIDDEN -k UNRETENTIVE {TactN} -r UNRETENTIVE {TactN} -z UNRETENTIVE {TsbmN1} -d HIDDEN {TsbmN1} {csk1_name}",
cwd="ns3",
)
settime(
f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -z OMNIPRESENT {TsbmN1} -d OMNIPRESENT {TsbmN1} {csk2_name}",
cwd="ns3",
)
# Signing.
render_and_sign_zone(zonename, [csk1_name, csk2_name])
# Step 6:
# The RRSIGs have been removed long enough to be HIDDEN.
zonename = f"step6.{zone}"
zones.append(Zone(zonename, f"{zonename}.db", Nameserver("ns3", "10.53.0.3")))
isctest.log.info(f"setup {zonename}")
# Additional time passed: 7h.
TpubN1 = "now-19h"
TsbmN1 = "now-12h"
csktimes = f"-P {TactN} -A {TactN} -P sync {TsbmN} -I {TsbmN1}"
newtimes = f"-P {TpubN1} -A {TpubN1} -P sync {TsbmN1}"
# Key generation.
csk1_name = keygen(f"-l csk1.conf {csktimes} {zonename}", cwd="ns3").strip()
csk2_name = keygen(f"-l csk2.conf {newtimes} {zonename}", cwd="ns3").strip()
settime(
f"-g HIDDEN -k HIDDEN {TactN} -r UNRETENTIVE {TactN} -z UNRETENTIVE {TactN} -d HIDDEN {TsbmN1} {csk1_name}",
cwd="ns3",
)
settime(
f"-g OMNIPRESENT -k OMNIPRESENT {TpubN1} -r OMNIPRESENT {TpubN1} -z OMNIPRESENT {TsbmN1} -d OMNIPRESENT {TsbmN1} {csk2_name}",
cwd="ns3",
)
# Signing.
render_and_sign_zone(zonename, [csk1_name, csk2_name])
return zones