2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-24 19:18:50 +00:00
bind/bin/tests/system/rollover/tests_rollover.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1256 lines
56 KiB
Python
Raw Normal View History

# 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)