diff --git a/CHANGES b/CHANGES index b14f754984..922acf6f01 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +5858. [bug] Don't remove CDS/CDNSKEY DELETE records on zone sign + when using 'auto-dnssec maintain;'. [GL #2931] + 5856. [bug] The "starting maxtime timer" message related to outgoing zone transfers was incorrectly logged at the ERROR level instead of DEBUG(1). [GL #3208] diff --git a/bin/tests/system/autosign/clean.sh b/bin/tests/system/autosign/clean.sh index bb738af862..f4ab636e8e 100644 --- a/bin/tests/system/autosign/clean.sh +++ b/bin/tests/system/autosign/clean.sh @@ -35,6 +35,8 @@ rm -f ns2/private.secure.example.db ns2/bar.db rm -f ns3/*.nzd ns3/*.nzd-lock ns3/*.nzf rm -f ns3/*.nzf rm -f ns3/autonsec3.example.db +rm -f ns3/cdnskey-delete.example.db +rm -f ns3/cds-delete.example.db rm -f ns3/delzsk.example.db rm -f ns3/dname-at-apex-nsec3.example.db rm -f ns3/inacksk2.example.db diff --git a/bin/tests/system/autosign/ns2/keygen.sh b/bin/tests/system/autosign/ns2/keygen.sh index ec45cac86c..ddb9469277 100644 --- a/bin/tests/system/autosign/ns2/keygen.sh +++ b/bin/tests/system/autosign/ns2/keygen.sh @@ -16,8 +16,9 @@ # Have the child generate subdomain keys and pass DS sets to us. ( cd ../ns3 && $SHELL keygen.sh ) -for subdomain in secure nsec3 autonsec3 optout rsasha256 rsasha512 nsec3-to-nsec oldsigs sync \ - dname-at-apex-nsec3 +for subdomain in secure nsec3 autonsec3 optout rsasha256 rsasha512 \ + nsec3-to-nsec oldsigs sync dname-at-apex-nsec3 cds-delete \ + cdnskey-delete do cp ../ns3/dsset-$subdomain.example. . done diff --git a/bin/tests/system/autosign/ns3/cdnskey-delete.example.db.in b/bin/tests/system/autosign/ns3/cdnskey-delete.example.db.in new file mode 100644 index 0000000000..3083a79f7d --- /dev/null +++ b/bin/tests/system/autosign/ns3/cdnskey-delete.example.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 http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/autosign/ns3/cds-delete.example.db.in b/bin/tests/system/autosign/ns3/cds-delete.example.db.in new file mode 100644 index 0000000000..3083a79f7d --- /dev/null +++ b/bin/tests/system/autosign/ns3/cds-delete.example.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 http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/autosign/ns3/keygen.sh b/bin/tests/system/autosign/ns3/keygen.sh index e9016f05e1..bd1d16e06f 100644 --- a/bin/tests/system/autosign/ns3/keygen.sh +++ b/bin/tests/system/autosign/ns3/keygen.sh @@ -332,7 +332,7 @@ $KEYGEN -a $DEFAULT_ALGORITHM -3 -q -P now -A now+3600 $zone > kg.out 2>&1 || du $DSFROMKEY $ksk.key > dsset-${zone}. # -# A zone that starts with a active KSK + ZSK and a inactive ZSK. +# A zone that starts with a active KSK + ZSK and a inactive ZSK. # setup inacksk3.example cp $infile $zonefile @@ -342,7 +342,7 @@ $KEYGEN -a $DEFAULT_ALGORITHM -3 -q $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. # -# A zone that starts with a active KSK + ZSK and a inactive ZSK. +# A zone that starts with a active KSK + ZSK and a inactive ZSK. # setup inaczsk3.example cp $infile $zonefile @@ -363,10 +363,29 @@ zsk=`$KEYGEN -a $DEFAULT_ALGORITHM -3 -q -I now-1w $zone 2>kg.out` || dumpit kg. echo $zsk > ../delzsk.key # -# Check that NSEC3 are correctly signed and returned from below a DNAME +# Check that NSEC3 are correctly signed and returned from below a DNAME # setup dname-at-apex-nsec3.example cp $infile $zonefile ksk=`$KEYGEN -q -a $DEFAULT_ALGORITHM -3 -fk $zone 2> kg.out` || dumpit kg.out $KEYGEN -q -a $DEFAULT_ALGORITHM -3 $zone > kg.out 2>&1 || dumpit kg.out $DSFROMKEY $ksk.key > dsset-${zone}. + +# +# Check that dynamically added CDS (DELETE) is kept in the zone after signing. +# +setup cds-delete.example +cp $infile $zonefile +ksk=`$KEYGEN -q -a $DEFAULT_ALGORITHM -3 -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -a $DEFAULT_ALGORITHM -3 $zone > kg.out 2>&1 || dumpit kg.out +$DSFROMKEY $ksk.key > dsset-${zone}. + +# +# Check that dynamically added CDNSKEY (DELETE) is kept in the zone after +# signing. +# +setup cdnskey-delete.example +cp $infile $zonefile +ksk=`$KEYGEN -q -a $DEFAULT_ALGORITHM -3 -fk $zone 2> kg.out` || dumpit kg.out +$KEYGEN -q -a $DEFAULT_ALGORITHM -3 $zone > kg.out 2>&1 || dumpit kg.out +$DSFROMKEY $ksk.key > dsset-${zone}. diff --git a/bin/tests/system/autosign/ns3/named.conf.in b/bin/tests/system/autosign/ns3/named.conf.in index 677f597e4b..29dfad6312 100644 --- a/bin/tests/system/autosign/ns3/named.conf.in +++ b/bin/tests/system/autosign/ns3/named.conf.in @@ -318,4 +318,18 @@ zone "dname-at-apex-nsec3.example" { auto-dnssec maintain; }; +zone "cds-delete.example" { + type primary; + file "cds-delete.example.db"; + allow-update { any; }; + auto-dnssec maintain; +}; + +zone "cdnskey-delete.example" { + type primary; + file "cdnskey-delete.example.db"; + allow-update { any; }; + auto-dnssec maintain; +}; + include "trusted.conf"; diff --git a/bin/tests/system/autosign/tests.sh b/bin/tests/system/autosign/tests.sh index 8b7a8dea84..3c7334ab3d 100755 --- a/bin/tests/system/autosign/tests.sh +++ b/bin/tests/system/autosign/tests.sh @@ -1645,6 +1645,89 @@ inac=`grep "DNSKEY .* is now inactive" ns1/named.run | wc -l` [ "$inac" -eq 1 ] || ret=1 del=`grep "DNSKEY .* is now deleted" ns1/named.run | wc -l` [ "$del" -eq 1 ] || ret=1 +n=`expr $n + 1` +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +echo_i "checking that CDS (DELETE) persists after zone sign ($n)" +echo_i "update add cds-delete.example. CDS 0 0 00" +ret=0 +$NSUPDATE > nsupdate.out 2>&1 < dig.out.ns3.test$n || return 1 + grep "CDS.*0.*0.*0.*00" dig.out.ns3.test$n > /dev/null 2>&1 || return 1 + return 0 +) +_cdnskey_delete_nx() { + $DIG $DIGOPTS +noall +answer $1 cdnskey @10.53.0.3 > dig.out.ns3.test$n || return 1 + grep "CDNSKEY.*0.*3.*0.*AA==" dig.out.ns3.test$n > /dev/null 2>&1 && return 1 + return 0 +} + +echo_i "query cds-delete.example. CDS" +retry_quiet 10 _cds_delete cds-delete.example. || ret=1 +echo_i "query cds-delete.example. CDNSKEY" +retry_quiet 1 _cdnskey_delete_nx cds-delete.example. || ret=1 + +echo_i "sign cds-delete.example." +nextpart ns3/named.run >/dev/null +$RNDCCMD 10.53.0.3 sign cds-delete.example > /dev/null 2>&1 || ret=1 +wait_for_log 10 "zone cds-delete.example/IN: next key event" ns3/named.run +# The CDS (DELETE) record should still be here. +echo_i "query cds-delete.example. CDS" +retry_quiet 1 _cds_delete cds-delete.example. || ret=1 +# The CDNSKEY (DELETE) record should still not be added. +echo_i "query cds-delete.example. CDNSKEY" +retry_quiet 1 _cdnskey_delete_nx cds-delete.example. || ret=1 + +n=`expr $n + 1` +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +echo_i "checking that CDNSKEY (DELETE) persists after zone sign ($n)" +echo_i "update add cdnskey-delete.example. CDNSKEY 0 3 0 AA==" +ret=0 +$NSUPDATE > nsupdate.out 2>&1 < dig.out.ns3.test$n || return 1 + grep "CDS.*0.*0.*0.*00" dig.out.ns3.test$n > /dev/null 2>&1 && return 1 + return 0 +) +_cdnskey_delete() { + $DIG $DIGOPTS +noall +answer $1 cdnskey @10.53.0.3 > dig.out.ns3.test$n || return 1 + grep "CDNSKEY.*0.*3.*0.*AA==" dig.out.ns3.test$n > /dev/null 2>&1 || return 1 + return 0 +} + +echo_i "query cdnskey-delete.example. CDNSKEY" +retry_quiet 10 _cdnskey_delete cdnskey-delete.example. || ret=1 +echo_i "query cdnskey-delete.example. CDS" +retry_quiet 1 _cds_delete_nx cdnskey-delete.example. || ret=1 + +echo_i "sign cdsnskey-delete.example." +nextpart ns3/named.run >/dev/null +$RNDCCMD 10.53.0.3 sign cdnskey-delete.example > /dev/null 2>&1 || ret=1 +wait_for_log 10 "zone cdnskey-delete.example/IN: next key event" ns3/named.run +# The CDNSKEY (DELETE) record should still be here. +echo_i "query cdnskey-delete.example. CDNSKEY" +retry_quiet 1 _cdnskey_delete cdnskey-delete.example. || ret=1 +# The CDS (DELETE) record should still not be added. +echo_i "query cdnskey-delete.example. CDS" +retry_quiet 1 _cds_delete_nx cdnskey-delete.example. || ret=1 + +n=`expr $n + 1` if [ $ret != 0 ]; then echo_i "failed"; fi status=`expr $status + $ret` diff --git a/doc/dnssec-guide/recipes.rst b/doc/dnssec-guide/recipes.rst index 8f19e819d0..8a9f6ec2fc 100644 --- a/doc/dnssec-guide/recipes.rst +++ b/doc/dnssec-guide/recipes.rst @@ -1067,6 +1067,12 @@ Below is an example showing how to remove DS records using the Revert to Unsigned Step #4 +If your parent allows managing DS record via CDS/CDNSKEY, as described in +:rfc:`5155`, you could add CDS/CDNSKEY DELETE records in your zone to signal +that the corresponding DS records from the parent zone needs to be removed. +If it is unclear which format the parent zone is expecting, you should publish +both CDS and CDNSKEY DELETE records. + To be on the safe side, wait a while before actually deleting all signed data from your zone, just in case some validating resolvers have cached information. After you are certain that all cached @@ -1099,7 +1105,8 @@ Then use :option:`rndc reload` to reload the zone. The "insecure" policy is a built-in policy (like "default"). It will make sure the zone is still DNSSEC maintained, to allow for a graceful transition to -unsigned. +unsigned. It also publishes the CDS and CDNSKEY DELETE records for you when +the time is right. When the DS records have been removed from the parent zone, use :option:`rndc dnssec -checkds -key id withdrawn example.com ` to tell :iscman:`named` that diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 8aaede7f0e..3181c29668 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -40,4 +40,5 @@ Feature Changes Bug Fixes ~~~~~~~~~ -- None. +- CDS and CDNSKEY DELETE records are removed from the zone when configured with + 'auto-dnssec maintain;'. This has been fixed. :gl:`#2931`. diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 375a6bab6d..ede233ab3c 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -2143,7 +2143,7 @@ isc_result_t dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, dns_name_t *origin, dns_rdataclass_t zclass, dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx, - bool dnssec_insecure) { + bool expect_cds_delete, bool expect_cdnskey_delete) { unsigned char dsbuf[5] = { 0, 0, 0, 0, 0 }; /* CDS DELETE rdata */ unsigned char keybuf[5] = { 0, 0, 3, 0, 0 }; /* CDNSKEY DELETE rdata */ char namebuf[DNS_NAME_FORMATSIZE]; @@ -2163,7 +2163,30 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, dns_name_format(origin, namebuf, sizeof(namebuf)); - if (dnssec_insecure) { + if (expect_cds_delete) { + if (!dns_rdataset_isassociated(cds) || + !exists(cds, &cds_delete)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDS (DELETE) for zone %s is now " + "published", + namebuf); + RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx)); + } + } else { + if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete)) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, + "CDS (DELETE) for zone %s is now " + "deleted", + namebuf); + RETERR(delrdata(&cds_delete, diff, origin, cds->ttl, + mctx)); + } + } + + if (expect_cdnskey_delete) { if (!dns_rdataset_isassociated(cdnskey) || !exists(cdnskey, &cdnskey_delete)) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, @@ -2174,16 +2197,6 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, RETERR(addrdata(&cdnskey_delete, diff, origin, ttl, mctx)); } - - if (!dns_rdataset_isassociated(cds) || - !exists(cds, &cds_delete)) { - isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, - DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, - "CDS (DELETE) for zone %s is now " - "published", - namebuf); - RETERR(addrdata(&cds_delete, diff, origin, ttl, mctx)); - } } else { if (dns_rdataset_isassociated(cdnskey) && exists(cdnskey, &cdnskey_delete)) { @@ -2195,17 +2208,6 @@ dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, RETERR(delrdata(&cdnskey_delete, diff, origin, cdnskey->ttl, mctx)); } - - if (dns_rdataset_isassociated(cds) && exists(cds, &cds_delete)) - { - isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, - DNS_LOGMODULE_DNSSEC, ISC_LOG_INFO, - "CDS (DELETE) for zone %s is now " - "deleted", - namebuf); - RETERR(delrdata(&cds_delete, diff, origin, cds->ttl, - mctx)); - } } result = ISC_R_SUCCESS; diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h index a5451fda7f..d96d0a7c80 100644 --- a/lib/dns/include/dns/dnssec.h +++ b/lib/dns/include/dns/dnssec.h @@ -369,11 +369,14 @@ isc_result_t dns_dnssec_syncdelete(dns_rdataset_t *cds, dns_rdataset_t *cdnskey, dns_name_t *origin, dns_rdataclass_t zclass, dns_ttl_t ttl, dns_diff_t *diff, isc_mem_t *mctx, - bool dnssec_insecure); + bool expect_cds_delete, bool expect_cdnskey_delete); /*%< * Add or remove the CDS DELETE record and the CDNSKEY DELETE record. - * If 'dnssec_insecure' is true, the DELETE records should be present. - * Otherwise, the DELETE records must be removed from the RRsets (if present). + * If 'expect_cds_delete' is true, the CDS DELETE record should be present. + * Otherwise, the CDS DELETE record must be removed from the RRsets (if + * present). If 'expect_cdnskey_delete' is true, the CDNSKEY DELETE record + * should be present. Otherwise, the CDNSKEY DELETE record must be removed + * from the RRsets (if present). * * Returns: *\li ISC_R_SUCCESS diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 53ca4b7c95..c47ffd0f2d 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -21688,16 +21688,69 @@ zone_rekey(dns_zone_t *zone) { KASP_UNLOCK(kasp); if (result == ISC_R_SUCCESS) { - bool cds_delete = false; + bool cdsdel = false; + bool cdnskeydel = false; isc_stdtime_t when; /* * Publish CDS/CDNSKEY DELETE records if the zone is * transitioning from secure to insecure. */ - if (kasp != NULL && - strcmp(dns_kasp_getname(kasp), "insecure") == 0) { - cds_delete = true; + if (kasp != NULL) { + if (strcmp(dns_kasp_getname(kasp), "insecure") == 0) { + cdsdel = true; + cdnskeydel = true; + } + } else { + /* Check if there is a CDS DELETE record. */ + if (dns_rdataset_isassociated(&cdsset)) { + for (result = dns_rdataset_first(&cdsset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&cdsset)) + { + dns_rdata_t crdata = DNS_RDATA_INIT; + dns_rdataset_current(&cdsset, &crdata); + /* + * CDS deletion record has this form + * "0 0 0 00" which is 5 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 0, + 0, 0 }, + 5) == 0) + { + cdsdel = true; + break; + } + } + } + + /* Check if there is a CDNSKEY DELETE record. */ + if (dns_rdataset_isassociated(&cdnskeyset)) { + for (result = dns_rdataset_first(&cdnskeyset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&cdnskeyset)) + { + dns_rdata_t crdata = DNS_RDATA_INIT; + dns_rdataset_current(&cdnskeyset, + &crdata); + /* + * CDNSKEY deletion record has this form + * "0 3 0 AA==" which is 2 zero octets, + * a 3, and 2 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 3, + 0, 0 }, + 5) == 0) + { + cdnskeydel = true; + break; + } + } + } } /* @@ -21734,36 +21787,36 @@ zone_rekey(dns_zone_t *zone) { goto failure; } - if (cds_delete) { + if (cdsdel || cdnskeydel) { /* * Only publish CDS/CDNSKEY DELETE records if there is * a KSK that can be used to verify the RRset. This * means there must be a key with the KSK role that is * published and is used for signing. */ - cds_delete = false; + bool allow = false; for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { dst_key_t *dstk = key->key; - bool ksk = false; - (void)dst_key_getbool(dstk, DST_BOOL_KSK, &ksk); - if (!ksk) { - continue; - } - if (dst_key_haskasp(dstk) && - dst_key_is_published(dstk, now, &when) && + if (dst_key_is_published(dstk, now, &when) && dst_key_is_signing(dstk, DST_BOOL_KSK, now, &when)) { - cds_delete = true; + allow = true; break; } } + if (cdsdel) { + cdsdel = allow; + } + if (cdnskeydel) { + cdnskeydel = allow; + } } - result = dns_dnssec_syncdelete(&cdsset, &cdnskeyset, - &zone->origin, zone->rdclass, - ttl, &diff, mctx, cds_delete); + result = dns_dnssec_syncdelete( + &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl, + &diff, mctx, cdsdel, cdnskeydel); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY "