From 3711f3b3c68bf2c88bc33e546fa7ce1f0de4340b Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 2 Jun 2026 11:41:13 +0200 Subject: [PATCH] Test removing DNSKEY records with class ANY The update should ignore DNSKEY, CDNSKEY and CDS records for keys that are used for signing. --- bin/tests/system/multisigner/ns1/root.db.in | 3 + bin/tests/system/multisigner/ns1/setup.sh | 2 +- .../system/multisigner/ns2/named.conf.j2 | 5 + bin/tests/system/multisigner/ns2/setup.sh | 4 +- .../multisigner/ns2/update-any.db.in.j2 | 23 ++ .../multisigner/ns3/model2.update-any.db | 16 ++ .../system/multisigner/ns3/named.conf.j2 | 8 + bin/tests/system/multisigner/ns3/setup.sh | 9 + .../multisigner/ns4/model2.update-any.db | 15 ++ .../system/multisigner/ns4/named.conf.j2 | 8 + bin/tests/system/multisigner/ns4/setup.sh | 9 + .../system/multisigner/tests_multisigner.py | 219 +++++++++++++++--- 12 files changed, 282 insertions(+), 39 deletions(-) create mode 100644 bin/tests/system/multisigner/ns2/update-any.db.in.j2 create mode 100644 bin/tests/system/multisigner/ns3/model2.update-any.db create mode 100644 bin/tests/system/multisigner/ns4/model2.update-any.db diff --git a/bin/tests/system/multisigner/ns1/root.db.in b/bin/tests/system/multisigner/ns1/root.db.in index e9dc46ff12..57213b66c4 100644 --- a/bin/tests/system/multisigner/ns1/root.db.in +++ b/bin/tests/system/multisigner/ns1/root.db.in @@ -12,6 +12,9 @@ a.root-servers.nil. A 10.53.0.1 multisigner. NS ns2.multisigner. ns2.multisigner. A 10.53.0.2 +update-any. NS ns2.update-any. +ns2.update-any. A 10.53.0.2 + bad-dsync. NS ns2.bad-dsync. ns2.bad-dsync. A 10.53.0.2 diff --git a/bin/tests/system/multisigner/ns1/setup.sh b/bin/tests/system/multisigner/ns1/setup.sh index b6cf3028d1..da2383d8b8 100644 --- a/bin/tests/system/multisigner/ns1/setup.sh +++ b/bin/tests/system/multisigner/ns1/setup.sh @@ -22,7 +22,7 @@ zonefile=root.db echo_i "ns1/setup.sh" -for tld in multisigner bad-dsync secondary; do +for tld in multisigner update-any bad-dsync secondary; do cp "../ns2/dsset-${tld}." . done diff --git a/bin/tests/system/multisigner/ns2/named.conf.j2 b/bin/tests/system/multisigner/ns2/named.conf.j2 index 84cd5beb06..d90567dff4 100644 --- a/bin/tests/system/multisigner/ns2/named.conf.j2 +++ b/bin/tests/system/multisigner/ns2/named.conf.j2 @@ -27,6 +27,11 @@ zone "multisigner" { file "multisigner.db.signed"; }; +zone "update-any" { + type primary; + file "update-any.db.signed"; +}; + zone "bad-dsync" { type primary; file "bad-dsync.db.signed"; diff --git a/bin/tests/system/multisigner/ns2/setup.sh b/bin/tests/system/multisigner/ns2/setup.sh index 4fd349125d..786cfa4787 100644 --- a/bin/tests/system/multisigner/ns2/setup.sh +++ b/bin/tests/system/multisigner/ns2/setup.sh @@ -30,10 +30,10 @@ setup() { $DSFROMKEY $KSK.key >dsset-ns2-${zone}. cat $infile $KSK.key $ZSK.key >$zonefile - $SIGNER -g -o $zone $zonefile - # >/dev/null 2>&1 + $SIGNER -g -o $zone $zonefile >/dev/null 2>&1 } setup "multisigner" +setup "update-any" setup "bad-dsync" setup "secondary" diff --git a/bin/tests/system/multisigner/ns2/update-any.db.in.j2 b/bin/tests/system/multisigner/ns2/update-any.db.in.j2 new file mode 100644 index 0000000000..e2cd55932e --- /dev/null +++ b/bin/tests/system/multisigner/ns2/update-any.db.in.j2 @@ -0,0 +1,23 @@ +$TTL 300 +$ORIGIN update-any. + +update-any. IN SOA mname1. . ( + 1 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + + NS ns2 +ns2 A 10.53.0.2 + +scanner A 10.53.0.2 + +model2 NS ns3 + NS ns4 + +ns3.model2 A 10.53.0.3 +ns4.model2 A 10.53.0.4 + +*._dsync DSYNC CDS NOTIFY @PORT@ scanner diff --git a/bin/tests/system/multisigner/ns3/model2.update-any.db b/bin/tests/system/multisigner/ns3/model2.update-any.db new file mode 100644 index 0000000000..74fa7faa1a --- /dev/null +++ b/bin/tests/system/multisigner/ns3/model2.update-any.db @@ -0,0 +1,16 @@ +$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/multisigner/ns3/named.conf.j2 b/bin/tests/system/multisigner/ns3/named.conf.j2 index a4437a890e..a7f2546cb5 100644 --- a/bin/tests/system/multisigner/ns3/named.conf.j2 +++ b/bin/tests/system/multisigner/ns3/named.conf.j2 @@ -33,6 +33,14 @@ zone "model2.multisigner." { inline-signing no; }; +zone "model2.update-any." { + type primary; + allow-update { any; }; + file "model2.update-any.db"; + dnssec-policy model2; + inline-signing no; +}; + zone "model2.bad-dsync." { type primary; allow-update { any; }; diff --git a/bin/tests/system/multisigner/ns3/setup.sh b/bin/tests/system/multisigner/ns3/setup.sh index 50f26480f7..2aa711c856 100644 --- a/bin/tests/system/multisigner/ns3/setup.sh +++ b/bin/tests/system/multisigner/ns3/setup.sh @@ -29,6 +29,15 @@ $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 $DSFROMKEY $KSK.key >dsset-ns3-${zone}. +zone="model2.update-any" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) +$SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns3-${zone}. + zone="model2.bad-dsync" echo_i "setting up zone: $zone" zonefile="${zone}.db" diff --git a/bin/tests/system/multisigner/ns4/model2.update-any.db b/bin/tests/system/multisigner/ns4/model2.update-any.db new file mode 100644 index 0000000000..8a84c8b72e --- /dev/null +++ b/bin/tests/system/multisigner/ns4/model2.update-any.db @@ -0,0 +1,15 @@ +$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 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 diff --git a/bin/tests/system/multisigner/ns4/named.conf.j2 b/bin/tests/system/multisigner/ns4/named.conf.j2 index 0637c2d2b1..cc73f37179 100644 --- a/bin/tests/system/multisigner/ns4/named.conf.j2 +++ b/bin/tests/system/multisigner/ns4/named.conf.j2 @@ -33,6 +33,14 @@ zone "model2.multisigner." { inline-signing yes; }; +zone "model2.update-any." { + type primary; + allow-update { any; }; + file "model2.update-any.db"; + dnssec-policy model2; + inline-signing yes; +}; + zone "model2.bad-dsync." { type primary; allow-update { any; }; diff --git a/bin/tests/system/multisigner/ns4/setup.sh b/bin/tests/system/multisigner/ns4/setup.sh index bb13b8bede..6d4509872e 100644 --- a/bin/tests/system/multisigner/ns4/setup.sh +++ b/bin/tests/system/multisigner/ns4/setup.sh @@ -29,6 +29,15 @@ $SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 $SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 $DSFROMKEY $KSK.key >dsset-ns4-${zone}. +zone="model2.update-any" +echo_i "setting up zone: $zone" +zonefile="${zone}.db" +KSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone) +ZSK=$($KEYGEN -q -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone) +$SETTIME -s -g $O -k $O now -r $O now -d $O now "$KSK" >settime.out.$zone.1 2>&1 +$SETTIME -s -g $O -k $O now -z $O now "$ZSK" >settime.out.$zone.2 2>&1 +$DSFROMKEY $KSK.key >dsset-ns4-${zone}. + zone="model2.bad-dsync" echo_i "setting up zone: $zone" zonefile="${zone}.db" diff --git a/bin/tests/system/multisigner/tests_multisigner.py b/bin/tests/system/multisigner/tests_multisigner.py index d3db8ad163..5fa713c9aa 100644 --- a/bin/tests/system/multisigner/tests_multisigner.py +++ b/bin/tests/system/multisigner/tests_multisigner.py @@ -233,7 +233,15 @@ def _check_remove_zsk_fail( def check_remove_zsk( - server, zone, keys, expected, extra_keys, extra, primary=None, check_fail=False + server, + zone, + keys, + expected, + extra_keys, + extra, + primary=None, + check_fail=False, + update_any=False, ): isctest.log.info("remove dnskey record:") @@ -242,20 +250,31 @@ def check_remove_zsk( if check_fail: _check_remove_zsk_fail( - server, zone, keys, expected, extra_keys, extra, primary=primary + server, + zone, + keys, + expected, + extra_keys, + extra, + primary=primary, ) - # Remove actual ZSK. - isctest.log.info( - f"- zone {zone} {primary.identifier}: remove ZSK from other providers" - ) - - update_msg = dns.update.UpdateMessage(zone) - for zsk in extra_keys: - dnskey = str(zsk.dnskey).split() - rdata = " ".join(dnskey[4:]) - update_msg.delete(f"{zone}.", "DNSKEY", rdata) - primary.nsupdate(update_msg) + if update_any: + # Remove ZSK with update ANY. + isctest.log.info( + f"- zone {zone} {primary.identifier}: remove DNSKEY RRset with update ANY (expect ours)" + ) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "DNSKEY") + primary.nsupdate(update_msg) + else: + # Remove actual ZSK. + update_msg = dns.update.UpdateMessage(zone) + for zsk in extra_keys: + dnskey = str(zsk.dnskey).split() + rdata = " ".join(dnskey[4:]) + update_msg.delete(f"{zone}.", "DNSKEY", rdata) + primary.nsupdate(update_msg) wait_for_serial(primary, server, zone) @@ -349,7 +368,15 @@ def _check_remove_cdnskey_fail( def check_remove_cdnskey( - server, zone, keys, expected, extra_keys, extra, primary=None, check_fail=False + server, + zone, + keys, + expected, + extra_keys, + extra, + primary=None, + check_fail=False, + update_any=False, ): isctest.log.info("remove cdnskey record:") @@ -358,20 +385,35 @@ def check_remove_cdnskey( if check_fail: _check_remove_cdnskey_fail( - server, zone, keys, expected, extra_keys, extra, primary=primary + server, + zone, + keys, + expected, + extra_keys, + extra, + primary=primary, ) - # Remove actual CDNSKEY. - isctest.log.info( - f"- zone {zone} {primary.identifier}: remove CDNSKEY from other providers" - ) + if update_any: + # Remove CDNSKEY with update ANY. + isctest.log.info( + f"- zone {zone} {primary.identifier}: remove CDNSKEY RRset with update ANY (expect ours)" + ) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDNSKEY") + primary.nsupdate(update_msg) + else: + # Remove actual CDNSKEY. + isctest.log.info( + f"- zone {zone} {primary.identifier}: remove CDNSKEY from other providers" + ) - update_msg = dns.update.UpdateMessage(zone) - for ksk in extra_keys: - dnskey = str(ksk.dnskey).split() - rdata = " ".join(dnskey[4:]) - update_msg.delete(f"{zone}.", "CDNSKEY", rdata) - primary.nsupdate(update_msg) + update_msg = dns.update.UpdateMessage(zone) + for ksk in extra_keys: + dnskey = str(ksk.dnskey).split() + rdata = " ".join(dnskey[4:]) + update_msg.delete(f"{zone}.", "CDNSKEY", rdata) + primary.nsupdate(update_msg) wait_for_serial(primary, server, zone) @@ -465,7 +507,15 @@ def _check_remove_cds_fail( def check_remove_cds( - server, zone, keys, expected, extra_keys, extra, primary=None, check_fail=False + server, + zone, + keys, + expected, + extra_keys, + extra, + primary=None, + check_fail=False, + update_any=False, ): isctest.log.info("remove cds record:") @@ -477,17 +527,26 @@ def check_remove_cds( server, zone, keys, expected, extra_keys, extra, primary=primary ) - # Remove actual CDS. - isctest.log.info( - f"- zone {zone} {primary.identifier}: remove CDS from other providers" - ) + if update_any: + # Remove CDS with update ANY. + isctest.log.info( + f"- zone {zone} {primary.identifier}: remove CDS RRset with update ANY (expect ours)" + ) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDS") + primary.nsupdate(update_msg) + else: + # Remove actual CDS. + isctest.log.info( + f"- zone {zone} {primary.identifier}: remove CDS from other providers" + ) - update_msg = dns.update.UpdateMessage(zone) - for ksk in extra_keys: - ds = dsfromkey(ksk) - rdata = " ".join(ds[4:]) - update_msg.delete(f"{zone}.", "CDS", rdata) - primary.nsupdate(update_msg) + update_msg = dns.update.UpdateMessage(zone) + for ksk in extra_keys: + ds = dsfromkey(ksk) + rdata = " ".join(ds[4:]) + update_msg.delete(f"{zone}.", "CDS", rdata) + primary.nsupdate(update_msg) wait_for_serial(primary, server, zone) @@ -595,6 +654,94 @@ def test_multisigner(ns2, ns3, ns4, default_algorithm): check_no_dnssec_in_journal(ns4, zone) +def test_multisigner_update_any(ns2, ns3, ns4, default_algorithm): + zone = "model2.update-any" + keyprops = [ + f"ksk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 0 {default_algorithm.number} {default_algorithm.bits} goal:omnipresent dnskey:omnipresent zrrsig:omnipresent", + ] + + # First make sure the zone is properly signed. + isctest.log.info(f"basic DNSSEC tests for {zone}") + isctest.kasp.wait_keymgr_done(ns3, zone) + isctest.kasp.wait_keymgr_done(ns4, zone) + + with ns3.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN: dsyncfetch: send NOTIFY(CDS) query to scanner.update-any" + ) + + with ns4.watch_log_from_start() as watcher: + watcher.wait_for_line( + f"zone {zone}/IN (signed): dsyncfetch: send NOTIFY(CDS) query to scanner.update-any" + ) + + with ns2.watch_log_from_start() as watcher: + # Receiving NOTIFY(CDS) has not been implemented yet. Until + # then, notifies for child zones towards the parent result in + # not authoritative (unless child and parent are served by the + # same name server). + watcher.wait_for_line(f"received notify for zone '{zone}': NOTAUTH") + + keys3 = isctest.kasp.keydir_to_keylist(zone, ns3.identifier) + ksks3 = [k for k in keys3 if k.is_ksk()] + zsks3 = [k for k in keys3 if not k.is_ksk()] + expected3 = isctest.kasp.policy_to_properties(ttl=TTL, keys=keyprops) + + check_dnssec(ns3, zone, keys3, expected3) + + keys4 = isctest.kasp.keydir_to_keylist(zone, ns4.identifier) + ksks4 = [k for k in keys4 if k.is_ksk()] + zsks4 = [k for k in keys4 if not k.is_ksk()] + expected4 = isctest.kasp.policy_to_properties(ttl=TTL, keys=keyprops) + + check_dnssec(ns4, zone, keys4, expected4) + + # Add DNSKEY to RRset. + newprops = [f"zsk unlimited {default_algorithm.number} {default_algorithm.bits}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False + extra[0].legacy = True + + check_add_zsk(ns3, zone, keys3, expected3, [zsks4[0]], extra) + check_add_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Remove DNSKEY from RRset. + check_remove_zsk(ns3, zone, keys3, expected3, [zsks4[0]], extra, update_any=True) + check_remove_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra, update_any=True) + check_no_dnssec_in_journal(ns4, zone) + + # Add CDNSKEY RRset. + newprops = [f"ksk unlimited {default_algorithm.number} {default_algorithm.bits}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False + extra[0].legacy = True + + check_add_cdnskey(ns3, zone, keys3, expected3, [ksks4[0]], extra) + check_add_cdnskey(ns4, zone, keys4, expected4, [ksks3[0]], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Remove CDNSKEY RRset. + check_remove_cdnskey( + ns3, zone, keys3, expected3, [ksks4[0]], extra, update_any=True + ) + check_remove_cdnskey( + ns4, zone, keys4, expected4, [ksks3[0]], extra, update_any=True + ) + check_no_dnssec_in_journal(ns4, zone) + + # Update CDS RRset. + check_add_cds(ns3, zone, keys3, expected3, [ksks4[0]], extra) + check_add_cds(ns4, zone, keys4, expected4, [ksks3[0]], extra) + check_no_dnssec_in_journal(ns4, zone) + + # Remove CDS RRset. + check_remove_cds(ns3, zone, keys3, expected3, [ksks4[0]], extra, update_any=True) + check_remove_cds(ns4, zone, keys4, expected4, [ksks3[0]], extra, update_any=True) + check_no_dnssec_in_journal(ns4, zone) + + def test_multisigner_bad_dsync(ns3, ns4): zone = "model2.bad-dsync"