Isolate rollover-ksk-3crowd test case

This commit is contained in:
Nicki Křížek 2025-06-10 16:33:08 +02:00
parent d6dffe6603
commit bc7be041e1
13 changed files with 257 additions and 212 deletions

View file

@ -0,0 +1 @@
../rollover/common.py

View file

@ -0,0 +1 @@
../../rollover-ksk-doubleksk/ns3/kasp.conf.j2

View file

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

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
include "kasp.conf";
include "named.common.conf";
zone "three-is-a-crowd.kasp" {
type primary;
file "three-is-a-crowd.kasp.db";
inline-signing yes;
/* Use same policy as KSK rollover test zones. */
dnssec-policy "ksk-doubleksk";
};

View file

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

View file

@ -0,0 +1,82 @@
#!/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"
echo "$zone" >>zones
}
# Set in the key state files the Predecessor/Successor fields.
# Key $1 is the predecessor of key $2.
key_successor() {
id1=$(keyfile_to_key_id "$1")
id2=$(keyfile_to_key_id "$2")
echo "Predecessor: ${id1}" >>"${2}.state"
echo "Successor: ${id2}" >>"${1}.state"
}
# Make lines shorter by storing key states in environment variables.
H="HIDDEN"
R="RUMOURED"
O="OMNIPRESENT"
U="UNRETENTIVE"
# Test #2375, the "three is a crowd" bug, where a new key is introduced but the
# previous rollover has not finished yet. In other words, we have a key KEY2
# that is the successor of key KEY1, and we introduce a new key KEY3 that is
# the successor of key KEY2:
#
# KEY1 < KEY2 < KEY3.
#
# The expected behavior is that all three keys remain in the zone, and not
# the bug behavior where KEY2 is removed and immediately replaced with KEY3.
#
# Set up a zone that has a KSK (KEY1) and have the successor key (KEY2)
# published as well.
setup three-is-a-crowd.kasp
# These times are the same as step3.ksk-doubleksk.autosign.
TpubN="now-60d"
TactN="now-1413h"
TretN="now"
TremN="now+50h"
TpubN1="now-27h"
TsbmN1="now"
TactN1="${TretN}"
TretN1="now+60d"
TremN1="now+1490h"
ksktimes="-P ${TpubN} -A ${TpubN} -P sync ${TactN} -I ${TretN} -D ${TremN} -D sync ${TactN1}"
newtimes="-P ${TpubN1} -A ${TactN1} -P sync ${TsbmN1} -I ${TretN1} -D ${TremN1}"
zsktimes="-P ${TpubN} -A ${TpubN}"
KSK1=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $newtimes $zone 2>keygen.out.$zone.2)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $zsktimes $zone 2>keygen.out.$zone.3)
$SETTIME -s -g $H -k $O $TactN -r $O $TactN -d $O $TactN "$KSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 "$KSK2" >settime.out.$zone.2 2>&1
$SETTIME -s -g $O -k $O $TactN -z $O $TactN "$ZSK" >settime.out.$zone.3 2>&1
# Set key rollover relationship.
key_successor $KSK1 $KSK2
# Sign zone.
cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" >"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -G "cds:sha-256" -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1

View file

@ -0,0 +1,94 @@
# 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.
# pylint: disable=redefined-outer-name,unused-import
from datetime import timedelta
import isctest
from isctest.kasp import KeyTimingMetadata
from common import (
pytestmark,
alg,
size,
KSK_CONFIG,
KSK_LIFETIME_POLICY,
KSK_IPUB,
KSK_IRET,
)
CDSS = ["CDS (SHA-256)"]
POLICY = "ksk-doubleksk"
OFFSET1 = -int(timedelta(days=60).total_seconds())
OFFSET2 = -int(timedelta(hours=27).total_seconds())
TTL = int(KSK_CONFIG["dnskey-ttl"].total_seconds())
def test_rollover_ksk_three_is_a_crowd(alg, size, servers):
"""Test #2375: Scheduled rollovers are happening faster than they can finish."""
server = servers["ns3"]
zone = "three-is-a-crowd.kasp"
step = {
"zone": zone,
"cdss": CDSS,
"keyprops": [
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSET1}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSET2}",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSET1}",
],
"keyrelationships": [0, 1],
}
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
# Rollover successor KSK (with DS in rumoured state).
expected = isctest.kasp.policy_to_properties(TTL, step["keyprops"])
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
isctest.kasp.check_keys(zone, keys, expected)
key = expected[1].key
now = KeyTimingMetadata.now()
with server.watch_log_from_here() as watcher:
server.rndc(f"dnssec -rollover -key {key.tag} -when {now} {zone}")
watcher.wait_for_line(f"keymgr: {zone} done")
# We now expect four keys (3x KSK, 1x ZSK).
step = {
"zone": zone,
"cdss": CDSS,
"keyprops": [
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSET1}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSET2}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:0",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSET1}",
],
"check-keytimes": False, # checked manually with modified values
}
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
expected = isctest.kasp.policy_to_properties(TTL, step["keyprops"])
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
isctest.kasp.check_keys(zone, keys, expected)
expected[0].metadata["Successor"] = expected[1].key.tag
expected[1].metadata["Predecessor"] = expected[0].key.tag
# Three is a crowd scenario.
expected[1].metadata["Successor"] = expected[2].key.tag
expected[2].metadata["Predecessor"] = expected[1].key.tag
isctest.kasp.check_keyrelationships(keys, expected)
for kp in expected:
kp.set_expected_keytimes(KSK_CONFIG)
# The first successor KSK is already being retired.
expected[1].timing["Retired"] = now + KSK_IPUB
expected[1].timing["Removed"] = now + KSK_IPUB + KSK_IRET
isctest.kasp.check_keytimes(keys, expected)

View file

@ -18,43 +18,31 @@ from common import (
pytestmark,
alg,
size,
DEFAULT_CONFIG,
KSK_CONFIG,
KSK_LIFETIME,
KSK_LIFETIME_POLICY,
KSK_IPUB,
KSK_IPUBC,
KSK_IRET,
KSK_KEYTTLPROP,
TIMEDELTA,
)
CDSS = ["CDS (SHA-256)"]
CONFIG = {
"dnskey-ttl": TIMEDELTA["PT2H"],
"ds-ttl": TIMEDELTA["PT1H"],
"max-zone-ttl": TIMEDELTA["P1D"],
"parent-propagation-delay": TIMEDELTA["PT1H"],
"publish-safety": TIMEDELTA["P1D"],
"purge-keys": TIMEDELTA["PT1H"],
"retire-safety": TIMEDELTA["P2D"],
"signatures-refresh": TIMEDELTA["P7D"],
"signatures-validity": TIMEDELTA["P14D"],
"zone-propagation-delay": TIMEDELTA["PT1H"],
}
POLICY = "ksk-doubleksk"
KSK_LIFETIME = TIMEDELTA["P60D"]
LIFETIME_POLICY = int(KSK_LIFETIME.total_seconds())
IPUB = Ipub(CONFIG)
IPUBC = IpubC(CONFIG)
IRET = Iret(CONFIG, zsk=False, ksk=True)
KEYTTLPROP = CONFIG["dnskey-ttl"] + CONFIG["zone-propagation-delay"]
OFFSETS = {}
OFFSETS["step1-p"] = -int(TIMEDELTA["P7D"].total_seconds())
OFFSETS["step2-p"] = -int(KSK_LIFETIME.total_seconds() - IPUBC.total_seconds())
OFFSETS["step2-p"] = -int(KSK_LIFETIME.total_seconds() - KSK_IPUBC.total_seconds())
OFFSETS["step2-s"] = 0
OFFSETS["step3-p"] = -int(KSK_LIFETIME.total_seconds())
OFFSETS["step3-s"] = -int(IPUBC.total_seconds())
OFFSETS["step4-p"] = OFFSETS["step3-p"] - int(IRET.total_seconds())
OFFSETS["step4-s"] = OFFSETS["step3-s"] - int(IRET.total_seconds())
OFFSETS["step5-p"] = OFFSETS["step4-p"] - int(KEYTTLPROP.total_seconds())
OFFSETS["step5-s"] = OFFSETS["step4-s"] - int(KEYTTLPROP.total_seconds())
OFFSETS["step6-p"] = OFFSETS["step5-p"] - int(CONFIG["purge-keys"].total_seconds())
OFFSETS["step6-s"] = OFFSETS["step5-s"] - int(CONFIG["purge-keys"].total_seconds())
OFFSETS["step3-s"] = -int(KSK_IPUBC.total_seconds())
OFFSETS["step4-p"] = OFFSETS["step3-p"] - int(KSK_IRET.total_seconds())
OFFSETS["step4-s"] = OFFSETS["step3-s"] - int(KSK_IRET.total_seconds())
OFFSETS["step5-p"] = OFFSETS["step4-p"] - int(KSK_KEYTTLPROP.total_seconds())
OFFSETS["step5-s"] = OFFSETS["step4-s"] - int(KSK_KEYTTLPROP.total_seconds())
OFFSETS["step6-p"] = OFFSETS["step5-p"] - int(KSK_CONFIG["purge-keys"].total_seconds())
OFFSETS["step6-s"] = OFFSETS["step5-s"] - int(KSK_CONFIG["purge-keys"].total_seconds())
def test_ksk_doubleksk_step1(alg, size, servers):
@ -64,14 +52,14 @@ def test_ksk_doubleksk_step1(alg, size, servers):
"cdss": CDSS,
"keyprops": [
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step1-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step1-p']}",
],
# Next key event is when the successor KSK needs to be published.
# That is the KSK lifetime - prepublication time (minus time
# already passed).
"nextev": KSK_LIFETIME - IPUB - timedelta(days=7),
"nextev": KSK_LIFETIME - KSK_IPUB - timedelta(days=7),
}
isctest.kasp.check_rollover_step(servers["ns3"], CONFIG, POLICY, step)
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
def test_ksk_doubleksk_step2(alg, size, servers):
@ -85,14 +73,14 @@ def test_ksk_doubleksk_step2(alg, size, servers):
"cdss": CDSS,
"keyprops": [
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step2-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:{OFFSETS['step2-s']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step2-p']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:{OFFSETS['step2-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when the successor KSK becomes OMNIPRESENT.
"nextev": IPUB,
"nextev": KSK_IPUB,
}
isctest.kasp.check_rollover_step(servers["ns3"], CONFIG, POLICY, step)
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
def test_ksk_doubleksk_step3(alg, size, servers):
@ -108,17 +96,17 @@ def test_ksk_doubleksk_step3(alg, size, servers):
"cdss": CDSS,
"keyprops": [
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step3-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSETS['step3-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSETS['step3-s']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{OFFSETS['step3-p']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{OFFSETS['step3-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when the predecessor DS has been replaced with
# the successor DS and enough time has passed such that the all
# validators that have this DS RRset cached only know about the
# successor DS. This is the the retire interval.
"nextev": IRET,
"nextev": KSK_IRET,
}
isctest.kasp.check_rollover_step(servers["ns3"], CONFIG, POLICY, step)
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
def test_ksk_doubleksk_step4(alg, size, servers):
@ -133,15 +121,15 @@ def test_ksk_doubleksk_step4(alg, size, servers):
"cdss": CDSS,
"keyprops": [
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step4-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-s']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive ds:hidden offset:{OFFSETS['step4-p']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step4-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when the DNSKEY enters the HIDDEN state.
# This is the DNSKEY TTL plus zone propagation delay.
"nextev": KEYTTLPROP,
"nextev": KSK_KEYTTLPROP,
}
isctest.kasp.check_rollover_step(servers["ns3"], CONFIG, POLICY, step)
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
def test_ksk_doubleksk_step5(alg, size, servers):
@ -154,15 +142,15 @@ def test_ksk_doubleksk_step5(alg, size, servers):
"cdss": CDSS,
"keyprops": [
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step5-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden ds:hidden offset:{OFFSETS['step5-p']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step5-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when the new successor needs to be published.
# This is the KSK lifetime minus Ipub minus Iret minus time elapsed.
"nextev": KSK_LIFETIME - IPUB - IRET - KEYTTLPROP,
"nextev": KSK_LIFETIME - KSK_IPUB - KSK_IRET - KSK_KEYTTLPROP,
}
isctest.kasp.check_rollover_step(servers["ns3"], CONFIG, POLICY, step)
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)
def test_ksk_doubleksk_step6(alg, size, servers):
@ -172,8 +160,8 @@ def test_ksk_doubleksk_step6(alg, size, servers):
"cdss": CDSS,
"keyprops": [
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{OFFSETS['step6-p']}",
f"ksk {LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}",
f"ksk {KSK_LIFETIME_POLICY} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{OFFSETS['step6-s']}",
],
"nextev": None,
}
isctest.kasp.check_rollover_step(servers["ns3"], CONFIG, POLICY, step)
isctest.kasp.check_rollover_step(servers["ns3"], KSK_CONFIG, POLICY, step)

View file

@ -108,6 +108,24 @@ ALGOROLL_OFFSETS["step6"] = ALGOROLL_OFFSETS["step5"] - int(
ALGOROLL_IRET.total_seconds()
)
ALGOROLL_OFFVAL = -DURATION["P7D"]
KSK_CONFIG = {
"dnskey-ttl": TIMEDELTA["PT2H"],
"ds-ttl": TIMEDELTA["PT1H"],
"max-zone-ttl": TIMEDELTA["P1D"],
"parent-propagation-delay": TIMEDELTA["PT1H"],
"publish-safety": TIMEDELTA["P1D"],
"purge-keys": TIMEDELTA["PT1H"],
"retire-safety": TIMEDELTA["P2D"],
"signatures-refresh": TIMEDELTA["P7D"],
"signatures-validity": TIMEDELTA["P14D"],
"zone-propagation-delay": TIMEDELTA["PT1H"],
}
KSK_LIFETIME = TIMEDELTA["P60D"]
KSK_LIFETIME_POLICY = int(KSK_LIFETIME.total_seconds())
KSK_IPUB = Ipub(KSK_CONFIG)
KSK_IPUBC = IpubC(KSK_CONFIG)
KSK_IRET = Iret(KSK_CONFIG, zsk=False, ksk=True)
KSK_KEYTTLPROP = KSK_CONFIG["dnskey-ttl"] + KSK_CONFIG["zone-propagation-delay"]
@pytest.fixture

View file

@ -67,26 +67,3 @@ dnssec-policy "zsk-prepub" {
zone-propagation-delay PT1H;
max-zone-ttl 1d;
};
dnssec-policy "ksk-doubleksk" {
signatures-refresh P1W;
signatures-validity P2W;
signatures-validity-dnskey P2W;
dnskey-ttl 2h;
publish-safety P1D;
retire-safety P2D;
purge-keys PT1H;
cdnskey no;
keys {
ksk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@;
zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
};
zone-propagation-delay PT1H;
max-zone-ttl 1d;
parent-ds-ttl 3600;
parent-propagation-delay PT1H;
};

View file

@ -100,14 +100,3 @@ zone "step6.zsk-prepub.autosign" {
file "step6.zsk-prepub.autosign.db";
dnssec-policy "zsk-prepub";
};
/*
* Zone for testing GL #2375: Three is a crowd.
*/
zone "three-is-a-crowd.kasp" {
type primary;
file "three-is-a-crowd.kasp.db";
inline-signing yes;
/* Use same policy as KSK rollover test zones. */
dnssec-policy "ksk-doubleksk";
};

View file

@ -315,45 +315,3 @@ private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK2" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1
# Test #2375, the "three is a crowd" bug, where a new key is introduced but the
# previous rollover has not finished yet. In other words, we have a key KEY2
# that is the successor of key KEY1, and we introduce a new key KEY3 that is
# the successor of key KEY2:
#
# KEY1 < KEY2 < KEY3.
#
# The expected behavior is that all three keys remain in the zone, and not
# the bug behavior where KEY2 is removed and immediately replaced with KEY3.
#
# Set up a zone that has a KSK (KEY1) and have the successor key (KEY2)
# published as well.
setup three-is-a-crowd.kasp
# These times are the same as step3.ksk-doubleksk.autosign.
TpubN="now-60d"
TactN="now-1413h"
TretN="now"
TremN="now+50h"
TpubN1="now-27h"
TsbmN1="now"
TactN1="${TretN}"
TretN1="now+60d"
TremN1="now+1490h"
ksktimes="-P ${TpubN} -A ${TpubN} -P sync ${TactN} -I ${TretN} -D ${TremN} -D sync ${TactN1}"
newtimes="-P ${TpubN1} -A ${TactN1} -P sync ${TsbmN1} -I ${TretN1} -D ${TremN1}"
zsktimes="-P ${TpubN} -A ${TpubN}"
KSK1=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $ksktimes $zone 2>keygen.out.$zone.1)
KSK2=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $newtimes $zone 2>keygen.out.$zone.2)
ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $zsktimes $zone 2>keygen.out.$zone.3)
$SETTIME -s -g $H -k $O $TactN -r $O $TactN -d $O $TactN "$KSK1" >settime.out.$zone.1 2>&1
$SETTIME -s -g $O -k $R $TpubN1 -r $R $TpubN1 -d $H $TpubN1 "$KSK2" >settime.out.$zone.2 2>&1
$SETTIME -s -g $O -k $O $TactN -z $O $TactN "$ZSK" >settime.out.$zone.3 2>&1
# Set key rollover relationship.
key_successor $KSK1 $KSK2
# Sign zone.
cat template.db.in "${KSK1}.key" "${KSK2}.key" "${ZSK}.key" >"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK1" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK2" >>"$infile"
private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile"
cp $infile $zonefile
$SIGNER -S -x -G "cds:sha-256" -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1

View file

@ -536,91 +536,3 @@ def test_rollover_zsk_prepublication(servers):
for step in steps:
isctest.kasp.check_rollover_step(server, config, policy, step)
def test_rollover_ksk_doubleksk(servers):
server = servers["ns3"]
policy = "ksk-doubleksk"
cdss = ["CDS (SHA-256)"]
config = {
"dnskey-ttl": timedelta(hours=2),
"ds-ttl": timedelta(seconds=3600),
"max-zone-ttl": timedelta(days=1),
"parent-propagation-delay": timedelta(hours=1),
"publish-safety": timedelta(days=1),
"purge-keys": timedelta(hours=1),
"retire-safety": timedelta(days=2),
"signatures-refresh": timedelta(days=7),
"signatures-validity": timedelta(days=14),
"zone-propagation-delay": timedelta(hours=1),
}
ttl = int(config["dnskey-ttl"].total_seconds())
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
ksk_lifetime = timedelta(days=60)
lifetime_policy = int(ksk_lifetime.total_seconds())
ipub = Ipub(config)
iret = Iret(config, zsk=False, ksk=True)
# Test #2375: Scheduled rollovers are happening faster than they can finish.
isctest.log.info(
"check that fast rollovers do not remove dependent keys from zone (#2375)"
)
offset1 = -int(timedelta(days=60).total_seconds())
offset2 = -int(timedelta(hours=27).total_seconds())
zone = "three-is-a-crowd.kasp"
step = {
"zone": zone,
"cdss": cdss,
"keyprops": [
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offset1}",
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{offset2}",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offset1}",
],
"keyrelationships": [0, 1],
}
isctest.kasp.check_rollover_step(servers["ns3"], config, policy, step)
# Rollover successor KSK (with DS in rumoured state).
expected = isctest.kasp.policy_to_properties(ttl, step["keyprops"])
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
isctest.kasp.check_keys(zone, keys, expected)
key = expected[1].key
now = KeyTimingMetadata.now()
with server.watch_log_from_here() as watcher:
server.rndc(f"dnssec -rollover -key {key.tag} -when {now} {zone}")
watcher.wait_for_line(f"keymgr: {zone} done")
# We now expect four keys (3x KSK, 1x ZSK).
step = {
"zone": zone,
"cdss": cdss,
"keyprops": [
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:unretentive offset:{offset1}",
f"ksk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:rumoured offset:{offset2}",
f"ksk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden offset:0",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offset1}",
],
"check-keytimes": False, # checked manually with modified values
}
isctest.kasp.check_rollover_step(servers["ns3"], config, policy, step)
expected = isctest.kasp.policy_to_properties(ttl, step["keyprops"])
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
isctest.kasp.check_keys(zone, keys, expected)
expected[0].metadata["Successor"] = expected[1].key.tag
expected[1].metadata["Predecessor"] = expected[0].key.tag
# Three is a crowd scenario.
expected[1].metadata["Successor"] = expected[2].key.tag
expected[2].metadata["Predecessor"] = expected[1].key.tag
isctest.kasp.check_keyrelationships(keys, expected)
for kp in expected:
kp.set_expected_keytimes(config, offset=None)
# The first successor KSK is already being retired.
expected[1].timing["Retired"] = now + ipub
expected[1].timing["Removed"] = now + ipub + iret
isctest.kasp.check_keytimes(keys, expected)