]> 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>
Thu, 11 Apr 2019 13:22:30 +0000 (15:22 +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.

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 2010bada1601be67fd65894a3eca34988375bec1..c49ee9a1ac7551637b171257d082d2e71fafc66e 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
@@ -49,6 +49,7 @@ 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
index 189e275014ab72d72dfed9ef0814896e1d8092f2..7a4bcf0cdcaf1d221b03af10fcfdc89d70763515 100644 (file)
@@ -26,6 +26,15 @@ options {
        minimal-responses no;
 };
 
+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";
@@ -167,6 +176,18 @@ 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";
index d0a978b61c82c74fd34f4265038cc6746c0f726a..eb2721598f502f6a047106ee400d5a73280fe4cf 100644 (file)
@@ -314,3 +314,20 @@ key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$
 key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -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 -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$zone")
+key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -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 3aa67472299060a15871f8f8164317334d780e93..17b4ceb5ab1bd9aa558047d15d31a808cf08f59c 100644 (file)
@@ -40,6 +40,26 @@ 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 "-- $* --"
@@ -2653,7 +2673,7 @@ my_dig() {
     "$DIG" +noadd +nosea +nostat +noquest +nocomm +nocmd -p "$PORT" @10.53.0.4 "$@"
 }
 
-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
 firstVal=$(my_dig insecure.example. dnskey| awk '$1 != ";;" { print $2 }')
 sleep 1
@@ -2709,7 +2729,7 @@ do
        fi
        echo_i "sleeping ...."
        sleep 3
-done;
+done
 grep "ANSWER: 3," dig.out.ns2.test$n > /dev/null || ret=1
 if [ "$ret" -ne 0 ]; then echo_i "nsec3 chain generation not complete"; fi
 dig_with_opts +noauth +nodnssec soa nsec3chain-test @10.53.0.2 > dig.out.ns2.test$n || ret=1
@@ -3856,5 +3876,198 @@ n=$((n+1))
 test "$ret" -eq 0 || echo_i "failed"
 status=$((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