]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add test for ZSK rollover while KSK offline
authorMatthijs Mekking <matthijs@isc.org>
Thu, 14 Mar 2019 08:32:20 +0000 (09:32 +0100)
committerMatthijs Mekking <matthijs@isc.org>
Fri, 12 Apr 2019 13:57:15 +0000 (15:57 +0200)
This commit adds a lengthy test where the ZSK is rolled but the
KSK is offline (except for when the DNSKEY RRset is changed).  The
specific scenario has the `dnskey-kskonly` configuration option set
meaning the DNSKEY RRset should only be signed with the KSK.

A new zone `updatecheck-kskonly.secure` is added to test against,
that can be dynamically updated, and that can be controlled with rndc
to load the DNSSEC keys.

There are some pre-checks for this test to make sure everything is
fine before the ZSK roll, after the new ZSK is published, and after
the old ZSK is deleted.  Note there are actually two ZSK rolls in
quick succession.

When the latest added ZSK becomes active and its predecessor becomes
inactive, the KSK is offline.  However, the DNSKEY RRset did not
change and it has a good signature that is valid for long enough.
The expected behavior is that the DNSKEY RRset stays signed with
the KSK only (signature does not need to change).  However, the
test will fail because after reconfiguring the keys for the zone,
it wants to add re-sign tasks for the new active keys (in sign_apex).
Because the KSK is offline, named determines that the only other
active key, the latest ZSK, will be used to resign the DNSKEY RRset,
in addition to keeping the RRSIG of the KSK.

The question is: Why do we need to resign the DNSKEY RRset
immediately when a new key becomes active?  This is not required,
only once the next resign task is triggered the new active key
should replace signatures that are in need of refreshing.

(cherry-picked from commit c48b85d0a3c34480179d44e736e3e535dbae1001)

bin/tests/system/dnssec/clean.sh
bin/tests/system/dnssec/ns2/named.conf.in
bin/tests/system/dnssec/ns2/sign.sh
bin/tests/system/dnssec/ns2/template.secure.db.in [new file with mode: 0644]
bin/tests/system/dnssec/tests.sh

index f67c61d52fa2a5adb2e169b1915badf5062f5072..9ca3f2c00364ab56f2a92d677152a3cfc3c2992a 100644 (file)
@@ -15,7 +15,7 @@ rm -f ./*/K* ./*/keyset-* ./*/dsset-* ./*/dlvset-* ./*/signedkey-* ./*/*.signed
 rm -f ./*/example.bk
 rm -f ./*/named.conf
 rm -f ./*/named.memstats
-rm -f ./*/named.run
+rm -f ./*/named.run ./*/named.run.prev
 rm -f ./*/named.secroots
 rm -f ./*/tmp* ./*/*.jnl ./*/*.bk ./*/*.jbk
 rm -f ./*/trusted.conf ./*/managed.conf ./*/revoked.conf
@@ -48,6 +48,9 @@ rm -f ./ns2/in-addr.arpa.db
 rm -f ./ns2/nsec3chain-test.db
 rm -f ./ns2/private.secure.example.db
 rm -f ./ns2/single-nsec3.db
+rm -f ./ns2/updatecheck-kskonly.secure.*
+rm -f ./ns3/secure.example.db ./ns3/*.managed.db ./ns3/*.trusted.db
+rm -f ./ns3/unsupported.managed.db.tmp ./ns3/unsupported.trusted.db.tmp
 rm -f ./ns3/auto-nsec.example.db ./ns3/auto-nsec3.example.db
 rm -f ./ns3/badds.example.db
 rm -f ./ns3/dname-at-apex-nsec3.example.db
index 67cff87b7666d493ec3bf9a06a4a88dcc91a4f6a..22991b34ee4ffdb80db31df33b2d9b5d0a0ba024 100644 (file)
@@ -26,6 +26,15 @@ options {
        notify-delay 1;
 };
 
+key rndc_key {
+        secret "1234abcd8765";
+        algorithm hmac-sha256;
+};
+
+controls {
+        inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
 zone "." {
        type hint;
        file "../../common/root.hint";
@@ -133,4 +142,21 @@ zone "cdnskey-auto.secure" {
        allow-update { any; };
 };
 
+zone "updatecheck-kskonly.secure" {
+       type master;
+       auto-dnssec maintain;
+       key-directory ".";
+       dnssec-dnskey-kskonly yes;
+       update-check-ksk yes;
+       sig-validity-interval 10;
+       dnskey-sig-validity 40;
+       file "updatecheck-kskonly.secure.db.signed";
+       allow-update { any; };
+};
+
+zone "corp" {
+       type master;
+       file "corp.db";
+};
+
 include "trusted.conf";
index ca186084ca4ff94b0c7a554cb29b2e74ac7e5d99..6ee989b14fdb844b06db16711edc748e6511a966 100644 (file)
@@ -239,3 +239,20 @@ key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
 key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
 sed 's/DNSKEY/CDNSKEY/' $key1.key > $key1.cds
 cat $infile $key1.cds > $zonefile.signed
+
+zone=updatecheck-kskonly.secure
+infile=template.secure.db.in
+zonefile=${zone}.db
+key1=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone -fk $zone`
+key2=`$KEYGEN -q -r $RANDFILE -a RSASHA1 -b 1024 -n zone $zone`
+# Save key id's for checking active key usage
+echo $key1 | sed -e 's/.*[+]//' -e 's/^0*//' > $zone.ksk.id
+echo $key2 | sed -e 's/.*[+]//' -e 's/^0*//' > $zone.zsk.id
+echo ${key1} > $zone.ksk.key
+echo ${key2} > $zone.zsk.key
+# Add CDS and CDNSKEY records
+sed 's/DNSKEY/CDNSKEY/' $key1.key > $key1.cdnskey
+$DSFROMKEY -C $key1.key > $key1.cds
+cat $infile $key1.key $key2.key $key1.cdnskey $key1.cds > $zonefile
+# Don't sign, let auto-dnssec maintain do it.
+mv $zonefile $zonefile.signed
diff --git a/bin/tests/system/dnssec/ns2/template.secure.db.in b/bin/tests/system/dnssec/ns2/template.secure.db.in
new file mode 100644 (file)
index 0000000..e42cb4a
--- /dev/null
@@ -0,0 +1,12 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 3600
+@      SOA     ns2.example. . 1 3600 1200 86400 1200
+@      NS      ns2.example.
index 8fedb8a878e0079da6fee1178c40c0451df2005b..54e5219b091f4e4623c65ff2b16838639df77307 100644 (file)
@@ -23,6 +23,26 @@ ANSWEROPTS="+noall +answer +dnssec -p ${PORT}"
 DELVOPTS="-a ns1/trusted.conf -p ${PORT}"
 RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s"
 
+# TODO: Move wait_for_log and loadkeys_on to conf.sh.common
+wait_for_log() {
+        msg=$1
+        file=$2
+        for i in 1 2 3 4 5 6 7 8 9 10; do
+                nextpart "$file" | grep "$msg" > /dev/null && return
+                sleep 1
+        done
+        echo_i "exceeded time limit waiting for '$msg' in $file"
+        ret=1
+}
+
+dnssec_loadkeys_on() {
+       nsidx=$1
+       zone=$2
+       nextpart ns${nsidx}/named.run > /dev/null
+       rndccmd 10.53.0.${nsidx} loadkeys ${zone} | sed "s/^/ns${nsidx} /" | cat_i
+       wait_for_log "next key event" ns${nsidx}/named.run
+}
+
 # convert private-type records to readable form
 showprivate () {
     echo "-- $@ --"
@@ -2586,7 +2606,7 @@ n=`expr $n + 1`
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=`expr $status + $ret`
 
-echo_i "checking dnskey query with no data still gets put in cache ($n)"
+echo_i "checking DNSKEY query with no data still gets put in cache ($n)"
 ret=0
 myDIGOPTS="+noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT} @10.53.0.4"
 firstVal=`$DIG $myDIGOPTS insecure.example. dnskey| awk '$1 != ";;" { print $2 }'`
@@ -2643,7 +2663,7 @@ do
        fi
        echo_i "sleeping ...."
        sleep 3
-done;
+done
 grep "ANSWER: 3," dig.out.ns2.test$n > /dev/null || ret=1
 if [ $ret != 0 ]; then echo_i "nsec3 chain generation not complete"; fi
 $DIG $DIGOPTS +noauth +nodnssec soa nsec3chain-test @10.53.0.2 > dig.out.ns2.test$n || ret=1
@@ -3573,5 +3593,198 @@ n=`expr $n + 1`
 test "$ret" -eq 0 || echo_i "failed"
 status=`expr $status + $ret`
 
+###
+### Additional checks for when the KSK is offline.
+###
+
+# Save some useful information
+zone="updatecheck-kskonly.secure"
+KSK=`cat ns2/${zone}.ksk.key`
+ZSK=`cat ns2/${zone}.zsk.key`
+KSK_ID=`cat ns2/${zone}.ksk.id`
+ZSK_ID=`cat ns2/${zone}.zsk.id`
+SECTIONS="+answer +noauthority +noadditional"
+echo_i "testing zone $zone KSK=$KSK_ID ZSK=$ZSK_ID"
+
+# Basic checks to make sure everything is fine before the KSK is made offline.
+for qtype in "DNSKEY" "CDNSKEY" "CDS"
+do
+  echo_i "checking $qtype RRset is signed with KSK only (update-check-ksk, dnssec-ksk-only) ($n)"
+  ret=0
+  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
+  lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
+  test "$lines" -eq 1 || ret=1
+  grep $KSK_ID dig.out.test$n > /dev/null || ret=1
+  grep $ZSK_ID dig.out.test$n > /dev/null && ret=1
+  n=$((n+1))
+  test "$ret" -eq 0 || echo_i "failed"
+  status=$((status+ret))
+done
+
+echo_i "checking SOA RRset is signed with ZSK only (update-check-ksk and dnssec-ksk-only) ($n)"
+ret=0
+dig_with_opts $SECTIONS @10.53.0.2 soa $zone > dig.out.test$n
+lines=$(awk '$4 == "RRSIG" && $5 == "SOA" {print}' dig.out.test$n | wc -l)
+grep $KSK_ID dig.out.test$n > /dev/null && ret=1
+grep $ZSK_ID dig.out.test$n > /dev/null || ret=1
+test "$lines" -eq 1 || ret=1
+n=$((n+1))
+test "$ret" -eq 0 || echo_i "failed"
+status=$((status+ret))
+
+# Roll the ZSK.
+sleep 1
+zsk2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 -n zone "$zone")
+echo_i "new ZSK $zsk2 created for zone $zone"
+echo "$zsk2" | sed -e 's/.*[+]//' -e 's/^0*//' > ns2/$zone.zsk.id2
+ZSK_ID2=`cat ns2/$zone.zsk.id2`
+dnssec_loadkeys_on 2 $zone
+
+# Wait until new ZSK becomes active.
+sleep 1
+echo_i "make ZSK $ZSK inactive and make new ZSK $zsk2 active for zone $zone"
+$SETTIME -I now -K ns2 $ZSK > /dev/null
+$SETTIME -A now -K ns2 $zsk2 > /dev/null
+dnssec_loadkeys_on 2 $zone
+
+# Remove the KSK from disk.
+sleep 1
+echo_i "remove the KSK $KSK for zone $zone from disk"
+mv ns2/$KSK.key ns2/$KSK.key.bak
+mv ns2/$KSK.private ns2/$KSK.private.bak
+
+# Update the zone that requires a resign of the SOA RRset.
+sleep 1
+echo_i "update the zone with $zone IN TXT nsupdate added me"
+(
+echo zone $zone
+echo server 10.53.0.2 "$PORT"
+echo update add $zone. 300 in txt "nsupdate added me"
+echo send
+) | $NSUPDATE
+
+# Redo the tests now that the zone is updated and the KSK is offline.
+for qtype in "DNSKEY" "CDNSKEY" "CDS"
+do
+  echo_i "checking $qtype RRset is signed with KSK only, KSK offline (update-check-ksk, dnssec-ksk-only) ($n)"
+  ret=0
+  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
+  lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
+  test "$lines" -eq 1 || ret=1
+  grep $KSK_ID  dig.out.test$n > /dev/null || ret=1
+  grep $ZSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID2 dig.out.test$n > /dev/null && ret=1
+  n=$((n+1))
+  test "$ret" -eq 0 || echo_i "failed"
+  status=$((status+ret))
+done
+
+for qtype in "SOA" "TXT"
+do
+  echo_i "checking $qtype RRset is signed with ZSK only, KSK offline (update-check-ksk and dnssec-ksk-only) ($n)"
+  ret=0
+  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
+  lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
+  grep $KSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID2 dig.out.test$n > /dev/null || ret=1
+  test "$lines" -eq 1 || ret=1
+  n=$((n+1))
+  test "$ret" -eq 0 || echo_i "failed"
+  status=$((status+ret))
+done
+
+# Put back the KSK.
+sleep 1
+echo_i "put back the KSK $KSK for zone $zone from disk"
+mv ns2/$KSK.key.bak ns2/$KSK.key
+mv ns2/$KSK.private.bak ns2/$KSK.private
+
+# Roll the ZSK again.
+sleep 1
+zsk3=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 -n zone "$zone")
+echo_i "new ZSK $zsk3 created for zone $zone"
+echo "$zsk3" | sed -e 's/.*[+]//' -e 's/^0*//' > ns2/$zone.zsk.id3
+ZSK_ID3=`cat ns2/$zone.zsk.id3`
+dnssec_loadkeys_on 2 $zone
+
+# Wait until new ZSK becomes active.
+sleep 1
+echo_i "delete old ZSK $ZSK make ZSK $ZSK2 inactive and make new ZSK $zsk3 active for zone $zone"
+$SETTIME -D now -K ns2 $ZSK > /dev/null
+$SETTIME -I +5 -K ns2 $zsk2 > /dev/null
+$SETTIME -A +5 -K ns2 $zsk3 > /dev/null
+dnssec_loadkeys_on 2 $zone
+
+# Remove the KSK from disk.
+sleep 1
+echo_i "remove the KSK $KSK for zone $zone from disk"
+mv ns2/$KSK.key ns2/$KSK.key.bak
+mv ns2/$KSK.private ns2/$KSK.private.bak
+
+# Update the zone that requires a resign of the SOA RRset.
+sleep 1
+echo_i "update the zone with $zone IN TXT nsupdate added me again"
+(
+echo zone $zone
+echo server 10.53.0.2 "$PORT"
+echo update add $zone. 300 in txt "nsupdate added me again"
+echo send
+) | $NSUPDATE
+
+# Redo the tests now that the ZSK roll has deleted the old key.
+for qtype in "DNSKEY" "CDNSKEY" "CDS"
+do
+  echo_i "checking $qtype RRset is signed with KSK only, old ZSK deleted (update-check-ksk, dnssec-ksk-only) ($n)"
+  ret=0
+  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
+  lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
+  test "$lines" -eq 1 || ret=1
+  grep $KSK_ID  dig.out.test$n > /dev/null || ret=1
+  grep $ZSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID2 dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID3 dig.out.test$n > /dev/null && ret=1
+  n=$((n+1))
+  test "$ret" -eq 0 || echo_i "failed"
+  status=$((status+ret))
+done
+
+for qtype in "SOA" "TXT"
+do
+  echo_i "checking $qtype RRset is signed with ZSK only, old ZSK deleted (update-check-ksk and dnssec-ksk-only) ($n)"
+  ret=0
+  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
+  lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
+  grep $KSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID2 dig.out.test$n > /dev/null || ret=1
+  grep $ZSK_ID3 dig.out.test$n > /dev/null && ret=1
+  test "$lines" -eq 1 || ret=1
+  n=$((n+1))
+  test "$ret" -eq 0 || echo_i "failed"
+  status=$((status+ret))
+done
+
+# Wait for newest ZSK to become active.
+echo_i "sleep 6 to make new ZSK $zsk3 active and ZSK $zsk2 inactive"
+sleep 6
+
+# Redo the tests one more time.
+for qtype in "DNSKEY" "CDNSKEY" "CDS"
+do
+  echo_i "checking $qtype RRset is signed with KSK only, new ZSK active (update-check-ksk, dnssec-ksk-only) ($n)"
+  ret=0
+  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone > dig.out.test$n
+  lines=$(awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print}' dig.out.test$n | wc -l)
+  test "$lines" -eq 1 || ret=1
+  grep $KSK_ID  dig.out.test$n > /dev/null || ret=1
+  grep $ZSK_ID  dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID2 dig.out.test$n > /dev/null && ret=1
+  grep $ZSK_ID3 dig.out.test$n > /dev/null && ret=1
+  n=$((n+1))
+  test "$ret" -eq 0 || echo_i "failed"
+  status=$((status+ret))
+done
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1