2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 10:10:06 +00:00
bind/bin/tests/system/rollover/tests_rollover.py
Nicki Křížek c00121b4c2 Add dynamic update facility to NamedInstance
Deduplicate the code for dynamic updates and increase code clarity by
using an actual dns.update.UpdateMessage rather than an undefined
intermediary format passed around as a list of arguments.
2025-06-02 09:21:06 +00:00

1256 lines
56 KiB
Python

# 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.
import os
from datetime import timedelta
import pytest
pytest.importorskip("dns", minversion="2.0.0")
import dns.update
import isctest
from isctest.kasp import KeyTimingMetadata
pytestmark = pytest.mark.extra_artifacts(
[
"*.axfr*",
"dig.out*",
"K*.key*",
"K*.private*",
"ns*/*.db",
"ns*/*.db.infile",
"ns*/*.db.jnl",
"ns*/*.db.jbk",
"ns*/*.db.signed",
"ns*/*.db.signed.jnl",
"ns*/*.conf",
"ns*/dsset-*",
"ns*/K*.key",
"ns*/K*.private",
"ns*/K*.state",
"ns*/keygen.out.*",
"ns*/settime.out.*",
"ns*/signer.out.*",
"ns*/zones",
]
)
def Ipub(config):
return (
config["dnskey-ttl"]
+ config["zone-propagation-delay"]
+ config["publish-safety"]
)
def IpubC(config, rollover=True):
if rollover:
ttl = config["dnskey-ttl"]
safety_interval = config["publish-safety"]
else:
ttl = config["max-zone-ttl"]
safety_interval = timedelta(0)
return ttl + config["zone-propagation-delay"] + safety_interval
def Iret(config, zsk=True, ksk=False, rollover=True):
sign_delay = timedelta(0)
safety_interval = timedelta(0)
if rollover:
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
safety_interval = config["retire-safety"]
iretKSK = timedelta(0)
if ksk:
# KSK: Double-KSK Method: Iret = DprpP + TTLds
iretKSK = (
config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval
)
iretZSK = timedelta(0)
if zsk:
# ZSK: Pre-Publication Method: Iret = Dsgn + Dprp + TTLsig
iretZSK = (
sign_delay
+ config["zone-propagation-delay"]
+ config["max-zone-ttl"]
+ safety_interval
)
return max(iretKSK, iretZSK)
def test_rollover_manual(servers):
server = servers["ns3"]
policy = "manual-rollover"
config = {
"dnskey-ttl": timedelta(hours=1),
"ds-ttl": timedelta(days=1),
"max-zone-ttl": timedelta(days=1),
"parent-propagation-delay": timedelta(hours=1),
"publish-safety": timedelta(hours=1),
"retire-safety": timedelta(hours=1),
"signatures-refresh": timedelta(days=7),
"signatures-validity": timedelta(days=14),
"zone-propagation-delay": timedelta(minutes=5),
}
ttl = int(config["dnskey-ttl"].total_seconds())
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
zone = "manual-rollover.kasp"
isctest.kasp.check_dnssec_verify(server, zone)
key_properties = [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
]
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
offset = -timedelta(days=7)
for kp in expected:
kp.set_expected_keytimes(config, offset=offset)
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# Schedule KSK rollover in six months.
assert len(ksks) == 1
ksk = ksks[0]
startroll = expected[0].timing["Active"] + timedelta(days=30 * 6)
expected[0].timing["Retired"] = startroll + Ipub(config)
expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret(
config, zsk=False, ksk=True
)
with server.watch_log_from_here() as watcher:
server.rndc(f"dnssec -rollover -key {ksk.tag} -when {startroll} {zone}")
watcher.wait_for_line(f"keymgr: {zone} done")
isctest.kasp.check_dnssec_verify(server, zone)
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# Schedule KSK rollover now.
now = KeyTimingMetadata.now()
with server.watch_log_from_here() as watcher:
server.rndc(f"dnssec -rollover -key {ksk.tag} -when {now} {zone}")
watcher.wait_for_line(f"keymgr: {zone} done")
isctest.kasp.check_dnssec_verify(server, zone)
key_properties = [
f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent",
]
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
expected[0].metadata["Successor"] = expected[1].key.tag
expected[1].metadata["Predecessor"] = expected[0].key.tag
isctest.kasp.check_keyrelationships(keys, expected)
for kp in expected:
off = offset
if "Predecessor" in kp.metadata:
off = 0
kp.set_expected_keytimes(config, offset=off)
expected[0].timing["Retired"] = now + Ipub(config)
expected[0].timing["Removed"] = expected[0].timing["Retired"] + Iret(
config, zsk=False, ksk=True
)
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# Schedule ZSK rollover now.
assert len(zsks) == 1
zsk = zsks[0]
now = KeyTimingMetadata.now()
with server.watch_log_from_here() as watcher:
server.rndc(f"dnssec -rollover -key {zsk.tag} -when {now} {zone}")
watcher.wait_for_line(f"keymgr: {zone} done")
isctest.kasp.check_dnssec_verify(server, zone)
key_properties = [
f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent",
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden",
f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden",
]
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
expected[0].metadata["Successor"] = expected[1].key.tag
expected[1].metadata["Predecessor"] = expected[0].key.tag
expected[2].metadata["Successor"] = expected[3].key.tag
expected[3].metadata["Predecessor"] = expected[2].key.tag
isctest.kasp.check_keyrelationships(keys, expected)
# Try to schedule a ZSK rollover for an inactive key (should fail).
zsk = expected[3].key
response = server.rndc(f"dnssec -rollover -key {zsk.tag} {zone}")
assert "key is not actively signing" in response
def test_rollover_multisigner(servers):
server = servers["ns3"]
policy = "multisigner-model2"
config = {
"dnskey-ttl": timedelta(hours=1),
"ds-ttl": timedelta(days=1),
"max-zone-ttl": timedelta(days=1),
"parent-propagation-delay": timedelta(hours=1),
"publish-safety": timedelta(hours=1),
"retire-safety": timedelta(hours=1),
"signatures-refresh": timedelta(days=5),
"signatures-validity": timedelta(days=14),
"zone-propagation-delay": timedelta(minutes=5),
}
ttl = int(config["dnskey-ttl"].total_seconds())
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
offset = -timedelta(days=7)
offval = int(offset.total_seconds())
def keygen(zone):
keygen_command = [
os.environ.get("KEYGEN"),
"-a",
alg,
"-L",
"3600",
"-M",
"0:32767",
zone,
]
return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
zone = "multisigner-model2.kasp"
isctest.kasp.check_dnssec_verify(server, zone)
key_properties = [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:rumoured tag-range:32768-65535",
]
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
newprops = [f"zsk unlimited {alg} {size} tag-range:0-32767"]
expected2 = isctest.kasp.policy_to_properties(ttl, newprops)
expected2[0].properties["private"] = False
expected2[0].properties["legacy"] = True
expected = expected + expected2
ownkeys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
extkeys = isctest.kasp.keydir_to_keylist(zone)
keys = ownkeys + extkeys
ksks = [k for k in ownkeys if k.is_ksk()]
zsks = [k for k in ownkeys if not k.is_ksk()]
zsks = zsks + extkeys
isctest.kasp.check_keys(zone, keys, expected)
for kp in expected:
kp.set_expected_keytimes(config)
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# Update zone with ZSK from another provider for zone.
out = keygen(zone)
newkeys = isctest.kasp.keystr_to_keylist(out)
newprops = [f"zsk unlimited {alg} {size} tag-range:0-32767"]
expected2 = isctest.kasp.policy_to_properties(ttl, newprops)
expected2[0].properties["private"] = False
expected2[0].properties["legacy"] = True
expected = expected + expected2
dnskey = newkeys[0].dnskey().split()
rdata = " ".join(dnskey[4:])
update_msg = dns.update.UpdateMessage(zone)
update_msg.add(f"{dnskey[0]}", 3600, "DNSKEY", rdata)
server.nsupdate(update_msg)
isctest.kasp.check_dnssec_verify(server, zone)
keys = keys + newkeys
zsks = zsks + newkeys
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# Remove ZSKs from the other providers for zone.
dnskey2 = extkeys[0].dnskey().split()
rdata2 = " ".join(dnskey2[4:])
update_msg = dns.update.UpdateMessage(zone)
update_msg.delete(f"{dnskey[0]}", "DNSKEY", rdata)
update_msg.delete(f"{dnskey2[0]}", "DNSKEY", rdata2)
server.nsupdate(update_msg)
isctest.kasp.check_dnssec_verify(server, zone)
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
keys = ownkeys
ksks = [k for k in ownkeys if k.is_ksk()]
zsks = [k for k in ownkeys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# A zone transitioning from single-signed to multi-signed. We should have
# the old omnipresent keys outside of the desired key range and the new
# keys in the desired key range.
zone = "single-to-multisigner.kasp"
isctest.kasp.check_dnssec_verify(server, zone)
key_properties = [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden tag-range:32768-65535",
f"zsk unlimited {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden tag-range:32768-65535",
f"ksk unlimited {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent tag-range:0-32767 offset:{offval}",
f"zsk unlimited {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent tag-range:0-32767 offset:{offval}",
]
expected = isctest.kasp.policy_to_properties(ttl, key_properties)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
for kp in expected:
kp.set_expected_keytimes(config)
start = expected[0].key.get_timing("Created")
expected[2].timing["Retired"] = start
expected[2].timing["Removed"] = expected[2].timing["Retired"] + Iret(
config, zsk=False, ksk=True
)
expected[3].timing["Retired"] = start
expected[3].timing["Removed"] = expected[3].timing["Retired"] + Iret(
config, zsk=True, ksk=False
)
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
def check_rollover_step(server, config, policy, step):
zone = step["zone"]
keyprops = step["keyprops"]
nextev = step["nextev"]
cdss = step.get("cdss", None)
keyrelationships = step.get("keyrelationships", None)
smooth = step.get("smooth", False)
ds_swap = step.get("ds-swap", True)
isctest.log.info(f"check rollover step {zone}")
ttl = int(config["dnskey-ttl"].total_seconds())
expected = isctest.kasp.policy_to_properties(ttl, keyprops)
isctest.kasp.check_dnssec_verify(server, zone)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
for kp in expected:
# Set expected key timing metadata.
kp.set_expected_keytimes(config)
# Set rollover relationships.
if keyrelationships is not None:
prd = keyrelationships[0]
suc = keyrelationships[1]
expected[prd].metadata["Successor"] = expected[suc].key.tag
expected[suc].metadata["Predecessor"] = expected[prd].key.tag
isctest.kasp.check_keyrelationships(keys, expected)
# Check that CDS publication/withdrawal is logged.
if "KSK" not in kp.metadata:
continue
if kp.metadata["KSK"] == "no":
continue
key = kp.key
if ds_swap and kp.metadata["DSState"] == "rumoured":
assert cdss is not None
for algstr in ["CDNSKEY", "CDS (SHA-256)", "CDS (SHA-384)"]:
if algstr in cdss:
isctest.kasp.check_cdslog(server, zone, key, algstr)
else:
isctest.kasp.check_cdslog_prohibit(server, zone, key, algstr)
# The DS can be introduced. We ignore any parent registration delay,
# so set the DS publish time to now.
server.rndc(f"dnssec -checkds -key {key.tag} published {zone}")
if ds_swap and kp.metadata["DSState"] == "unretentive":
# The DS can be withdrawn. We ignore any parent registration
# delay, so set the DS withdraw time to now.
server.rndc(f"dnssec -checkds -key {key.tag} withdrawn {zone}")
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks, cdss=cdss)
isctest.kasp.check_subdomain(server, zone, ksks, zsks, smooth=smooth)
def check_next_key_event():
return isctest.kasp.next_key_event_equals(server, zone, nextev)
isctest.run.retry_with_timeout(check_next_key_event, timeout=5)
def test_rollover_enable_dnssec(servers):
server = servers["ns3"]
policy = "enable-dnssec"
cdss = ["CDNSKEY", "CDS (SHA-256)"]
config = {
"dnskey-ttl": timedelta(seconds=300),
"ds-ttl": timedelta(hours=2),
"max-zone-ttl": timedelta(hours=12),
"parent-propagation-delay": timedelta(hours=1),
"publish-safety": timedelta(minutes=5),
"retire-safety": timedelta(minutes=20),
"signatures-refresh": timedelta(days=7),
"signatures-validity": timedelta(days=14),
"zone-propagation-delay": timedelta(minutes=5),
}
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
ipub = Ipub(config)
ipubC = IpubC(config, rollover=False)
iretZSK = Iret(config, rollover=False)
iretKSK = Iret(config, zsk=False, ksk=True, rollover=False)
offsets = {
"step1": 0,
"step2": -int(ipub.total_seconds()),
"step3": -int(iretZSK.total_seconds()),
"step4": -int(ipubC.total_seconds() + iretKSK.total_seconds()),
}
steps = [
{
# Step 1.
"zone": "step1.enable-dnssec.autosign",
"cdss": cdss,
"keyprops": [
f"csk unlimited {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden offset:{offsets['step1']}",
],
# Next key event is when the DNSKEY RRset becomes OMNIPRESENT,
# after the publication interval.
"nextev": ipub,
},
{
# Step 2.
"zone": "step2.enable-dnssec.autosign",
"cdss": cdss,
# The DNSKEY is omnipresent, but the zone signatures not yet.
# Thus, the DS remains hidden.
# dnskey: rumoured -> omnipresent
# krrsig: rumoured -> omnipresent
"keyprops": [
f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:hidden offset:{offsets['step2']}",
],
# Next key event is when the zone signatures become OMNIPRESENT,
# Minus the time already elapsed.
"nextev": iretZSK - ipub,
},
{
# Step 3.
"zone": "step3.enable-dnssec.autosign",
"cdss": cdss,
# All signatures should be omnipresent, so the DS can be submitted.
# zrrsig: rumoured -> omnipresent
# ds: hidden -> rumoured
"keyprops": [
f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{offsets['step3']}",
],
# Next key event is when the DS can move to the OMNIPRESENT state.
# This is after the retire interval.
"nextev": iretKSK,
},
{
# Step 4.
"zone": "step4.enable-dnssec.autosign",
"cdss": cdss,
# DS has been published long enough.
# ds: rumoured -> omnipresent
"keyprops": [
f"csk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step4']}",
],
# Next key event is never, the zone dnssec-policy has been
# established. So we fall back to the default loadkeys interval.
"nextev": timedelta(hours=1),
},
]
for step in steps:
check_rollover_step(server, config, policy, step)
def test_rollover_zsk_prepublication(servers):
server = servers["ns3"]
policy = "zsk-prepub"
config = {
"dnskey-ttl": timedelta(seconds=3600),
"ds-ttl": timedelta(days=1),
"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),
}
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
zsk_lifetime = timedelta(days=30)
lifetime_policy = int(zsk_lifetime.total_seconds())
ipub = Ipub(config)
iret = Iret(config, rollover=True)
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
offsets = {}
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
offsets["step2-p"] = -int(zsk_lifetime.total_seconds() - ipub.total_seconds())
offsets["step2-s"] = 0
offsets["step3-p"] = -int(zsk_lifetime.total_seconds())
offsets["step3-s"] = -int(ipub.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())
steps = [
{
# Step 1.
# Introduce the first key. This will immediately be active.
"zone": "step1.zsk-prepub.autosign",
"keyprops": [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step1-p']}",
],
# Next key event is when the successor ZSK needs to be published.
# That is the ZSK lifetime - prepublication time (minus time
# already passed).
"nextev": zsk_lifetime - ipub - timedelta(days=7),
},
{
# Step 2.
# It is time to pre-publish the successor ZSK.
# ZSK1 goal: omnipresent -> hidden
# ZSK2 goal: hidden -> omnipresent
# ZSK2 dnskey: hidden -> rumoured
"zone": "step2.zsk-prepub.autosign",
"keyprops": [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step2-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured zrrsig:hidden offset:{offsets['step2-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when the successor ZSK becomes OMNIPRESENT.
# That is the DNSKEY TTL plus the zone propagation delay
"nextev": ipub,
},
{
# Step 3.
# Predecessor ZSK is no longer actively signing. Successor ZSK is
# now actively signing.
# ZSK1 zrrsig: omnipresent -> unretentive
# ZSK2 dnskey: rumoured -> omnipresent
# ZSK2 zrrsig: hidden -> rumoured
"zone": "step3.zsk-prepub.autosign",
"keyprops": [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step3-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent zrrsig:unretentive offset:{offsets['step3-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:rumoured offset:{offsets['step3-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when all the RRSIG records have been replaced
# with signatures of the new ZSK, in other words when ZRRSIG
# becomes OMNIPRESENT.
"nextev": iret,
# Set 'smooth' to true so expected signatures of subdomain are
# from the predecessor ZSK.
"smooth": True,
},
{
# Step 4.
# Predecessor ZSK is no longer needed. All RRsets are signed with
# the successor ZSK.
# ZSK1 dnskey: omnipresent -> unretentive
# ZSK1 zrrsig: unretentive -> hidden
# ZSK2 zrrsig: rumoured -> omnipresent
"zone": "step4.zsk-prepub.autosign",
"keyprops": [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step4-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive zrrsig:hidden offset:{offsets['step4-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig: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,
},
{
# Step 5.
# Predecessor ZSK is now removed.
# ZSK1 dnskey: unretentive -> hidden
"zone": "step5.zsk-prepub.autosign",
"keyprops": [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step5-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden zrrsig:hidden offset:{offsets['step5-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step5-s']}",
],
"keyrelationships": [1, 2],
# Next key event is when the new successor needs to be published.
# This is the ZSK lifetime minus Iret minus Ipub minus time
# elapsed.
"nextev": zsk_lifetime - iret - ipub - keyttlprop,
},
{
# Step 6.
# Predecessor ZSK is now purged.
"zone": "step6.zsk-prepub.autosign",
"keyprops": [
f"ksk unlimited {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent offset:{offsets['step6-p']}",
f"zsk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent offset:{offsets['step6-s']}",
],
"nextev": None,
},
]
for step in steps:
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)
ipubc = IpubC(config)
iret = Iret(config, zsk=False, ksk=True)
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
offsets = {}
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
offsets["step2-p"] = -int(ksk_lifetime.total_seconds() - 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())
steps = [
{
# Step 1.
# Introduce the first key. This will immediately be active.
"zone": "step1.ksk-doubleksk.autosign",
"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']}",
],
# 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),
},
{
# Step 2.
# Successor KSK is prepublished (and signs DNSKEY RRset).
# KSK1 goal: omnipresent -> hidden
# KSK2 goal: hidden -> omnipresent
# KSK2 dnskey: hidden -> rumoured
# KSK2 krrsig: hidden -> rumoured
"zone": "step2.ksk-doubleksk.autosign",
"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']}",
],
"keyrelationships": [1, 2],
# Next key event is when the successor KSK becomes OMNIPRESENT.
"nextev": ipub,
},
{
# Step 3.
# The successor DNSKEY RRset has become omnipresent. The
# predecessor DS can be withdrawn and the successor DS can be
# introduced.
# KSK1 ds: omnipresent -> unretentive
# KSK2 dnskey: rumoured -> omnipresent
# KSK2 krrsig: rumoured -> omnipresent
# KSK2 ds: hidden -> rumoured
"zone": "step3.ksk-doubleksk.autosign",
"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']}",
],
"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,
},
{
# Step 4.
# The predecessor DNSKEY may be removed, the successor DS is
# omnipresent.
# KSK1 dnskey: omnipresent -> unretentive
# KSK1 krrsig: omnipresent -> unretentive
# KSK1 ds: unretentive -> hidden
# KSK2 ds: rumoured -> omnipresent
"zone": "step4.ksk-doubleksk.autosign",
"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']}",
],
"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,
},
{
# Step 5.
# The predecessor DNSKEY is long enough removed from the zone it
# has become hidden.
# KSK1 dnskey: unretentive -> hidden
# KSK1 krrsig: unretentive -> hidden
"zone": "step5.ksk-doubleksk.autosign",
"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']}",
],
"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,
},
{
# Step 6.
# Predecessor KSK is now purged.
"zone": "step6.ksk-doubleksk.autosign",
"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']}",
],
"nextev": None,
},
]
for step in steps:
check_rollover_step(server, config, policy, step)
# Test #2375: Scheduled rollovers are happening faster than they can finish.
zone = "three-is-a-crowd.kasp"
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())
isctest.kasp.check_dnssec_verify(server, zone)
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}",
]
expected = isctest.kasp.policy_to_properties(ttl, keyprops)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
isctest.kasp.check_keys(zone, keys, expected)
expected[0].metadata["Successor"] = expected[1].key.tag
expected[1].metadata["Predecessor"] = expected[0].key.tag
isctest.kasp.check_keyrelationships(keys, expected)
for kp in expected:
kp.set_expected_keytimes(config, offset=None)
isctest.kasp.check_keytimes(keys, expected)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks, cdss=cdss)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
# Rollover successor KSK (with DS in rumoured state).
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")
isctest.kasp.check_dnssec_verify(server, zone)
# We now expect four keys (3x KSK, 1x ZSK).
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}",
]
expected = isctest.kasp.policy_to_properties(ttl, keyprops)
keys = isctest.kasp.keydir_to_keylist(zone, server.identifier)
ksks = [k for k in keys if k.is_ksk()]
zsks = [k for k in keys if not k.is_ksk()]
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)
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=policy)
isctest.kasp.check_apex(server, zone, ksks, zsks, cdss=cdss)
isctest.kasp.check_subdomain(server, zone, ksks, zsks)
def test_rollover_csk_roll1(servers):
server = servers["ns3"]
policy = "csk-roll1"
cdss = ["CDNSKEY", "CDS (SHA-384)"]
config = {
"dnskey-ttl": timedelta(hours=1),
"ds-ttl": timedelta(seconds=3600),
"max-zone-ttl": timedelta(days=1),
"parent-propagation-delay": timedelta(hours=1),
"publish-safety": timedelta(hours=1),
"purge-keys": timedelta(hours=1),
"retire-safety": timedelta(hours=2),
"signatures-refresh": timedelta(days=5),
"signatures-validity": timedelta(days=30),
"zone-propagation-delay": timedelta(hours=1),
}
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
csk_lifetime = timedelta(days=31 * 6)
lifetime_policy = int(csk_lifetime.total_seconds())
ipub = Ipub(config)
iretZSK = Iret(config)
iretKSK = Iret(config, zsk=False, ksk=True)
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
signdelay = iretZSK - iretKSK - keyttlprop
offsets = {}
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
offsets["step2-p"] = -int(csk_lifetime.total_seconds() - ipub.total_seconds())
offsets["step2-s"] = 0
offsets["step3-p"] = -int(csk_lifetime.total_seconds())
offsets["step3-s"] = -int(ipub.total_seconds())
offsets["step4-p"] = offsets["step3-p"] - int(iretKSK.total_seconds())
offsets["step4-s"] = offsets["step3-s"] - int(iretKSK.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(signdelay.total_seconds())
offsets["step6-s"] = offsets["step5-s"] - int(signdelay.total_seconds())
offsets["step7-p"] = offsets["step6-p"] - int(keyttlprop.total_seconds())
offsets["step7-s"] = offsets["step6-s"] - int(keyttlprop.total_seconds())
offsets["step8-p"] = offsets["step7-p"] - int(config["purge-keys"].total_seconds())
offsets["step8-s"] = offsets["step7-s"] - int(config["purge-keys"].total_seconds())
steps = [
{
# Step 1.
# Introduce the first key. This will immediately be active.
"zone": "step1.csk-roll1.autosign",
"cdss": cdss,
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
],
# Next key event is when the successor CSK needs to be published
# minus time already elapsed. This is Lcsk - Ipub + Dreg (we ignore
# registration delay).
"nextev": csk_lifetime - ipub - timedelta(days=7),
},
{
# Step 2.
# Successor CSK is prepublished (signs DNSKEY RRset, but not yet
# other RRsets).
# CSK1 goal: omnipresent -> hidden
# CSK2 goal: hidden -> omnipresent
# CSK2 dnskey: hidden -> rumoured
# CSK2 krrsig: hidden -> rumoured
"zone": "step2.csk-roll1.autosign",
"cdss": cdss,
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{offsets['step2-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the successor CSK becomes OMNIPRESENT.
"nextev": ipub,
},
{
# Step 3.
# Successor CSK becomes omnipresent, meaning we can start signing
# the remainder of the zone with the successor CSK, and we can
# submit the DS.
"zone": "step3.csk-roll1.autosign",
"cdss": cdss,
# Predecessor CSK will be removed, so moving to UNRETENTIVE.
# CSK1 zrrsig: omnipresent -> unretentive
# Successor CSK DNSKEY is OMNIPRESENT, so moving ZRRSIG to RUMOURED.
# CSK2 dnskey: rumoured -> omnipresent
# CSK2 krrsig: rumoured -> omnipresent
# CSK2 zrrsig: hidden -> rumoured
# The predecessor DS can be withdrawn and the successor DS can be
# introduced.
# CSK1 ds: omnipresent -> unretentive
# CSK2 ds: hidden -> rumoured
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{offsets['step3-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{offsets['step3-s']}",
],
"keyrelationships": [0, 1],
# 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": iretKSK,
# Set 'smooth' to true so expected signatures of subdomain are
# from the predecessor ZSK.
"smooth": True,
},
{
# Step 4.
"zone": "step4.csk-roll1.autosign",
"cdss": cdss,
# The predecessor CSK is no longer signing the DNSKEY RRset.
# CSK1 krrsig: omnipresent -> unretentive
# The predecessor DS is hidden. The successor DS is now omnipresent.
# CSK1 ds: unretentive -> hidden
# CSK2 ds: rumoured -> omnipresent
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:unretentive zrrsig:unretentive ds:hidden offset:{offsets['step4-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{offsets['step4-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the KRRSIG enters the HIDDEN state.
# This is the DNSKEY TTL plus zone propagation delay.
"nextev": keyttlprop,
# We already swapped the DS in the previous step, so disable ds-swap.
"ds-swap": False,
},
{
# Step 5.
"zone": "step5.csk-roll1.autosign",
"cdss": cdss,
# The predecessor KRRSIG records are now all hidden.
# CSK1 krrsig: unretentive -> hidden
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:hidden zrrsig:unretentive ds:hidden offset:{offsets['step5-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:omnipresent offset:{offsets['step5-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the DNSKEY can be removed. This is when
# all ZRRSIG records have been replaced with signatures of the new
# CSK.
"nextev": signdelay,
},
{
# Step 6.
"zone": "step6.csk-roll1.autosign",
"cdss": cdss,
# The predecessor ZRRSIG records are now all hidden (so the DNSKEY
# can be removed).
# CSK1 dnskey: omnipresent -> unretentive
# CSK1 zrrsig: unretentive -> hidden
# CSK2 zrrsig: rumoured -> omnipresent
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step6-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step6-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the DNSKEY enters the HIDDEN state.
# This is the DNSKEY TTL plus zone propagation delay.
"nextev": keyttlprop,
},
{
# Step 7.
"zone": "step7.csk-roll1.autosign",
"cdss": cdss,
# The predecessor CSK is now completely HIDDEN.
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step7-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step7-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the new successor needs to be published.
# This is the Lcsk, minus time passed since the key started signing,
# minus the prepublication time.
"nextev": csk_lifetime - iretZSK - ipub - keyttlprop,
},
{
# Step 8.
# Predecessor CSK is now purged.
"zone": "step8.csk-roll1.autosign",
"cdss": cdss,
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step8-s']}",
],
"nextev": None,
},
]
for step in steps:
check_rollover_step(server, config, policy, step)
def test_rollover_csk_roll2(servers):
server = servers["ns3"]
policy = "csk-roll2"
cdss = ["CDNSKEY", "CDS (SHA-256)", "CDS (SHA-384)"]
config = {
"dnskey-ttl": timedelta(hours=1),
"ds-ttl": timedelta(seconds=3600),
"max-zone-ttl": timedelta(days=1),
"parent-propagation-delay": timedelta(days=7),
"publish-safety": timedelta(hours=1),
"purge-keys": timedelta(0),
"retire-safety": timedelta(hours=1),
"signatures-refresh": timedelta(hours=12),
"signatures-validity": timedelta(days=1),
"zone-propagation-delay": timedelta(hours=1),
}
alg = os.environ["DEFAULT_ALGORITHM_NUMBER"]
size = os.environ["DEFAULT_BITS"]
csk_lifetime = timedelta(days=31 * 6)
lifetime_policy = int(csk_lifetime.total_seconds())
ipub = Ipub(config)
iret = Iret(config, zsk=True, ksk=True)
iretZSK = Iret(config)
iretKSK = Iret(config, ksk=True)
keyttlprop = config["dnskey-ttl"] + config["zone-propagation-delay"]
offsets = {}
offsets["step1-p"] = -int(timedelta(days=7).total_seconds())
offsets["step2-p"] = -int(csk_lifetime.total_seconds() - ipub.total_seconds())
offsets["step2-s"] = 0
offsets["step3-p"] = -int(csk_lifetime.total_seconds())
offsets["step3-s"] = -int(ipub.total_seconds())
offsets["step4-p"] = offsets["step3-p"] - int(iretZSK.total_seconds())
offsets["step4-s"] = offsets["step3-s"] - int(iretZSK.total_seconds())
offsets["step5-p"] = offsets["step4-p"] - int(
iretKSK.total_seconds() - iretZSK.total_seconds()
)
offsets["step5-s"] = offsets["step4-s"] - int(
iretKSK.total_seconds() - iretZSK.total_seconds()
)
offsets["step6-p"] = offsets["step5-p"] - int(keyttlprop.total_seconds())
offsets["step6-s"] = offsets["step5-s"] - int(keyttlprop.total_seconds())
offsets["step7-p"] = offsets["step6-p"] - int(timedelta(days=90).total_seconds())
offsets["step7-s"] = offsets["step6-s"] - int(timedelta(days=90).total_seconds())
steps = [
{
# Step 1.
# Introduce the first key. This will immediately be active.
"zone": "step1.csk-roll2.autosign",
"cdss": cdss,
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step1-p']}",
],
# Next key event is when the successor CSK needs to be published
# minus time already elapsed. This is Lcsk - Ipub + Dreg (we ignore
# registration delay).
"nextev": csk_lifetime - ipub - timedelta(days=7),
},
{
# Step 2.
# Successor CSK is prepublished (signs DNSKEY RRset, but not yet
# other RRsets).
# CSK1 goal: omnipresent -> hidden
# CSK2 goal: hidden -> omnipresent
# CSK2 dnskey: hidden -> rumoured
# CSK2 krrsig: hidden -> rumoured
"zone": "step2.csk-roll2.autosign",
"cdss": cdss,
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step2-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden offset:{offsets['step2-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the successor CSK becomes OMNIPRESENT.
"nextev": ipub,
},
{
# Step 3.
# Successor CSK becomes omnipresent, meaning we can start signing
# the remainder of the zone with the successor CSK, and we can
# submit the DS.
"zone": "step3.csk-roll2.autosign",
"cdss": cdss,
# Predecessor CSK will be removed, so moving to UNRETENTIVE.
# CSK1 zrrsig: omnipresent -> unretentive
# Successor CSK DNSKEY is OMNIPRESENT, so moving ZRRSIG to RUMOURED.
# CSK2 dnskey: rumoured -> omnipresent
# CSK2 krrsig: rumoured -> omnipresent
# CSK2 zrrsig: hidden -> rumoured
# The predecessor DS can be withdrawn and the successor DS can be
# introduced.
# CSK1 ds: omnipresent -> unretentive
# CSK2 ds: hidden -> rumoured
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:unretentive ds:unretentive offset:{offsets['step3-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:rumoured ds:rumoured offset:{offsets['step3-s']}",
],
"keyrelationships": [0, 1],
# 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": iretZSK,
# Set 'smooth' to true so expected signatures of subdomain are
# from the predecessor ZSK.
"smooth": True,
},
{
# Step 4.
"zone": "step4.csk-roll2.autosign",
"cdss": cdss,
# The predecessor ZRRSIG is HIDDEN. The successor ZRRSIG is
# OMNIPRESENT.
# CSK1 zrrsig: unretentive -> hidden
# CSK2 zrrsig: rumoured -> omnipresent
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:omnipresent krrsig:omnipresent zrrsig:hidden ds:unretentive offset:{offsets['step4-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:rumoured offset:{offsets['step4-s']}",
],
"keyrelationships": [0, 1],
# 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 retire interval of the KSK part (minus)
# time already elapsed).
"nextev": iret - iretZSK,
# We already swapped the DS in the previous step, so disable ds-swap.
"ds-swap": False,
},
{
# Step 5.
"zone": "step5.csk-roll2.autosign",
"cdss": cdss,
# The predecessor DNSKEY can be removed.
# CSK1 dnskey: omnipresent -> unretentive
# CSK1 krrsig: omnipresent -> unretentive
# CSK1 ds: unretentive -> hidden
# The successor key is now fully OMNIPRESENT.
# CSK2 ds: rumoured -> omnipresent
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:unretentive krrsig:unretentive zrrsig:hidden ds:hidden offset:{offsets['step5-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step5-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the DNSKEY enters the HIDDEN state.
# This is the DNSKEY TTL plus zone propagation delay.
"nextev": keyttlprop,
},
{
# Step 6.
"zone": "step6.csk-roll2.autosign",
"cdss": cdss,
# The predecessor CSK is now completely HIDDEN.
# CSK1 dnskey: unretentive -> hidden
# CSK1 krrsig: unretentive -> hidden
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step6-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step6-s']}",
],
"keyrelationships": [0, 1],
# Next key event is when the new successor needs to be published.
# This is the Lcsk, minus time passed since the key was published.
"nextev": csk_lifetime - iret - ipub - keyttlprop,
},
{
# Step 7.
"zone": "step7.csk-roll2.autosign",
"cdss": cdss,
# The predecessor CSK is now completely HIDDEN.
"keyprops": [
f"csk {lifetime_policy} {alg} {size} goal:hidden dnskey:hidden krrsig:hidden zrrsig:hidden ds:hidden offset:{offsets['step7-p']}",
f"csk {lifetime_policy} {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent zrrsig:omnipresent ds:omnipresent offset:{offsets['step7-s']}",
],
"keyrelationships": [0, 1],
"nextev": None,
},
]
for step in steps:
check_rollover_step(server, config, policy, step)