diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 5b39b9fffd..fc00a4df0c 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -1108,6 +1108,98 @@ def verify_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig= return True +def verify_rrsig_is_refreshed( + server, fqdn, zonefile, qname, qtype, ksks, zsks, tsig=None +): + """ + Verify signature for RRset has been refreshed. + """ + response = _query(server, qname, qtype, tsig=tsig) + + if response.rcode() != dns.rcode.NOERROR: + return False + + rrtype = dns.rdatatype.to_text(qtype) + match = f"{qname}. {DEFAULT_TTL} IN {rrtype}" + rrsigs = [] + for rrset in response.answer: + if rrset.match( + dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype + ): + rrsigs.append(rrset) + elif not match in rrset.to_text(): + return False + + if len(rrsigs) == 0: + return False + + tmp_zonefile = f"{zonefile}.tmp" + isctest.run.cmd( + [ + os.environ["CHECKZONE"], + "-D", + "-q", + "-o", + tmp_zonefile, + "-f", + "raw", + fqdn, + zonefile, + ], + ) + + zone = dns.zone.from_file(tmp_zonefile, fqdn) + for rrsig in rrsigs: + if isctest.util.zone_contains(zone, rrsig): + return False + + # Zone is updated, ready to verify the signatures. + check_signatures(rrsigs, qtype, fqdn, ksks, zsks) + + return True + + +def verify_rrsig_is_reused(server, fqdn, zonefile, qname, qtype, ksks, zsks, tsig=None): + """ + Verify signature for RRset has been reused. + """ + response = _query(server, qname, qtype, tsig=tsig) + + assert response.rcode() == dns.rcode.NOERROR + + rrtype = dns.rdatatype.to_text(qtype) + match = f"{qname}. {DEFAULT_TTL} IN {rrtype}" + rrsigs = [] + for rrset in response.answer: + if rrset.match( + dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype + ): + rrsigs.append(rrset) + else: + assert match in rrset.to_text() + + tmp_zonefile = f"{zonefile}.tmp" + isctest.run.cmd( + [ + os.environ["CHECKZONE"], + "-D", + "-q", + "-o", + tmp_zonefile, + "-f", + "raw", + fqdn, + zonefile, + ], + ) + + zone = dns.zone.from_file(tmp_zonefile, dns.name.from_text(fqdn), relativize=False) + for rrsig in rrsigs: + assert isctest.util.zone_contains(zone, rrsig) + + check_signatures(rrsigs, qtype, fqdn, ksks, zsks) + + def next_key_event_equals(server, zone, next_event): if next_event is None: # No next key event check. @@ -1202,6 +1294,7 @@ def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]: Then, optional data for specific tests may follow: - "goal", "dnskey", "krrsig", "zrrsig", "ds", followed by a value, sets the given state to the specific value + - "missing", set if the private key file for this key is not available. - "offset", an offset for testing key rollover timings """ proplist = [] @@ -1252,6 +1345,8 @@ def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]: elif line[i].startswith("offset:"): keyval = line[i].split(":") keyprop.properties["offset"] = timedelta(seconds=int(keyval[1])) + elif line[i] == "missing": + keyprop.properties["private"] = False else: assert False, f"undefined optional data {line[i]}" diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index 76fe7b4a81..b6062dad22 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -200,13 +200,14 @@ $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" $SIGNER -PS -z -x -s now-2w -e now-1mi -o $zone -f "${zonefile}" $infile >signer.out.$zone.1 2>&1 +# Treat the next zones as if they were signed six months ago. +T="now-6mo" +keytimes="-P $T -A $T" + # These signatures are set to expire long in the past, update immediately. setup expired-sigs.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -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) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -217,8 +218,6 @@ $SIGNER -PS -x -s now-2mo -e now-1mo -o $zone -O raw -f "${zonefile}.signed" $in # The DNSKEY's TTLs do not match the policy. setup dnskey-ttl-mismatch.autosign -T="now-6mo" -keytimes="-P $T -A $T" KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 -f KSK $keytimes $zone 2>keygen.out.$zone.1) ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 30 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 @@ -229,11 +228,8 @@ $SIGNER -PS -x -o $zone -O raw -f "${zonefile}.signed" $infile >signer.out.$zone # These signatures are still good, and can be reused. setup fresh-sigs.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -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) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -244,11 +240,8 @@ $SIGNER -S -x -s now-1h -e now+2w -o $zone -O raw -f "${zonefile}.signed" $infil # These signatures are still good, but not fresh enough, update immediately. setup unfresh-sigs.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -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) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -259,11 +252,13 @@ $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infil # These signatures are still good, but the private KSK is missing. setup ksk-missing.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" +# KSK file will be gone missing, so we set expected times during setup. +TI="now+550d" # Lifetime of 2 years minus 6 months equals 550 days +TD="now+13226h" # 550 days plus retire time of 1 day 2 hours equals 13226 hours +TS="now-257755mi" # 6 months minus 1 day, 5 minutes equals 257695 minutes +ksktimes="$keytimes -P sync $TS -I $TI -D $TD" 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) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -278,10 +273,11 @@ rm -f "${KSK}".private # These signatures are still good, but the private ZSK is missing. setup zsk-missing.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) +# ZSK file will be gone missing, so we set expected times during setup. +TI="now+185d" # Lifetime of 1 year minus 6 months equals 185 days +TD="now+277985mi" # 185 days plus retire time (sign delay, retire safety, propagation, zone TTL) +zsktimes="$keytimes -I $TI -D $TD" +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $zsktimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 @@ -298,11 +294,8 @@ rm -f "${ZSK}".private # These signatures are still good, but the key files will be removed # before a second run of reconfiguring keys. setup keyfiles-missing.autosign -T="now-6mo" -ksktimes="-P $T -A $T -P sync $T" -zsktimes="-P $T -A $T" -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) +KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $keytimes $zone 2>keygen.out.$zone.1) +ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 $keytimes $zone 2>keygen.out.$zone.2) $SETTIME -s -g $O -d $O $T -k $O $T -r $O $T "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O $T -z $O $T "$ZSK" >settime.out.$zone.2 2>&1 cat template.db.in "${KSK}.key" "${ZSK}.key" >"$infile" @@ -313,7 +306,6 @@ $SIGNER -S -x -s now-1w -e now+1w -o $zone -O raw -f "${zonefile}.signed" $infil # These signatures are already expired, and the private ZSK is retired. setup zsk-retired.autosign -T="now-6mo" ksktimes="-P $T -A $T -P sync $T" zsktimes="-P $T -A $T -I now" KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 300 -f KSK $ksktimes $zone 2>keygen.out.$zone.1) diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 8c6768b428..ad2b49fb0d 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -514,47 +514,6 @@ check_apex check_subdomain dnssec_verify -# -# Zone: secondary.kasp. -# -set_zone "secondary.kasp" -set_policy "rsasha256" "3" "1234" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_algorithm_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Update zone. -n=$((n + 1)) -echo_i "check that we correctly sign the zone after IXFR for zone ${ZONE} ($n)" -ret=0 -cp ns2/secondary.kasp.db.in2 ns2/secondary.kasp.db -rndccmd 10.53.0.2 reload "$ZONE" >/dev/null || log_error "rndc reload zone ${ZONE} failed" - -_wait_for_done_subdomains() { - ret=0 - dig_with_opts "a.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.a" || return 1 - grep "status: NOERROR" "dig.out.$DIR.test$n.a" >/dev/null || return 1 - grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.11" "dig.out.$DIR.test$n.a" >/dev/null || return 1 - check_signatures $_qtype "dig.out.$DIR.test$n.a" "ZSK" - if [ $ret -gt 0 ]; then return $ret; fi - - dig_with_opts "d.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.d" || return 1 - grep "status: NOERROR" "dig.out.$DIR.test$n.d" >/dev/null || return 1 - grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*10\.0\.0\.4" "dig.out.$DIR.test$n.d" >/dev/null || return 1 - check_signatures $_qtype "dig.out.$DIR.test$n.d" "ZSK" - return $ret -} -retry_quiet 5 _wait_for_done_subdomains || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - # TODO: we might want to test: # - configuring a zone with too many active keys (should trigger retire). # - configuring a zone with keys not matching the policy. @@ -593,10 +552,10 @@ set_keytimes_autosign_policy() { } # -# Zone: expired-sigs.autosign. +# Zone: zsk-retired.autosign. # -set_zone "expired-sigs.autosign" -set_policy "autosign" "2" "300" +set_zone "zsk-retired.autosign" +set_policy "autosign" "3" "300" set_server "ns3" "10.53.0.3" # Key properties. key_clear "KEY1" @@ -625,181 +584,6 @@ set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent" # Expect only two keys. key_clear "KEY3" key_clear "KEY4" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Verify all signatures have been refreshed. -check_rrsig_refresh() { - # Apex. - _qtypes="DNSKEY SOA NS NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" - ret=0 - dig_with_opts "$ZONE" "@${SERVER}" "$_qtype" >"dig.out.$DIR.test$n" || log_error "dig ${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - # If this exact RRSIG is also in the zone file it is not refreshed. - _rrsig=$(cat "rrsig.out.$ZONE.$_qtype") - grep "${_rrsig}" "${DIR}/${ZONE}.db" >/dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - - # Below apex. - _labels="a b c ns3" - for _label in $_labels; do - _qtypes="A NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_label} ${_qtype} rrsig is refreshed correctly for zone ${ZONE} ($n)" - ret=0 - dig_with_opts "${_label}.${ZONE}" "@${SERVER}" "$_qtype" >"dig.out.$DIR.test$n" || log_error "dig ${_label}.${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - _rrsig=$(cat "rrsig.out.$ZONE.$_qtype") - grep "${_rrsig}" "${DIR}/${ZONE}.db" >/dev/null && log_error "RRSIG (${_qtype}) not refreshed in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - done -} - -check_rrsig_refresh - -# -# Zone: fresh-sigs.autosign. -# -set_zone "fresh-sigs.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify - -# Verify signature reuse. -check_rrsig_reuse() { - # Apex. - _qtypes="NS NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" - ret=0 - dig_with_opts "$ZONE" "@${SERVER}" "$_qtype" >"dig.out.$DIR.test$n" || log_error "dig ${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - # If this exact RRSIG is also in the signed zone file it is not refreshed. - _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' <"rrsig.out.$ZONE.$_qtype") - $CHECKZONE -f raw -F text -s full -o zone.out.${ZONE}.test$n "${ZONE}" "${DIR}/${ZONE}.db.signed" >/dev/null - grep "${_rrsig}" zone.out.${ZONE}.test$n >/dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - - # Below apex. - _labels="a b c ns3" - for _label in $_labels; do - _qtypes="A NSEC" - for _qtype in $_qtypes; do - n=$((n + 1)) - echo_i "check ${_label} ${_qtype} rrsig is reused correctly for zone ${ZONE} ($n)" - ret=0 - dig_with_opts "${_label}.${ZONE}" "@${SERVER}" "$_qtype" >"dig.out.$DIR.test$n" || log_error "dig ${_label}.${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || log_error "mismatch status in DNS response" - grep "${ZONE}\..*IN.*RRSIG.*${_qtype}.*${ZONE}" "dig.out.$DIR.test$n" >"rrsig.out.$ZONE.$_qtype" || log_error "missing RRSIG (${_qtype}) record in response" - # If this exact RRSIG is also in the signed zone file it is not refreshed. - _rrsig=$(awk '{print $5, $6, $7, $8, $9, $10, $11, $12, $13, $14;}' <"rrsig.out.$ZONE.$_qtype") - $CHECKZONE -f raw -F text -s full -o zone.out.${ZONE}.test$n "${ZONE}" "${DIR}/${ZONE}.db.signed" >/dev/null - grep "${_rrsig}" zone.out.${ZONE}.test$n >/dev/null || log_error "RRSIG (${_qtype}) not reused in zone ${ZONE}" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - done - done -} - -check_rrsig_reuse - -# -# Zone: unfresh-sigs.autosign. -# -set_zone "unfresh-sigs.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_autosign_policy -check_keytimes -check_apex -check_subdomain -dnssec_verify -check_rrsig_refresh - -# -# Zone: ksk-missing.autosign. -# -set_zone "ksk-missing.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. -# Skip checking the private file, because it is missing. -key_set "KEY1" "PRIVATE" "no" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -check_apex -check_subdomain -dnssec_verify - -# Restore the PRIVATE variable. -key_set "KEY1" "PRIVATE" "yes" - -# -# Zone: zsk-missing.autosign. -# -set_zone "zsk-missing.autosign" -set_policy "autosign" "2" "300" -set_server "ns3" "10.53.0.3" -# Key properties, timings and states same as above. -# Skip checking the private file, because it is missing. -key_set "KEY2" "PRIVATE" "no" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -# For the apex, we expect the SOA to be signed with the KSK because the ZSK is -# offline. Temporary treat KEY1 as a zone signing key too. -set_keyrole "KEY1" "csk" -set_zonesigning "KEY1" "yes" -set_zonesigning "KEY2" "no" -check_apex -set_keyrole "KEY1" "ksk" -set_zonesigning "KEY1" "no" -set_zonesigning "KEY2" "yes" -check_subdomain -dnssec_verify - -# Restore the PRIVATE variable. -key_set "KEY2" "PRIVATE" "yes" - -# -# Zone: zsk-retired.autosign. -# -set_zone "zsk-retired.autosign" -set_policy "autosign" "3" "300" -set_server "ns3" "10.53.0.3" # The third key is not yet expected to be signing. set_keyrole "KEY3" "zsk" set_keylifetime "KEY3" "31536000" @@ -846,7 +630,7 @@ check_keytimes check_apex check_subdomain dnssec_verify -check_rrsig_refresh +#check_rrsig_refresh # Load again, make sure the purged key is not an issue when verifying keys. echo_i "load keys for $ZONE, making sure a recently purged key is not an issue when verifying keys ($n)" @@ -857,164 +641,6 @@ grep "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) -# -# Zone: legacy-keys.kasp. -# -set_zone "legacy-keys.kasp" -# This zone has two active keys and two old keys left in key directory, so -# expect 4 key files. -set_policy "migrate-to-dnssec-policy" "4" "1234" -set_server "ns3" "10.53.0.3" - -# Key properties. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "16070400" -set_keyalgorithm "KEY1" "8" "RSASHA256" "2048" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "16070400" -set_keyalgorithm "KEY2" "8" "RSASHA256" "2048" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" -# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. -# ZSK: DNSKEY, RRSIG (zsk) published. -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "rumoured" -set_keystate "KEY1" "STATE_KRRSIG" "rumoured" -set_keystate "KEY1" "STATE_DS" "hidden" - -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "rumoured" -set_keystate "KEY2" "STATE_ZRRSIG" "rumoured" -# Two keys only. -key_clear "KEY3" -key_clear "KEY4" - -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" - -# Make sure the correct legacy keys were used (and not the removed predecessor -# keys). -n=$((n + 1)) -echo_i "check correct keys were used when migrating zone ${ZONE} to dnssec-policy ($n)" -ret=0 -kskfile=$(cat ns3/legacy-keys.kasp.ksk) -basefile=$(key_get KEY1 BASEFILE) -echo_i "filename: $basefile (expect $kskfile)" -test "$DIR/$kskfile" = "$basefile" || ret=1 -zskfile=$(cat ns3/legacy-keys.kasp.zsk) -basefile=$(key_get KEY2 BASEFILE) -echo_i "filename: $basefile (expect $zskfile)" -test "$DIR/$zskfile" = "$basefile" || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -# KSK times. -created=$(key_get KEY1 CREATED) -keyfile=$(key_get KEY1 BASEFILE) -grep "; Publish:" "${keyfile}.key" >published.test${n}.key1 -published=$(awk '{print $3}' published.test${n}.key2 -published=$(awk '{print $3}' /dev/null || log_error "rndc loadkeys zone ${ZONE} failed" -wait_for_log 3 "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" $DIR/named.run || ret=1 -# Check keys again, make sure no new keys are created. -set_policy "autosign" "0" "300" -key_clear "KEY1" -key_clear "KEY2" -check_keys -# Zone is still signed correctly. -dnssec_verify - # # Test dnssec-policy inheritance. # diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py index 0f93a690ee..eba8e73277 100644 --- a/bin/tests/system/kasp/tests_kasp.py +++ b/bin/tests/system/kasp/tests_kasp.py @@ -80,9 +80,11 @@ pytestmark = pytest.mark.extra_artifacts( ) -def check_all(server, zone, policy, ksks, zsks, tsig=None): +def check_all(server, zone, policy, ksks, zsks, zsk_missing=False, tsig=None): isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy) - isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig) + isctest.kasp.check_apex( + server, zone, ksks, zsks, zsk_missing=zsk_missing, tsig=tsig + ) isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig) isctest.kasp.check_dnssec_verify(server, zone, tsig=tsig) @@ -169,6 +171,128 @@ def test_kasp_cases(servers): f"zsk {lifetime['P1Y']} {alg} {sizes[2]} goal:omnipresent dnskey:rumoured zrrsig:rumoured", ] + # Additional test functions. + def test_ixfr_is_signed( + expected_updates, zone=None, policy=None, ksks=None, zsks=None + ): + isctest.log.info(f"check that the zone {zone} is correctly signed after ixfr") + isctest.log.debug( + f"expected updates {expected_updates} policy {policy} ksks {ksks} zsks {zsks}" + ) + + shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db") + servers["ns2"].rndc(f"reload {zone}", log=False) + + def update_is_signed(): + parts = update.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + rdata = parts[2] + return isctest.kasp.verify_update_is_signed( + server, zone, qname, qtype, rdata, ksks, zsks + ) + + for update in expected_updates: + isctest.run.retry_with_timeout(update_is_signed, timeout=5) + + def test_rrsig_refresh(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info(f"check that the zone {zone} refreshes expired signatures") + + def rrsig_is_refreshed(): + parts = query.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + return isctest.kasp.check_rrsig_is_refreshed( + server, zone, f"ns3/{zone}.db.signed", qname, qtype, ksks, zsks + ) + + queries = [ + f"{zone} DNSKEY", + f"{zone} SOA", + f"{zone} NS", + f"{zone} NSEC", + f"a.{zone} A", + f"a.{zone} NSEC", + f"b.{zone} A", + f"b.{zone} NSEC", + f"c.{zone} A", + f"c.{zone} NSEC", + f"ns3.{zone} A", + f"ns3.{zone} NSEC", + ] + + for query in queries: + isctest.run.retry_with_timeout(rrsig_is_refreshed, timeout=5) + + def test_rrsig_reuse(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info(f"check that the zone {zone} reuses fresh signatures") + + def rrsig_is_reused(): + parts = query.split() + qname = parts[0] + qtype = dns.rdatatype.from_text(parts[1]) + return isctest.kasp.check_rrsig_is_reused( + server, zone, f"{keydir}/{zone}.db.signed", qname, qtype, ksks, zsks + ) + + queries = [ + f"{zone} NS", + f"{zone} NSEC", + f"a.{zone} A", + f"a.{zone} NSEC", + f"b.{zone} A", + f"b.{zone} NSEC", + f"c.{zone} A", + f"c.{zone} NSEC", + f"ns3.{zone} A", + f"ns3.{zone} NSEC", + ] + + for query in queries: + rrsig_is_reused() + + def test_legacy_keys(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info(f"check that the zone {zone} uses correct legacy keys") + + assert len(ksks) == 1 + assert len(zsks) == 1 + + # This assumes the zone has a policy that dictates one KSK and one ZSK. + # The right keys to be used are stored in "{zone}.ksk" and "{zone}.zsk". + with open(f"{keydir}/{zone}.ksk", "r", encoding="utf-8") as file: + kskfile = file.read() + with open(f"{keydir}/{zone}.zsk", "r", encoding="utf-8") as file: + zskfile = file.read() + + assert f"{keydir}/{kskfile}".strip() == ksks[0].path + assert f"{keydir}/{zskfile}".strip() == zsks[0].path + + def test_remove_keyfiles(zone=None, policy=None, ksks=None, zsks=None): + # pylint: disable=unused-argument + isctest.log.info( + "check that removing key files does not create new keys to be generated" + ) + + for k in ksks + zsks: + os.remove(k.keyfile) + os.remove(k.privatefile) + os.remove(k.statefile) + + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line( + f"zone {zone}/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" + ) + + # Check keys again, make sure no new keys are created. + keys = isctest.kasp.keydir_to_keylist(zone, keydir) + isctest.kasp.check_keys(zone, keys, []) + # Zone is still signed correctly. + isctest.kasp.check_dnssec_verify(server, zone) + # Test case function. def test_case(): zone = test["zone"] @@ -177,6 +301,7 @@ def test_kasp_cases(servers): pregenerated = False if test.get("pregenerated"): pregenerated = test["pregenerated"] + zsk_missing = zone == "zsk-missing.autosign" isctest.log.info(f"check test case zone {zone} policy {policy}") @@ -203,7 +328,13 @@ def test_kasp_cases(servers): isctest.kasp.check_keytimes(keys, expected) - check_all(server, zone, policy, ksks, zsks) + check_all(server, zone, policy, ksks, zsks, zsk_missing=zsk_missing) + + if "additional-tests" in test: + for additional_test in test["additional-tests"]: + callback = additional_test["callback"] + arguments = additional_test["arguments"] + callback(*arguments, zone=zone, policy=policy, ksks=ksks, zsks=zsks) # Test cases. rsa_cases = [] @@ -231,6 +362,78 @@ def test_kasp_cases(servers): "offset": -timedelta(days=30 * 6), "key-properties": autosign_properties, }, + { + "zone": "expired-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_rrsig_refresh, + "arguments": [], + }, + ], + }, + { + "zone": "fresh-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_rrsig_reuse, + "arguments": [], + }, + ], + }, + { + "zone": "unfresh-sigs.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_rrsig_refresh, + "arguments": [], + }, + ], + }, + { + "zone": "keyfiles-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": autosign_properties, + "additional-tests": [ + { + "callback": test_remove_keyfiles, + "arguments": [], + }, + ], + }, + { + "zone": "ksk-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": [ + f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent missing", + f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ], + }, + { + "zone": "zsk-missing.autosign", + "policy": "autosign", + "config": autosign_config, + "offset": -timedelta(days=30 * 6), + "key-properties": [ + f"ksk 63072000 {alg} {size} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 31536000 {alg} {size} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent missing", + ], + }, { "zone": "dnssec-keygen.kasp", "policy": "rsasha256", @@ -255,6 +458,22 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(8), }, + { + "zone": "legacy-keys.kasp", + "policy": "migrate-to-dnssec-policy", + "config": kasp_config, + "pregenerated": True, + "key-properties": [ + "ksk 16070400 8 2048 goal:omnipresent dnskey:rumoured krrsig:rumoured ds:hidden", + "zsk 16070400 8 2048 goal:omnipresent dnskey:rumoured zrrsig:rumoured", + ], + "additional-tests": [ + { + "callback": test_legacy_keys, + "arguments": [], + }, + ], + }, { "zone": "pregenerated.kasp", "policy": "rsasha256", @@ -274,6 +493,23 @@ def test_kasp_cases(servers): "config": kasp_config, "key-properties": fips_properties(10), }, + { + "zone": "secondary.kasp", + "policy": "rsasha256", + "config": kasp_config, + "key-properties": fips_properties(8), + "additional-tests": [ + { + "callback": test_ixfr_is_signed, + "arguments": [ + [ + "a.secondary.kasp. A 10.0.0.11", + "d.secondary.kasp. A 10.0.0.4", + ], + ], + }, + ], + }, { "zone": "some-keys.kasp", "policy": "rsasha256",