diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index bdd0235ba1..13bdbf9e24 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -237,19 +237,21 @@ class KeyProperties: self.timing["PublishCDS"] = self.timing["Published"] + ipubc - if self.metadata["Lifetime"] != 0: + if "Lifetime" in self.metadata and self.metadata["Lifetime"] != 0: self.timing["DeleteCDS"] = ( self.timing["PublishCDS"] + self.metadata["Lifetime"] ) def Iret(self, config): - if self.metadata["Lifetime"] == 0: + if "Lifetime" not in self.metadata or self.metadata["Lifetime"] == 0: return iret = Iret(config, zsk=self.key.is_zsk(), ksk=self.key.is_ksk()) self.timing["Removed"] = self.timing["Retired"] + iret - def set_expected_keytimes(self, config, offset=None, pregenerated=False): + def set_expected_keytimes( + self, config, offset=None, pregenerated=False, migrate=False + ): if self.key is None: raise ValueError("KeyProperties must be attached to a Key") @@ -260,18 +262,24 @@ class KeyProperties: offset = self.properties["offset"] self.timing["Generated"] = self.key.get_timing("Created") - - self.timing["Published"] = self.timing["Generated"] + self.timing["Published"] = self.key.get_timing("Created") if pregenerated: self.timing["Published"] = self.key.get_timing("Publish") - self.timing["Published"] = self.timing["Published"] + offset - self.Ipub(config) + + if migrate: + self.timing["Published"] = self.key.get_timing("Publish") + if self.key.is_ksk(): + self.timing["PublishCDS"] = self.key.get_timing("SyncPublish") + self.timing["Active"] = self.key.get_timing("Activate") + else: + self.timing["Published"] = self.timing["Published"] + offset + self.Ipub(config) + self.IpubC(config) # Set Retired timing metadata if key has lifetime. - if self.metadata["Lifetime"] != 0: + if "Lifetime" in self.metadata and self.metadata["Lifetime"] != 0: self.timing["Retired"] = self.timing["Active"] + self.metadata["Lifetime"] - self.IpubC(config) self.Iret(config) # Key state change times must exist, but since we cannot reliably tell @@ -1485,8 +1493,9 @@ def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]: keyprop.properties["dnskey_ttl"] = ttl keyprop.metadata["Algorithm"] = line[2] keyprop.metadata["Length"] = line[3] - keyprop.metadata["Lifetime"] = 0 - if line[1] != "unlimited": + if line[1] == "unlimited": + keyprop.metadata["Lifetime"] = 0 + elif line[1] != "-": keyprop.metadata["Lifetime"] = int(line[1]) for i in range(4, len(line)): diff --git a/bin/tests/system/migrate2kasp/ns3/kasp.conf.j2 b/bin/tests/system/migrate2kasp/ns3/kasp.conf.j2 new file mode 100644 index 0000000000..55a827bb63 --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns3/kasp.conf.j2 @@ -0,0 +1,104 @@ +/* + * 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. + */ + +dnssec-policy "migrate" { + dnskey-ttl 7200; + + keys { + ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@; + }; +}; + +dnssec-policy "timing-metadata" { + dnskey-ttl 300; + + signatures-refresh P1W; + signatures-validity P2W; + signatures-validity-dnskey P2W; + + keys { + ksk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@; + zsk key-directory lifetime P60D algorithm @DEFAULT_ALGORITHM@; + }; + + // Together 12h + zone-propagation-delay 3600; + max-zone-ttl 11h; + + // Together 3h + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; + +/* + * This policy tests migration from existing keys with 1024 bits RSASHA1 keys + * to ECDSAP256SHA256 keys. + */ +dnssec-policy "migrate-nomatch-algnum" { + dnskey-ttl 300; + + keys { + ksk key-directory lifetime unlimited algorithm ecdsa256; + zsk key-directory lifetime P60D algorithm ecdsa256; + }; + + // Together 12h + zone-propagation-delay 3600; + max-zone-ttl 11h; + + // Together 3h + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; + +/* + * This policy tests migration from existing keys with 2048 bits RSASHA256 keys + * to 3072 bits RSASHA256 keys. + */ +dnssec-policy "migrate-nomatch-alglen" { + dnskey-ttl 300; + + keys { + ksk key-directory lifetime unlimited algorithm rsasha256 3072; + zsk key-directory lifetime P60D algorithm rsasha256 3072; + }; + + // Together 12h + zone-propagation-delay 3600; + max-zone-ttl 11h; + + // Together 3h + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; + +/* + * This policy tests migration from existing KSK and ZSK to CSK. + * The keys clause matches the default policy. + */ +dnssec-policy "migrate-nomatch-kzc" { + dnskey-ttl 300; + + keys { + csk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + }; + + // Together 12h + zone-propagation-delay 3600; + max-zone-ttl 11h; + + // Together 3h + parent-propagation-delay pt1h; + parent-ds-ttl 7200; +}; diff --git a/bin/tests/system/migrate2kasp/ns3/named.conf.j2 b/bin/tests/system/migrate2kasp/ns3/named.conf.j2 new file mode 100644 index 0000000000..a52538b262 --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns3/named.conf.j2 @@ -0,0 +1,111 @@ +/* + * 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. + */ + +// NS3 + +include "kasp.conf"; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +/* These are zones that migrate to dnssec-policy. */ +zone "migrate.kasp" { + type primary; + file "migrate.kasp.db"; + dnssec-policy "migrate"; + inline-signing no; + allow-update { any; }; +}; + +zone "csk.kasp" { + type primary; + file "csk.kasp.db"; + dnssec-policy "default"; + inline-signing no; + allow-update { any; }; +}; + +zone "csk-nosep.kasp" { + type primary; + file "csk-nosep.kasp.db"; + dnssec-policy "default"; + inline-signing no; + allow-update { any; }; +}; + +zone "rumoured.kasp" { + type primary; + file "rumoured.kasp.db"; + dnssec-policy "timing-metadata"; + inline-signing no; + allow-update { any; }; +}; + +zone "omnipresent.kasp" { + type primary; + file "omnipresent.kasp.db"; + dnssec-policy "timing-metadata"; + inline-signing no; + allow-update { any; }; +}; + +zone "no-syncpublish.kasp" { + type primary; + file "no-syncpublish.kasp.db"; + dnssec-policy "timing-metadata"; + inline-signing no; + allow-update { any; }; +}; + +zone "migrate-nomatch-algnum.kasp" { + type primary; + file "migrate-nomatch-algnum.kasp.db"; + dnssec-policy "migrate-nomatch-algnum"; + inline-signing no; + allow-update { any; }; +}; + +zone "migrate-nomatch-alglen.kasp" { + type primary; + file "migrate-nomatch-alglen.kasp.db"; + dnssec-policy "migrate-nomatch-alglen"; + inline-signing no; + allow-update { any; }; +}; + +zone "migrate-nomatch-kzc.kasp" { + type primary; + file "migrate-nomatch-kzc.kasp.db"; + dnssec-policy "migrate-nomatch-kzc"; + inline-signing no; + allow-update { any; }; +}; diff --git a/bin/tests/system/migrate2kasp/ns3/setup.sh b/bin/tests/system/migrate2kasp/ns3/setup.sh new file mode 100644 index 0000000000..0f6cccc879 --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns3/setup.sh @@ -0,0 +1,161 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +echo_i "ns3/setup.sh" + +setup() { + zone="$1" + echo_i "setting up zone: $zone" + zonefile="${zone}.db" + infile="${zone}.db.infile" +} + +# Make lines shorter by storing key states in environment variables. +H="HIDDEN" +R="RUMOURED" +O="OMNIPRESENT" +U="UNRETENTIVE" + +# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy. +setup migrate.kasp +echo "$zone" >>zones +ksktimes="-P now -A now -P sync now" +zsktimes="-P now -A now" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +# Set up Single-Type Signing Scheme zones with auto-dnssec maintain to +# migrate to dnssec-policy. This is a zone that has 'update-check-ksk no;' +# configured, meaning the zone is signed with a single CSK. +setup csk.kasp +echo "$zone" >>zones +csktimes="-P now -A now -P sync now" +CSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 -f KSK $csktimes $zone 2>keygen.out.$zone.1) +cat template.db.in "${CSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK" >>"$infile" +$SIGNER -S -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +setup csk-nosep.kasp +echo "$zone" >>zones +csktimes="-P now -A now -P sync now" +CSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 7200 $csktimes $zone 2>keygen.out.$zone.1) +cat template.db.in "${CSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$CSK" >>"$infile" +$SIGNER -S -z -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy, but this +# time the existing keys do not match the policy. The existing keys are +# RSASHA256 keys, and will be migrated to a dnssec-policy that dictates +# ECDSAP256SHA256 keys. +setup migrate-nomatch-algnum.kasp +echo "$zone" >>zones +Tds="now-3h" # Time according to dnssec-policy that DS will be OMNIPRESENT +Tkey="now-3900s" # DNSKEY TTL + propagation delay +Tsig="now-12h" # Zone's maximum TTL + propagation delay +ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}" +zsktimes="-P ${Tkey} -A ${Tsig}" +KSK=$($KEYGEN -a RSASHA256 -b 2048 -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a RSASHA256 -b 2048 -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone 5 "$KSK" >>"$infile" +private_type_record $zone 5 "$ZSK" >>"$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +# Set up a zone with auto-dnssec maintain to migrate to dnssec-policy, but this +# time the existing keys do not match the policy. The existing keys are +# 2048 bits RSASHA256 keys, and will be migrated to a dnssec-policy that +# dictates 3072 bits RSASHA256 keys. +setup migrate-nomatch-alglen.kasp +echo "$zone" >>zones +Tds="now-3h" # Time according to dnssec-policy that DS will be OMNIPRESENT +Tkey="now-3900s" # DNSKEY TTL + propagation delay +Tsig="now-12h" # Zone's maximum TTL + propagation delay +ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}" +zsktimes="-P ${Tkey} -A ${Tsig}" +KSK=$($KEYGEN -a RSASHA256 -b 2048 -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a RSASHA256 -b 2048 -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone 5 "$KSK" >>"$infile" +private_type_record $zone 5 "$ZSK" >>"$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +# Set up a zone with auto-dnssec maintain to migrate to default dnssec-policy. +# The zone is signed with KSK/ZSK split, but the dnssec-policy uses CSK. +setup migrate-nomatch-kzc.kasp +echo "$zone" >>zones +Tds="now-3h" # Time according to dnssec-policy that DS will be OMNIPRESENT +Tkey="now-3900s" # DNSKEY TTL + propagation delay +Tsig="now-12h" # Zone's maximum TTL + propagation delay +ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}" +zsktimes="-P ${Tkey} -A ${Tsig}" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +cp $infile $zonefile +private_type_record $zone 5 "$KSK" >>"$infile" +private_type_record $zone 5 "$ZSK" >>"$infile" +$SIGNER -PS -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone.1 2>&1 + +# +# Set up zones to test time metadata correctly sets state. +# + +# Key states expected to be rumoured after migration. +setup rumoured.kasp +echo "$zone" >>zones +Tds="now-2h" +Tkey="now-300s" +Tsig="now-11h" +ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}" +zsktimes="-P ${Tkey} -A ${Tsig}" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +# Key states expected to be omnipresent after migration. +setup omnipresent.kasp +echo "$zone" >>zones +Tds="now-3h" # Time according to dnssec-policy that DS will be OMNIPRESENT +Tkey="now-3900s" # DNSKEY TTL + propagation delay +Tsig="now-12h" # Zone's maximum TTL + propagation delay +ksktimes="-P ${Tkey} -A ${Tkey} -P sync ${Tds}" +zsktimes="-P ${Tkey} -A ${Tsig}" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 + +# Key states expected to be omnipresent after migration, except DS because -P sync is missing. +setup no-syncpublish.kasp +echo "$zone" >>zones +Tsig="now-12h" # Zone's maximum TTL + propagation delay +ksktimes="-P ${Tsig} -A ${Tsig}" +zsktimes="-P ${Tsig} -A ${Tsig}" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) +cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$KSK" >>"$infile" +private_type_record $zone $DEFAULT_ALGORITHM_NUMBER "$ZSK" >>"$infile" +$SIGNER -S -x -s now-1h -e now+2w -o $zone -O full -f $zonefile $infile >signer.out.$zone.1 2>&1 diff --git a/bin/tests/system/migrate2kasp/ns3/template.db.in b/bin/tests/system/migrate2kasp/ns3/template.db.in new file mode 100644 index 0000000000..5850e016b9 --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns3/template.db.in @@ -0,0 +1,26 @@ +; 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. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns3 +ns3 A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 diff --git a/bin/tests/system/migrate2kasp/ns4/named.conf.j2 b/bin/tests/system/migrate2kasp/ns4/named.conf.j2 new file mode 100644 index 0000000000..a36549292b --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns4/named.conf.j2 @@ -0,0 +1,92 @@ +/* + * 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. + */ + +// NS4 + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + allow-transfer { any; }; + recursion no; + key-directory "."; + dnssec-validation no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +dnssec-policy "rsasha256" { + keys { + zsk key-directory lifetime P3M algorithm 8 2048; + ksk key-directory lifetime P1Y algorithm 8 2048; + }; + + dnskey-ttl 300; + publish-safety 1h; + retire-safety 1h; + + signatures-refresh 5d; + signatures-validity 14d; + signatures-validity-dnskey 14d; + + max-zone-ttl 1d; + zone-propagation-delay 300; + + parent-ds-ttl 86400; + parent-propagation-delay 3h; +}; + +key "external" { + algorithm @DEFAULT_HMAC@; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "internal" { + algorithm @DEFAULT_HMAC@; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +view "ext" { + match-clients { key "external"; }; + + zone "view-rsasha256.kasp" { + type primary; + file "view-rsasha256.kasp.ext.db"; + dnssec-policy "rsasha256"; + inline-signing no; + allow-update { any; }; + }; +}; + +view "int" { + match-clients { key "internal"; }; + + zone "view-rsasha256.kasp" { + type primary; + file "view-rsasha256.kasp.int.db"; + dnssec-policy "rsasha256"; + inline-signing no; + allow-update { any; }; + }; +}; diff --git a/bin/tests/system/migrate2kasp/ns4/setup.sh b/bin/tests/system/migrate2kasp/ns4/setup.sh new file mode 100644 index 0000000000..914e001539 --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns4/setup.sh @@ -0,0 +1,46 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +echo_i "ns4/setup.sh" + +# Make lines shorter by storing key states in environment variables. +H="HIDDEN" +R="RUMOURED" +O="OMNIPRESENT" +U="UNRETENTIVE" + +zone="view-rsasha256.kasp" +algo="RSASHA256" +num="8" +echo "$zone" >>zones + +# Set up zones in views with auto-dnssec maintain to migrate to dnssec-policy. +# The keys for these zones are in use long enough that they should start a +# rollover for the ZSK (P3M), but not long enough to initiate a KSK rollover (P1Y). +ksktimes="-P -93d -A -93d -P sync -93d" +zsktimes="-P -93d -A -93d" +KSK=$($KEYGEN -a $algo -L 300 -b 2048 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $algo -L 300 -b 2048 $zsktimes $zone 2>keygen.out.$zone.2) + +echo_i "setting up zone $zone (external)" +view="ext" +zonefile="${zone}.${view}.db" +cat template.$view.db.in "${KSK}.key" "${ZSK}.key" >"$zonefile" + +echo_i "setting up zone $zone (internal)" +view="int" +zonefile="${zone}.${view}.db" +cat template.$view.db.in "${KSK}.key" "${ZSK}.key" >"$zonefile" diff --git a/bin/tests/system/migrate2kasp/ns4/template.ext.db.in b/bin/tests/system/migrate2kasp/ns4/template.ext.db.in new file mode 100644 index 0000000000..06a714830f --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns4/template.ext.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns4 +ns4 A 10.53.0.4 + +view TXT "external" + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 diff --git a/bin/tests/system/migrate2kasp/ns4/template.int.db.in b/bin/tests/system/migrate2kasp/ns4/template.int.db.in new file mode 100644 index 0000000000..735c6bc7e4 --- /dev/null +++ b/bin/tests/system/migrate2kasp/ns4/template.int.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 +@ IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns4 +ns4 A 10.53.0.4 + +view TXT "internal" + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 diff --git a/bin/tests/system/migrate2kasp/setup.sh b/bin/tests/system/migrate2kasp/setup.sh new file mode 100644 index 0000000000..bf03478cd1 --- /dev/null +++ b/bin/tests/system/migrate2kasp/setup.sh @@ -0,0 +1,27 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../conf.sh + +set -e + +# Setup zones +( + cd ns3 + $SHELL setup.sh +) +( + cd ns4 + $SHELL setup.sh +) diff --git a/bin/tests/system/migrate2kasp/tests_migrate2kasp.py b/bin/tests/system/migrate2kasp/tests_migrate2kasp.py new file mode 100644 index 0000000000..92642c1f29 --- /dev/null +++ b/bin/tests/system/migrate2kasp/tests_migrate2kasp.py @@ -0,0 +1,358 @@ +# 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 isctest +import isctest.mark + +pytestmark = pytest.mark.extra_artifacts( + [ + "*.axfr", + "*.created", + "created.key-*", + "dig.out*", + "ns*/*.mkeys*", + "ns*/dsset-*", + "ns*/K*.key", + "ns*/K*.private", + "ns*/K*.state", + "ns*/kasp.conf", + "ns*/keygen.out*", + "ns*/managed-keys.bind*", + "ns*/named.conf", + "ns*/named.memstats", + "ns*/named.run", + "ns*/signer.out*", + "ns*/zones", + "ns*/*.db", + "ns*/*.db.infile", + "ns*/*.db.jbk", + "ns*/*.db.jnl", + "ns*/*.db.signed*", + "python.out.*", + "retired.*", + "rndc.dnssec.*", + "unused.key*", + "verify.out.*", + ] +) + +default_config = { + "dnskey-ttl": timedelta(hours=1), + "ds-ttl": timedelta(days=1), + "key-directory": "{keydir}", + "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), +} + +standard_config = { + "dnskey-ttl": timedelta(seconds=7200), + "ds-ttl": timedelta(days=1), + "key-directory": "{keydir}", + "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), +} + +timing_config = { + "dnskey-ttl": timedelta(seconds=300), + "ds-ttl": timedelta(seconds=7200), + "key-directory": "{keydir}", + "max-zone-ttl": timedelta(hours=11), + "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(seconds=3600), +} + +migrate_config = { + "dnskey-ttl": timedelta(seconds=300), + "ds-ttl": timedelta(seconds=7200), + "key-directory": "{keydir}", + "max-zone-ttl": timedelta(hours=11), + "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(seconds=3600), +} + +view_config = { + "dnskey-ttl": timedelta(seconds=300), + "ds-ttl": timedelta(seconds=86400), + "key-directory": "{keydir}", + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=3), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(seconds=300), +} + +lifetime = { + "P60D": int(timedelta(days=60).total_seconds()), + "P3M": int(timedelta(days=31 * 3).total_seconds()), + "P1Y": int(timedelta(days=365).total_seconds()), +} + + +@pytest.mark.parametrize( + "params", + [ + # Testing good migration (KSK/ZSK). + pytest.param( + { + "zone": "migrate.kasp", + "policy": "migrate", + "server": "ns3", + "config": standard_config, + "offset": 0, + "key-properties": [ + f"ksk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:rumoured", + f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + }, + id="migrate.kasp", + ), + # Testing a good migration (CSK). + pytest.param( + { + "zone": "csk.kasp", + "policy": "default", + "server": "ns3", + "config": default_config, + "offset": 0, + "key-properties": [ + f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:rumoured", + ], + }, + id="csk.kasp", + ), + # Testing a good migration (CSK, no SEP). + pytest.param( + { + "zone": "csk-nosep.kasp", + "policy": "default", + "server": "ns3", + "config": default_config, + "offset": 0, + "key-properties": [ + f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:rumoured", + ], + }, + id="csk-nosep.kasp", + ), + # Testing key states derived from timing metadata: rumoured. + pytest.param( + { + "zone": "rumoured.kasp", + "policy": "timing-metadata", + "server": "ns3", + "config": timing_config, + "offset": -timedelta(seconds=300), + "key-properties": [ + f"ksk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:rumoured", + f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + }, + id="rumoured.kasp", + ), + # Testing key states derived from timing metadata: omnipresent. + pytest.param( + { + "zone": "omnipresent.kasp", + "policy": "timing-metadata", + "server": "ns3", + "config": timing_config, + "offset": -timedelta(seconds=3900), + "key-properties": [ + f"ksk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ], + }, + id="omnipresent.kasp", + ), + # Testing key states derived from timing metadata: no SyncPublish. + pytest.param( + { + "zone": "no-syncpublish.kasp", + "policy": "timing-metadata", + "server": "ns3", + "config": timing_config, + "offset": -timedelta(hours=12), + "key-properties": [ + f"ksk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:rumoured", + f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ], + }, + id="no-syncpublish.kasp", + ), + # Test migration to dnssec-policy, existing keys do not match key algorithm. + pytest.param( + { + "zone": "migrate-nomatch-algnum.kasp", + "policy": "migrate-nomatch-algnum", + "server": "ns3", + "config": migrate_config, + "offset": -timedelta(seconds=3900), + "key-properties": [ + "ksk - 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + "zsk - 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"ksk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + f"zsk {lifetime['P60D']} {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + }, + id="migrate-nomatch-algnum.kasp", + ), + # Test migration to dnssec-policy, existing keys do not match key length. + pytest.param( + { + "zone": "migrate-nomatch-alglen.kasp", + "policy": "migrate-nomatch-alglen", + "server": "ns3", + "config": migrate_config, + "offset": -timedelta(seconds=3900), + "key-properties": [ + "ksk - 8 2048 goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + "zsk - 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent", + "ksk 0 8 3072 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + # This key is considered to be prepublished, so it is not yet signing. + f"zsk {lifetime['P60D']} 8 3072 goal:omnipresent dnskey:rumoured zrrsig:hidden", + ], + }, + id="migrate-nomatch-alglen.kasp", + ), + # Test migration to dnssec-policy, existing keys do not match role (KSK/ZSK -> CSK). + pytest.param( + { + "zone": "migrate-nomatch-kzc.kasp", + "policy": "migrate-nomatch-kzc", + "server": "ns3", + "config": migrate_config, + "offset": -timedelta(seconds=3900), + "key-properties": [ + f"ksk - {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:hidden dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk - {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:hidden dnskey:omnipresent zrrsig:omnipresent", + # This key is considered to be prepublished, so it is not yet signing, nor is the DS introduced. + f"csk 0 {os.environ['DEFAULT_ALGORITHM_NUMBER']} {os.environ['DEFAULT_BITS']} goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:hidden ds:hidden", + ], + }, + id="migrate-nomatch-kzc.kasp", + ), + # Test good migration with views. + pytest.param( + { + "zone": "view-rsasha256.kasp", + "policy": "rsasha256", + "server": "ns4", + "config": view_config, + "offset": -timedelta(days=31 * 3), + "key-properties": [ + f"zsk {lifetime['P3M']} 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"zsk {lifetime['P3M']} 8 2048 goal:omnipresent dnskey:rumoured zrrsig:hidden", + f"ksk {lifetime['P1Y']} 8 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + ], + "view": "ext", + "tsig": "external:YPfMoAk6h+3iN8MDRQC004iSNHY=", + }, + id="view-rsasha256.kasp (external)", + ), + pytest.param( + { + "zone": "view-rsasha256.kasp", + "policy": "rsasha256", + "server": "ns4", + "config": view_config, + "offset": -timedelta(days=31 * 3), + "key-properties": [ + f"zsk {lifetime['P3M']} 8 2048 goal:hidden dnskey:omnipresent zrrsig:omnipresent", + f"zsk {lifetime['P3M']} 8 2048 goal:omnipresent dnskey:rumoured zrrsig:hidden", + f"ksk {lifetime['P1Y']} 8 2048 goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + ], + "view": "int", + "tsig": "internal:4xILSZQnuO1UKubXHkYUsvBRPu8=", + }, + id="view-rsasha256.kasp (internal)", + ), + ], +) +def test_migrate2kasp_case(servers, params): + # Get test parameters. + zone = params["zone"] + policy = params["policy"] + server = servers[params["server"]] + keydir = server.identifier + view = params.get("view", None) + tsig = None + if "tsig" in params: + secret = params["tsig"] + tsig = f"{os.environ['DEFAULT_HMAC']}:{secret}" + + isctest.kasp.wait_keymgr_done(server, zone) + + params["config"]["key-directory"] = params["config"]["key-directory"].replace( + "{keydir}", keydir + ) + ttl = int(params["config"]["dnskey-ttl"].total_seconds()) + + # Test case. + isctest.log.info(f"check test case zone {zone} policy {policy}") + + # First make sure the zone is signed. + isctest.kasp.check_dnssec_verify(server, zone, tsig=tsig) + + # Key properties. + expected = isctest.kasp.policy_to_properties(ttl=ttl, keys=params["key-properties"]) + + # Special case: CSK without SEP bit set. + if zone == "csk-nosep.kasp": + expected[0].properties["role_full"] = "zone-signing" + expected[0].properties["flags"] = 256 + + # Key files. + keys = isctest.kasp.keydir_to_keylist(zone, params["config"]["key-directory"]) + 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 = params["offset"] if "offset" in params else None + + for expect in expected: + expect.set_expected_keytimes(params["config"], offset=offset, migrate=True) + + isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy, view=view) + isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig) + isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig) + + if "additional-tests" in params: + for additional_test in params["additional-tests"]: + callback = additional_test["callback"] + arguments = additional_test["arguments"] + callback(*arguments, params=params, ksks=ksks, zsks=zsks)