]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
convert dnssec signing tests to python
authorEvan Hunt <each@isc.org>
Wed, 2 Jul 2025 08:06:19 +0000 (01:06 -0700)
committerEvan Hunt <each@isc.org>
Thu, 31 Jul 2025 19:55:40 +0000 (12:55 -0700)
the shell tests that queried servers to check correct signing
behavior (using dnssec-signzone, dnssec-policy and nsupdate),
as well as "rndc signing", private-type records, rndc zonestatus,
offline keys, etc, have been moved to tests_signing.py.

the minimal update test in the dnssec_update_test.pl script
was also moved here and the perl script has been removed.

bin/tests/system/dnssec/dnssec_update_test.pl [deleted file]
bin/tests/system/dnssec/ns2/named.conf.j2
bin/tests/system/dnssec/ns2/sign.sh
bin/tests/system/dnssec/tests.sh
bin/tests/system/dnssec/tests_sh_dnssec.py
bin/tests/system/dnssec/tests_signing.py [new file with mode: 0644]
bin/tests/system/dnssec/tests_validation.py

diff --git a/bin/tests/system/dnssec/dnssec_update_test.pl b/bin/tests/system/dnssec/dnssec_update_test.pl
deleted file mode 100644 (file)
index a06c563..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/perl
-
-# 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 https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-#
-# DNSSEC Dynamic update test suite.
-#
-# Usage:
-#
-#   perl update_test.pl [-s server] [-p port] zone
-#
-# The server defaults to 127.0.0.1.
-# The port defaults to 53.
-#
-# Installation notes:
-#
-# This program uses the Net::DNS::Resolver module.
-# You can install it by saying
-#
-#    perl -MCPAN -e "install Net::DNS"
-#
-
-use Getopt::Std;
-use Net::DNS;
-use Net::DNS::Update;
-use Net::DNS::Resolver;
-
-$opt_s = "127.0.0.1";
-$opt_p = 53;
-
-getopt('s:p:');
-
-$res = new Net::DNS::Resolver;
-$res->nameservers($opt_s);
-$res->port($opt_p);
-$res->defnames(0); # Do not append default domain.
-
-@ARGV == 1 or die
-    "usage: perl update_test.pl [-s server] [-p port] zone\n";
-
-$zone = shift @ARGV;
-
-my $failures = 0;
-
-sub assert {
-    my ($cond, $explanation) = @_;
-    if (!$cond) {
-       print "Test Failed: $explanation ***\n";
-       $failures++
-    }
-}
-
-sub test {
-    my ($expected, @records) = @_;
-
-    my $update = new Net::DNS::Update("$zone");
-
-    foreach $rec (@records) {
-       $update->push(@$rec);
-    }
-
-    $reply = $res->send($update);
-
-    # Did it work?
-    if (defined $reply) {
-       my $rcode = $reply->header->rcode;
-        assert($rcode eq $expected, "expected $expected, got $rcode");
-    } else {
-       print "Update failed: ", $res->errorstring, "\n";
-    }
-}
-
-sub section {
-    my ($msg) = @_;
-    print "$msg\n";
-}
-
-section("Add a name");
-test("NOERROR", ["update", rr_add("a.$zone 300 A 73.80.65.49")]);
-
-section("Delete the name");
-test("NOERROR", ["update", rr_del("a.$zone")]);
-
-if ($failures) {
-    print "$failures update tests failed.\n";
-} else {
-    print "All update tests successful.\n";
-}
-
-exit $failures;
index da6df5d5fbd8dc5223271f3c85e2f62b46b9edce..093aba3120e6df03eb45e1fbbf867d9c6981708f 100644 (file)
@@ -185,7 +185,7 @@ zone "cdnskey-auto.secure" {
 
 zone "updatecheck-kskonly.secure" {
        type primary;
-       file "updatecheck-kskonly.secure.db.signed";
+       file "updatecheck-kskonly.secure.db";
        dnssec-policy kskonly;
        allow-update { any; };
 };
index 2658fd10b35091ce206b4c681b12cccb96e8e08a..ca824a2ad3f04e76b1392a222a11cf86aef2c571 100644 (file)
@@ -297,7 +297,6 @@ $SETTIME -s -g OMNIPRESENT -k OMNIPRESENT now -r OMNIPRESENT now -d RUMOURED now
 $SETTIME -s -g OMNIPRESENT -k OMNIPRESENT now -z OMNIPRESENT now $key2 >settime.out.$zone.zsk 2>&1
 # Don't sign, let dnssec-policy maintain do it.
 cat "$infile" "$key1.key" "$key2.key" >"$zonefile"
-mv $zonefile "$zonefile.signed"
 
 zone=hours-vs-days
 infile=hours-vs-days.db.in
index 2b4f9c864a8a7716b8b8a54193dca786bf29dde8..89fcf1c6ab274177d551a0fdfb18749b146b0198 100644 (file)
@@ -19,62 +19,10 @@ set -e
 status=0
 n=1
 
-rm -f dig.out.*
-
-dig_with_opts() {
-  "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@"
-}
-
-dig_with_answeropts() {
-  "$DIG" +noall +answer +dnssec -p "$PORT" "$@"
-}
-
 delv_with_opts() {
   "$DELV" -a ns1/trusted.conf -p "$PORT" "$@"
 }
 
-rndccmd() {
-  "$RNDC" -c ../_common/rndc.conf -p "$CONTROLPORT" -s "$@"
-}
-
-# TODO: Move loadkeys_on to conf.sh.common
-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 20 "next key event" ns${nsidx}/named.run || return 1
-}
-
-# convert private-type records to readable form
-showprivate() {
-  echo "-- $* --"
-  dig_with_opts +nodnssec +short "@$2" -t type65534 "$1" >dig.out.$1.test$n
-  cut -f3 -d' ' <dig.out.$1.$n | while read -r record; do
-    # shellcheck disable=SC2016
-    $PERL -e 'my $rdata = pack("H*", @ARGV[0]);
-              die "invalid record" unless length($rdata) == 5 || length($rdata) == 7;
-              my ($dns, $key, $remove, $complete, $alg) = unpack("CnCCn", $rdata);
-              die "invalid record" unless $dns != 0;
-              my $action = "signing";
-              $action = "removing" if $remove;
-              my $state = " (incomplete)";
-              $state = " (complete)" if $complete;
-              $alg = $dns if ! defined($alg);
-              print ("$action: alg: $alg, key: $key$state\n");' "$record"
-  done
-}
-
-# check that signing records are marked as complete
-checkprivate() {
-  for i in 1 2 3 4 5 6 7 8 9 10; do
-    showprivate "$@" | grep -q incomplete || return 0
-    sleep 1
-  done
-  echo_d "$1 signing incomplete"
-  return 1
-}
-
 if [ -x "${DELV}" ]; then
   ret=0
   echo_i "checking positive validation NSEC using dns_client ($n)"
@@ -286,691 +234,5 @@ if [ -x "${DELV}" ]; then
   status=$((status + ret))
 fi
 
-# Run a minimal update test if possible.  This is really just
-# a regression test for RT #2399; more tests should be added.
-
-if $PERL -e 'use Net::DNS;' 2>/dev/null; then
-  echo_i "running DNSSEC update test"
-  ret=0
-  {
-    output=$($PERL dnssec_update_test.pl -s 10.53.0.3 -p "$PORT" dynamic.example.)
-    rc=$?
-  } || true
-  test "$rc" -eq 0 || ret=1
-  echo "$output" | cat_i
-  [ $ret -eq 1 ] && status=1
-else
-  echo_i "The DNSSEC update test requires the Net::DNS library." >&2
-fi
-
-echo_i "checking that the NSEC3 record for the apex is properly signed when a DNSKEY is added via UPDATE ($n)"
-ret=0
-(
-  kskname=$($KEYGEN -q -3 -a $DEFAULT_ALGORITHM -fk update-nsec3.example)
-  (
-    echo zone update-nsec3.example
-    echo server 10.53.0.3 "$PORT"
-    grep DNSKEY "${kskname}.key" | sed -e 's/^/update add /' -e 's/IN/300 IN/'
-    echo send
-  ) | $NSUPDATE
-)
-dig_with_opts +dnssec a update-nsec3.example. @10.53.0.4 >dig.out.ns4.test$n || ret=1
-grep "NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "flags:.* ad[ ;]" dig.out.ns4.test$n >/dev/null || ret=1
-grep "NSEC3 1 0 0 - .*" dig.out.ns4.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "checking that signing records have been marked as complete ($n)"
-ret=0
-checkprivate dynamic.example 10.53.0.3 || ret=1
-checkprivate auto-nsec3.example 10.53.0.3 || ret=1
-checkprivate expiring.example 10.53.0.3 || ret=1
-checkprivate auto-nsec.example 10.53.0.3 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing' without arguments is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing -list' without zone is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing -list >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing -clear' without additional arguments is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing -clear >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that 'rndc signing -clear all' without zone is handled ($n)"
-ret=0
-rndccmd 10.53.0.3 signing -clear all >/dev/null 2>&1 && ret=1
-rndccmd 10.53.0.3 status >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check rndc signing -list output ($n)"
-ret=0
-{ rndccmd 10.53.0.3 signing -list dynamic.example >signing.out.dynamic.example; } 2>&1
-grep -q "No signing records found" signing.out.dynamic.example || {
-  ret=1
-  sed 's/^/ns3 /' signing.out.dynamic.example | cat_i
-}
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a split dnssec dnssec-signzone work ($n)"
-ret=0
-dig_with_opts soa split-dnssec.example. @10.53.0.4 >dig.out.ns4.test$n || ret=1
-grep "NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "ANSWER: 2," dig.out.ns4.test$n >/dev/null || ret=1
-grep "flags:.* ad[ ;]" dig.out.ns4.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a smart split dnssec dnssec-signzone work ($n)"
-ret=0
-dig_with_opts soa split-smart.example. @10.53.0.4 >dig.out.ns4.test$n || ret=1
-grep "NOERROR" dig.out.ns4.test$n >/dev/null || ret=1
-grep "ANSWER: 2," dig.out.ns4.test$n >/dev/null || ret=1
-grep "flags:.* ad[ ;]" dig.out.ns4.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "testing soon-to-expire RRSIGs without a replacement private key ($n)"
-ret=0
-dig_with_answeropts +nottlid expiring.example ns @10.53.0.3 | grep RRSIG >dig.out.ns3.test$n 2>&1
-# there must be a signature here
-[ -s dig.out.ns3.test$n ] || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that named doesn't loop when all private keys are not available ($n)"
-ret=0
-lines=$(grep -c "reading private key file expiring.example" ns3/named.run || true)
-test "${lines:-1000}" -lt 15 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check the correct resigning time is reported in zonestatus ($n)"
-ret=0
-rndccmd 10.53.0.3 \
-  zonestatus secure.example >rndc.out.ns3.test$n
-# next resign node: secure.example/DNSKEY
-qname=$(awk '/next resign node:/ { print $4 }' rndc.out.ns3.test$n | sed 's,/.*,,')
-qtype=$(awk '/next resign node:/ { print $4 }' rndc.out.ns3.test$n | sed 's,.*/,,')
-# next resign time: Thu, 24 Apr 2014 10:38:16 GMT
-time=$(awk 'BEGIN { m["Jan"] = "01"; m["Feb"] = "02"; m["Mar"] = "03";
-                  m["Apr"] = "04"; m["May"] = "05"; m["Jun"] = "06";
-                  m["Jul"] = "07"; m["Aug"] = "08"; m["Sep"] = "09";
-                  m["Oct"] = "10"; m["Nov"] = "11"; m["Dec"] = "12";}
-        /next resign time:/ { printf "%d%s%02d%s\n", $7, m[$6], $5, $8 }' rndc.out.ns3.test$n | sed 's/://g')
-dig_with_opts +noall +answer "$qname" "$qtype" @10.53.0.3 >dig.out.test$n
-expire=$(awk '$4 == "RRSIG" { print $9 }' dig.out.test$n)
-inception=$(awk '$4 == "RRSIG" { print $10 }' dig.out.test$n)
-$PERL -e 'exit(0) if ("'"$time"'" lt "'"$expire"'" && "'"$time"'" gt "'"$inception"'"); exit(1);' || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are signed using KSK by dnssec-signzone ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cds cds.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are not signed using ZSK by dnssec-signzone -x ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cds cds-x.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are signed using KSK by with dnssec-policy ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cds cds-auto.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a CDS deletion record is accepted ($n)"
-ret=0
-(
-  echo zone cds-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cds-update.secure CDS
-  echo update add cds-update.secure 0 CDS 0 0 0 00
-  echo send
-) | $NSUPDATE >nsupdate.out.test$n 2>&1
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "${lines:-10}" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" && $5 == "0" && $6 == "0" && $7 == "0" && $8 == "00" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS records are signed only using KSK when added by nsupdate ($n)"
-ret=0
-keyid=$(cat ns2/cds-update.secure.id)
-(
-  echo zone cds-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cds-update.secure CDS
-  echo send
-  dig_with_opts +noall +answer @10.53.0.2 dnskey cds-update.secure \
-    | grep "DNSKEY.257" \
-    | $DSFROMKEY -12 -C -f - -T 1 cds-update.secure \
-    | sed "s/^/update add /"
-  echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDS" && $11 == id {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDS deletion records are signed only using KSK when added by nsupdate ($n)"
-ret=0
-keyid=$(cat ns2/cds-update.secure.id)
-(
-  echo zone cds-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cds-update.secure CDS
-  echo update add cds-update.secure 0 CDS 0 0 0 00
-  echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDS" && $11 == id {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" && $5 == "0" && $6 == "0" && $7 == "0" && $8 == "00" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a non matching CDS record is accepted with a matching CDS record ($n)"
-ret=0
-(
-  echo zone cds-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cds-update.secure CDS
-  echo send
-  dig_with_opts +noall +answer @10.53.0.2 dnskey cds-update.secure \
-    | grep "DNSKEY.257" \
-    | $DSFROMKEY -12 -C -f - -T 1 cds-update.secure \
-    | sed "s/^/update add /"
-  dig_with_opts +noall +answer @10.53.0.2 dnskey cds-update.secure \
-    | grep "DNSKEY.257" | sed 's/DNSKEY.257/DNSKEY 258/' \
-    | $DSFROMKEY -12 -C -A -f - -T 1 cds-update.secure \
-    | sed "s/^/update add /"
-  echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 4 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are signed using KSK by dnssec-signzone ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are not signed using ZSK by dnssec-signzone -x ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-x.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are signed using KSK by with dnssec-auto ($n)"
-ret=0
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-auto.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# TODO: test case for GL #1689.
-# If we allow the dnssec tools to use deprecated algorithms (such as RSAMD5)
-# we could write a test that signs a zone with supported and unsupported
-# algorithm, apply a fixed rrset order such that the unsupported algorithm
-# precedes the supported one in the DNSKEY RRset, and verify the result still
-# validates succesfully.
-
-echo_i "check that a CDNSKEY deletion record is accepted ($n)"
-ret=0
-(
-  echo zone cdnskey-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cdnskey-update.secure CDNSKEY
-  echo update add cdnskey-update.secure 0 CDNSKEY 0 3 0 AA==
-  echo send
-) | $NSUPDATE >nsupdate.out.test$n 2>&1
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure >dig.out.test$n
-lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "${lines:-10}" -eq 1 || ret=1
-lines=$(awk '$4 == "CDNSKEY" && $5 == "0" && $6 == "3" && $7 == "0" && $8 == "AA==" {print}' dig.out.test$n | wc -l)
-test "${lines:-10}" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that CDNSKEY records are signed using KSK only when added by nsupdate ($n)"
-ret=0
-keyid=$(cat ns2/cdnskey-update.secure.id)
-(
-  echo zone cdnskey-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cdnskey-update.secure CDNSKEY
-  dig_with_opts +noall +answer @10.53.0.2 dnskey cdnskey-update.secure \
-    | sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 257/p'
-  echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDNSKEY" && $11 == id {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that a non matching CDNSKEY record is accepted with a matching CDNSKEY record ($n)"
-ret=0
-(
-  echo zone cdnskey-update.secure
-  echo server 10.53.0.2 "$PORT"
-  echo update delete cdnskey-update.secure CDNSKEY
-  dig_with_opts +noall +answer @10.53.0.2 dnskey cdnskey-update.secure \
-    | sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 257/p'
-  dig_with_opts +noall +answer @10.53.0.2 dnskey cdnskey-update.secure \
-    | sed -n -e "s/^/update add /" -e 's/DNSKEY.257/CDNSKEY 258/p'
-  echo send
-) | $NSUPDATE
-dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure >dig.out.test$n
-lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l)
-test "$lines" -eq 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that DNAME at apex with NSEC3 is correctly signed (dnssec-signzone) ($n)"
-ret=0
-dig_with_opts txt dname-at-apex-nsec3.example @10.53.0.3 >dig.out.ns3.test$n || ret=1
-grep "RRSIG.NSEC3 $DEFAULT_ALGORITHM_NUMBER 3 600" dig.out.ns3.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "check that DNSKEY and other occluded data are excluded from the delegating bitmap ($n)"
-ret=0
-dig_with_opts axfr occluded.example @10.53.0.3 >dig.out.ns3.test$n || ret=1
-grep "^delegation.occluded.example..*NSEC.*NS KEY DS RRSIG NSEC$" dig.out.ns3.test$n >/dev/null || ret=1
-grep "^delegation.occluded.example..*DNSKEY.*" dig.out.ns3.test$n >/dev/null || ret=1
-grep "^delegation.occluded.example..*AAAA.*" dig.out.ns3.test$n >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-echo_i "checking DNSSEC records are occluded from ANY in an insecure zone ($n)"
-ret=0
-dig_with_opts any x.insecure.example. @10.53.0.3 >dig.out.ns3.1.test$n || ret=1
-grep "status: NOERROR" dig.out.ns3.1.test$n >/dev/null || ret=1
-grep "ANSWER: 0," dig.out.ns3.1.test$n >/dev/null || ret=1
-dig_with_opts any z.secure.example. @10.53.0.3 >dig.out.ns3.2.test$n || ret=1
-grep "status: NOERROR" dig.out.ns3.2.test$n >/dev/null || ret=1
-# A+RRSIG, NSEC+RRSIG
-grep "ANSWER: 4," dig.out.ns3.2.test$n >/dev/null || ret=1
-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"
-
-# Set key state for KSK. The ZSK rollovers below assume that there is a chain
-# of trust established, so we tell named that the DS is in omnipresent state.
-$SETTIME -s -d OMNIPRESENT now -K ns2 $KSK >/dev/null
-
-# Print IDs of keys used for generating RRSIG records for RRsets of type $1
-# found in dig output file $2.
-get_keys_which_signed() {
-  qtype=$1
-  output=$2
-  # The key ID is the 11th column of the RRSIG record line.
-  awk -v qt="$qtype" '$4 == "RRSIG" && $5 == qt {print $11}' <"$output"
-}
-
-# 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 ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/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 ($n)"
-ret=0
-dig_with_opts $SECTIONS @10.53.0.2 soa $zone >dig.out.test$n
-lines=$(get_keys_which_signed "SOA" dig.out.test$n | wc -l)
-test "$lines" -eq 1 || ret=1
-get_keys_which_signed "SOA" dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
-get_keys_which_signed "SOA" dig.out.test$n | grep "^$ZSK_ID$" >/dev/null || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Roll the ZSK.
-zsk2=$("$KEYGEN" -q -P none -A none -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 "$zone")
-keyfile_to_key_id "$zsk2" >ns2/$zone.zsk.id2
-ZSK_ID2=$(cat ns2/$zone.zsk.id2)
-ret=0
-echo_i "prepublish new ZSK $ZSK_ID2 for $zone ($n)"
-rndccmd 10.53.0.2 dnssec -rollover -key $ZSK_ID $zone 2>&1 | sed 's/^/ns2 /' | cat_i
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-zsk_count_equals() {
-  expectedzsks=$1
-  dig_with_opts @10.53.0.2 DNSKEY $zone >dig.out.test$n
-  lines=$(cat dig.out.test$n | grep "DNSKEY.*256 3 13" | wc -l)
-  test "$lines" -eq $expectedzsks || return 1
-}
-echo_i "check DNSKEY RRset has successor ZSK $ZSK_ID2 ($n)"
-ret=0
-# The expected number of ZSKs is 2.
-retry_quiet 5 zsk_count_equals 2 || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Make new ZSK active.
-echo_i "make ZSK $ZSK_ID inactive and make new ZSK $ZSK_ID2 active for zone $zone ($n)"
-ret=0
-$SETTIME -s -I now -K ns2 $ZSK >/dev/null
-$SETTIME -s -k OMNIPRESENT now -A now -K ns2 $zsk2 >/dev/null
-dnssec_loadkeys_on 2 $zone || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Wait for newest ZSK to become active.
-echo_i "wait until new ZSK $ZSK_ID2 active and ZSK $ZSK_ID inactive"
-for i in 1 2 3 4 5 6 7 8 9 10; do
-  ret=0
-  grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID2 (ZSK) is now active" ns2/named.run >/dev/null || ret=1
-  grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID (ZSK) is now inactive" ns2/named.run >/dev/null || ret=1
-  [ "$ret" -eq 0 ] && break
-  sleep 1
-done
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Remove the KSK from disk.
-echo_i "remove the KSK $KSK_ID 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.
-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 ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/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 new ZSK $ZSK_ID2 only, KSK offline ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null || ret=1
-  n=$((n + 1))
-  test "$ret" -eq 0 || echo_i "failed"
-  status=$((status + ret))
-done
-
-# Put back the KSK.
-echo_i "put back the KSK $KSK_ID 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.
-zsk3=$("$KEYGEN" -q -P none -A none -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -K ns2 "$zone")
-ret=0
-keyfile_to_key_id "$zsk3" >ns2/$zone.zsk.id3
-ZSK_ID3=$(cat ns2/$zone.zsk.id3)
-echo_i "delete old ZSK $ZSK_ID, schedule ZSK $ZSK_ID2 inactive, and pre-publish ZSK $ZSK_ID3 for zone $zone ($n)"
-$SETTIME -s -k HIDDEN now -z HIDDEN now -D now -K ns2 $ZSK >/dev/null
-$SETTIME -s -k OMNIPRESENT now -z OMNIPRESENT now -K ns2 $zsk2 >/dev/null
-dnssec_loadkeys_on 2 $zone || ret=1
-rndccmd 10.53.0.2 dnssec -rollover -key $ZSK_ID2 $zone 2>&1 | sed 's/^/ns2 /' | cat_i
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Wait for newest ZSK to become published.
-echo_i "wait until new ZSK $ZSK_ID3 published"
-for i in 1 2 3 4 5 6 7 8 9 10; do
-  ret=0
-  grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID3 (ZSK) is now published" ns2/named.run >/dev/null || ret=1
-  [ "$ret" -eq 0 ] && break
-  sleep 1
-done
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Remove the KSK from disk.
-echo_i "remove the KSK $KSK_ID 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.
-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 ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/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 $ZSK_ID2 only, old ZSK deleted ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/dev/null && ret=1
-  n=$((n + 1))
-  test "$ret" -eq 0 || echo_i "failed"
-  status=$((status + ret))
-done
-
-# Put back the KSK.
-echo_i "put back the KSK $KSK_ID for zone $zone from disk"
-mv ns2/$KSK.key.bak ns2/$KSK.key
-mv ns2/$KSK.private.bak ns2/$KSK.private
-
-# Make the new ZSK (ZSK3) active.
-echo_i "make new ZSK $ZSK_ID3 active for zone $zone ($n)"
-ret=0
-$SETTIME -s -I now -K ns2 $zsk2 >/dev/null
-$SETTIME -s -k OMNIPRESENT now -A now -K ns2 $zsk3 >/dev/null
-dnssec_loadkeys_on 2 $zone || ret=1
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Wait for newest ZSK to become active.
-echo_i "wait until new ZSK $ZSK_ID3 active and ZSK $ZSK_ID2 inactive"
-for i in 1 2 3 4 5 6 7 8 9 10; do
-  ret=0
-  grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID3 (ZSK) is now active" ns2/named.run >/dev/null || ret=1
-  grep "DNSKEY $zone/$DEFAULT_ALGORITHM/$ZSK_ID2 (ZSK) is now inactive" ns2/named.run >/dev/null || ret=1
-  [ "$ret" -eq 0 ] && break
-  sleep 1
-done
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# Remove the KSK from disk.
-echo_i "remove the KSK $KSK_ID 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.
-echo_i "update the zone with $zone IN TXT nsupdate added me one more time"
-(
-  echo zone $zone
-  echo server 10.53.0.2 "$PORT"
-  echo update add $zone. 300 in txt "nsupdate added me one more time"
-  echo send
-) | $NSUPDATE
-n=$((n + 1))
-test "$ret" -eq 0 || echo_i "failed"
-status=$((status + ret))
-
-# 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 ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/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 new ZSK $ZSK_ID3 only, new ZSK active ($n)"
-  ret=0
-  dig_with_opts $SECTIONS @10.53.0.2 $qtype $zone >dig.out.test$n
-  lines=$(get_keys_which_signed $qtype dig.out.test$n | wc -l)
-  test "$lines" -eq 1 || ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$KSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID2$" >/dev/null && ret=1
-  get_keys_which_signed $qtype dig.out.test$n | grep "^$ZSK_ID3$" >/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
index 9f9cc76bf68a9faf4923307430b1c27f6951b5a8..8448082c8ca67edd7a72fe22446a7d1cffd5d6f6 100644 (file)
@@ -72,6 +72,7 @@ pytestmark = pytest.mark.extra_artifacts(
         "ns2/too-many-iterations.db",
         "ns2/inconsistent.db",
         "ns2/trusted.db",
+        "ns2/updatecheck-kskonly.secure.db",
         "ns2/updatecheck-kskonly.secure.ksk.id",
         "ns2/updatecheck-kskonly.secure.ksk.key",
         "ns2/updatecheck-kskonly.secure.zsk.id",
diff --git a/bin/tests/system/dnssec/tests_signing.py b/bin/tests/system/dnssec/tests_signing.py
new file mode 100644 (file)
index 0000000..a5919e7
--- /dev/null
@@ -0,0 +1,613 @@
+# 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 https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from collections import namedtuple
+import os
+import re
+import struct
+import time
+
+from dns import dnssec, name, rdataclass, rdatatype, update
+
+import pytest
+
+pytest.importorskip("dns", minversion="2.0.0")
+import isctest
+
+
+# helper functions
+def grep_c(regex, filename):
+    with open(filename, "r", encoding="utf-8") as f:
+        blob = f.read().splitlines()
+    results = [x for x in blob if re.search(regex, x)]
+    return len(results)
+
+
+# run dnssec-keygen
+def keygen(*args):
+    keygen_cmd = [os.environ.get("KEYGEN")]
+    keygen_cmd.extend(args)
+    return isctest.run.cmd(keygen_cmd, log_stdout=True).stdout.decode("utf-8").strip()
+
+
+# run dnssec-settime
+def settime(*args):
+    settime_cmd = [os.environ.get("SETTIME")]
+    settime_cmd.extend(args)
+    return isctest.run.cmd(settime_cmd, log_stdout=True).stdout.decode("utf-8").strip()
+
+
+@pytest.mark.parametrize(
+    "domain",
+    [
+        "auto-nsec.example",
+        "auto-nsec3.example",
+    ],
+)
+def test_signing_complete(domain):
+    PrivateType = namedtuple("PrivateType", ["alg", "key", "rem", "complete"])
+
+    def convert_private(rdata) -> PrivateType:
+        length = len(rdata.to_wire())
+        assert length in (5, 7)
+        if length == 7:
+            _, key, rem, complete, alg = struct.unpack(">BHBBH", rdata.to_wire())
+        else:
+            alg, key, rem, complete = struct.unpack(">BHBB", rdata.to_wire())
+        return PrivateType(alg, key, rem, complete)
+
+    # query for a private type record, make sure it shows "complete"
+    def check_complete():
+        msg = isctest.query.create(domain, 65534)
+        res = isctest.query.tcp(msg, "10.53.0.3")
+        assert res.answer
+        for rdata in res.answer[0]:
+            record = convert_private(rdata)
+            assert record.complete
+        return True
+
+    isctest.run.retry_with_timeout(check_complete, 10)
+
+
+def test_split_dnssec():
+    # check that split-dnssec signing worked (dnssec-signzone -D)
+    msg = isctest.query.create("split-dnssec.example.", "SOA")
+    res1 = isctest.query.tcp(msg, "10.53.0.3")
+    res2 = isctest.query.tcp(msg, "10.53.0.4")
+    isctest.check.same_answer(res1, res2)
+    isctest.check.noerror(res2)
+    isctest.check.rr_count_eq(res2.answer, 2)
+    isctest.check.adflag(res2)
+
+    # check that smart split-dnssec signing worked (dnssec-signzone -DS)
+    msg = isctest.query.create("split-smart.example.", "SOA")
+    res1 = isctest.query.tcp(msg, "10.53.0.3")
+    res2 = isctest.query.tcp(msg, "10.53.0.4")
+    isctest.check.same_answer(res1, res2)
+    isctest.check.noerror(res2)
+    isctest.check.rr_count_eq(res2.answer, 2)
+    isctest.check.adflag(res2)
+
+
+def test_expiring_rrsig():
+    # check soon-to-expire RRSIGs without a replacement private
+    # key aren't deleted. this response has to have an RRSIG:
+    msg = isctest.query.create("expiring.example.", "NS")
+    res = isctest.query.tcp(msg, "10.53.0.3")
+    _, sigs = res.answer
+    assert sigs
+
+    # check that named doesn't loop when private keys are not available
+    n = grep_c("reading private key file expiring.example", "ns3/named.run")
+    assert n < 15
+
+    # check expired signatures stay place when updates are disabled
+    msg = isctest.query.create("expired.example", "SOA")
+    res = isctest.query.tcp(msg, "10.53.0.3")
+    _, sigs = res.answer
+    assert sigs
+
+
+def test_apex_signing():
+    # check that DNAME at apex with NSEC3 is correctly signed
+    msg = isctest.query.create("dname-at-apex-nsec3.example.", "TXT")
+    res = isctest.query.tcp(msg, "10.53.0.3")
+    sigs = [str(a) for a in res.authority if a.rdtype == rdatatype.RRSIG]
+    alg = os.environ.get("DEFAULT_ALGORITHM_NUMBER")
+    assert any(f"NSEC3 {alg} 3 600" in a for a in sigs)
+
+
+def test_occluded_data():
+    # check that DNSKEY and other occluded data are excluded from
+    # a delegating bitmap
+    msg = isctest.query.create("occluded.example.", "AXFR")
+    res = isctest.query.tcp(msg, "10.53.0.3")
+
+    n = "delegation.occluded.example."
+    delegation = [r for r in res.answer if str(r.name) == n]
+    assert [r for r in delegation if r.rdtype == rdatatype.DNSKEY], str(delegation)
+    assert [r for r in delegation if r.rdtype == rdatatype.AAAA], str(delegation)
+    nsec = [r for r in delegation if r.rdtype == rdatatype.NSEC]
+    assert nsec, str(delegation)
+    assert "DNSKEY" not in str(nsec[0]), str(res)
+    assert "AAAA" not in str(nsec[0]), str(res)
+
+    # check that DNSSEC records are occluded from ANY in an insecure zone
+    msg = isctest.query.create("x.extrakey.example.", "ANY")
+    res = isctest.query.tcp(msg, "10.53.0.3")
+    isctest.check.noerror(res)
+    isctest.check.empty_answer(res)
+    msg = isctest.query.create("z.secure.example.", "ANY")
+    res = isctest.query.tcp(msg, "10.53.0.3")
+    isctest.check.noerror(res)
+    isctest.check.rr_count_eq(res.answer, 4)  # A+RRSIG, NSEC+RRSIG
+
+
+def test_update_signing():
+    # minimal update test: add and delete a single record
+    up = update.UpdateMessage("dynamic.example.")
+    up.add("a.dynamic.example.", 300, "A", "73.80.65.49")
+    res = isctest.query.tcp(up, "10.53.0.3")
+    isctest.check.noerror(res)
+
+    up = update.UpdateMessage("dynamic.example.")
+    up.delete("a.dynamic.example.")
+    res = isctest.query.tcp(up, "10.53.0.3")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("a.dynamic.example", "A")
+    res = isctest.query.tcp(msg, "10.53.0.4")
+    isctest.check.nxdomain(res)
+    isctest.check.adflag(res)
+
+    # check that the NSEC3 record for the apex is properly signed
+    # when a DNSKEY is added via UPDATE
+    key = keygen(
+        "-q3fk", "-a", os.environ["DEFAULT_ALGORITHM"], "update-nsec3.example."
+    )
+
+    with open(f"{key}.key", "r", encoding="utf-8") as f:
+        dnskey = f.read().splitlines()[-1]
+        dnskey = " ".join(dnskey.split()[3:])
+
+    up = update.UpdateMessage("update-nsec3.example.")
+    up.add("update-nsec3.example.", 300, "DNSKEY", dnskey)
+    res = isctest.query.tcp(up, "10.53.0.3")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("update-nsec3.example", "A")
+    res = isctest.query.tcp(msg, "10.53.0.4")
+    isctest.check.noerror(res)
+    isctest.check.adflag(res)
+    nsec3 = [str(a) for a in res.authority if a.rdtype == rdatatype.NSEC3]
+    assert any("1 0 0 -" in a for a in nsec3)
+
+
+def test_cds_signing():
+    # check that CDS records are signed using KSK+ZSK by dnssec-signzone
+    msg = isctest.query.create("cds.secure.", "CDS")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cds, sigs = res.answer
+    assert len(sigs) == 2
+
+    # check that CDS records are not signed using ZSK by dnssec-signzone -x
+    msg = isctest.query.create("cds-x.secure.", "CDS")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cds, sigs = res.answer
+    assert len(sigs) == 2  # there are two KSKs here
+
+    # check that CDS records are signed using KSK by dnssec-policy
+    msg = isctest.query.create("cds-auto.secure.", "CDS")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cds, sigs = res.answer
+    assert len(sigs) == 1
+
+    # check that CDS records are signed only using KSK when added by nsupdate
+    with open("ns2/cds-update.secure.id", encoding="utf-8") as f:
+        keyid = int(f.read().splitlines()[0])
+    up = update.UpdateMessage("cds-update.secure.")
+    up.delete("cds-update.secure.", "CDS")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cds-update.secure.", "DNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    isctest.check.noerror(res)
+    dnskeys, sigs = res.answer
+    ksk = [a for a in dnskeys if a.flags == 257][0]
+    ds = dnssec.make_ds("cds-update.secure.", ksk, 2)
+    up.add("cds-update.secure.", 1, "CDS", str(ds))
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cds-update.secure.", "CDS")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cds, sig = res.answer
+    assert len(cds) == 1
+    assert len(sig) == 1
+    assert sig[0].key_tag == keyid
+
+    # check that CDS deletion records are signed only using KSK when
+    # added by nsupdate
+    up = update.UpdateMessage("cds-update.secure.")
+    up.delete("cds-update.secure.", "CDS")
+    up.add("cds-update.secure.", 0, "CDS", "0 0 0 00")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cds-update.secure.", "CDS")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cds, sig = res.answer
+    assert len(cds) == 1
+    assert "0 0 0 00" in str(cds[0])
+    assert len(sig) == 1
+    assert sig[0].key_tag == keyid
+
+    # check that a non-matching CDS record is accepted with a
+    # matching CDS record. first, generate a DNSKEY with different flags:
+    badksk = type(ksk)(
+        ksk.rdclass, ksk.rdtype, ksk.flags + 1, ksk.protocol, ksk.algorithm, ksk.key
+    )
+    up = update.UpdateMessage("cds-update.secure.")
+    badds = dnssec.make_ds("cds-update.secure.", badksk, 2)
+    up.delete("cds-update.secure.", "CDS")
+    up.add("cds-update.secure.", 1, "CDS", str(ds))
+    up.add("cds-update.secure.", 1, "CDS", str(badds))
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cds-update.secure.", "CDS")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cds, sig = res.answer
+    assert len(cds) == 2
+    assert len(sig) == 1
+
+
+def test_cdnskey_signing():
+    # check that CDNSKEY records are signed using KSK+ZSK by dnssec-signzone
+    msg = isctest.query.create("cdnskey.secure.", "CDNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cdnskey, sigs = res.answer
+    assert len(sigs) == 2
+
+    # check that CDNSKEY records are not signed using ZSK by dnssec-signzone -x
+    msg = isctest.query.create("cdnskey-x.secure.", "CDNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cdnskey, sigs = res.answer
+    assert len(sigs) == 2  # two KSKs here
+
+    # check that CDNSKEY records are signed using KSK by dnssec-policy
+    msg = isctest.query.create("cdnskey-auto.secure.", "CDNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cdnskey, sigs = res.answer
+    assert len(sigs) == 1
+
+    # check that CDNSKEY records are signed only using KSK
+    # when added by nsupdate
+    with open("ns2/cdnskey-update.secure.id", encoding="utf-8") as f:
+        keyid = int(f.read().splitlines()[0])
+    up = update.UpdateMessage("cdnskey-update.secure.")
+    up.delete("cdnskey-update.secure.", "CDNSKEY")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cdnskey-update.secure.", "DNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    isctest.check.noerror(res)
+    dnskeys, sigs = res.answer
+    ksk = [a for a in dnskeys if a.flags == 257][0]
+    up.add("cdnskey-update.secure.", 1, "CDNSKEY", str(ksk))
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cdnskey-update.secure.", "CDNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cdnskey, sig = res.answer
+    assert len(cdnskey) == 1
+    assert len(sig) == 1
+    assert sig[0].key_tag == keyid
+
+    # check that CDNSKEY deletion records are signed only using KSK when
+    # added by nsupdate
+    up = update.UpdateMessage("cdnskey-update.secure.")
+    up.delete("cdnskey-update.secure.", "CDNSKEY")
+    up.add("cdnskey-update.secure.", 0, "CDNSKEY", "0 3 0 AA==")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cdnskey-update.secure.", "CDNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cdnskey, sig = res.answer
+    assert len(cdnskey) == 1
+    assert "0 3 0 AA==" in str(cdnskey[0])
+    assert len(sig) == 1
+    assert sig[0].key_tag == keyid
+
+    # check that a non-matching CDNSKEY record is accepted with a
+    # matching CDNSKEY record. first, generate a DNSKEY with different flags:
+    badksk = type(ksk)(
+        ksk.rdclass, ksk.rdtype, ksk.flags + 1, ksk.protocol, ksk.algorithm, ksk.key
+    )
+    up = update.UpdateMessage("cdnskey-update.secure.")
+    up.delete("cdnskey-update.secure.", "CDNSKEY")
+    up.add("cdnskey-update.secure.", 1, "CDNSKEY", str(ksk))
+    up.add("cdnskey-update.secure.", 1, "CDNSKEY", str(badksk))
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    msg = isctest.query.create("cdnskey-update.secure.", "CDNSKEY")
+    res = isctest.query.tcp(msg, "10.53.0.2")
+    cdnskey, sig = res.answer
+    assert len(cdnskey) == 2
+    assert len(sig) == 1
+
+
+@pytest.mark.parametrize(
+    "cmd",
+    [
+        "signing",  # without arguments
+        "signing -list",  # without zone
+        "signing -clear",  # without zone
+        "signing -clear all",  # without zone
+    ],
+)
+def test_rndc_signing_except(cmd, servers):
+    ns3 = servers["ns3"]
+
+    # check that 'rndc signing' errors are handled
+    with pytest.raises(isctest.rndc.RNDCException):
+        ns3.rndc(cmd, log=False)
+    ns3.rndc("status", log=False)
+
+
+def test_rndc_signing_output(servers):
+    ns3 = servers["ns3"]
+
+    response = ns3.rndc("signing -list dynamic.example", log=False)
+    assert "No signing records found" in response
+
+
+def test_zonestatus_signing(servers):
+    ns3 = servers["ns3"]
+    # check that the correct resigning time is reported in zonestatus.
+    # zonestatus reports a name/type and expecting resigning time;
+    # we convert the time to seconds since epoch, look up the RRSIG
+    # for the name and type, and check that the resigning time is
+    # after the inception and before the expiration.
+
+    response = ns3.rndc("zonestatus secure.example", log=False)
+
+    # next resign node: secure.example/DNSKEY
+    nrn = [r for r in response.splitlines() if "next resign node" in r][0]
+    rdname, rdtype = nrn.split()[3].split("/")
+
+    # next resign time: Thu, 24 Apr 2014 10:38:16 GMT
+    nrt = [r for r in response.splitlines() if "next resign time" in r][0]
+    rtime = " ".join(nrt.split()[3:])
+    rt = time.strptime(rtime, "%a, %d %b %Y %H:%M:%S %Z")
+    when = int(time.strftime("%s", rt))
+
+    msg = isctest.query.create(rdname, rdtype)
+    res = isctest.query.tcp(msg, "10.53.0.3")
+    _, sigs = res.answer
+    assert sigs[0].inception < when
+    assert when < sigs[0].expiration
+
+
+def test_offline_ksk_signing(servers):
+    def getfrom(file):
+        with open(file, encoding="utf-8") as f:
+            return f.read().strip()
+
+    def getkeyid(key: str):
+        m = re.match(r"K.*\+\d*\+(\d*)", key)
+        return int(m.group(1))
+
+    def check_signing_keys(types: list[str], expect: list[str], prohibit: list[str]):
+        for qtype in types:
+            isctest.log.debug(f"checking signing keys for {qtype}")
+            msg = isctest.query.create(zone, qtype)
+            res = isctest.query.tcp(msg, "10.53.0.2")
+            assert res.answer, str(res)
+            rrset = res.get_rrset(
+                res.answer,
+                name.from_text(f"{zone}."),
+                rdataclass.IN,
+                rdatatype.RRSIG,
+                rdatatype.RdataType.make(qtype),
+            )
+            assert rrset, f"expected RRSIG({qtype}) missing from ANSWER" + str(res)
+            keys = {rr.key_tag for rr in rrset}
+            assert len(keys) == 1, str(res)
+            for exp in expect:
+                assert exp in keys
+            for proh in prohibit:
+                assert proh not in keys
+            return True
+
+    def check_zskcount():
+        msg = isctest.query.create(zone, "DNSKEY")
+        res = isctest.query.tcp(msg, "10.53.0.2")
+        dnskeys, _ = res.answer
+        zskcount = len([rr for rr in dnskeys if rr.flags == 256])
+        assert zskcount == 2, str(res)
+        return True
+
+    def ksk_remove():
+        isctest.log.info("remove the KSK from disk")
+        os.rename(f"ns2/{KSK}.key", f"ns2/{KSK}.key.bak")
+        os.rename(f"ns2/{KSK}.private", f"ns2/{KSK}.private.bak")
+
+    def ksk_recover():
+        isctest.log.info("put back the KSK")
+        os.rename(f"ns2/{KSK}.key.bak", f"ns2/{KSK}.key")
+        os.rename(f"ns2/{KSK}.private.bak", f"ns2/{KSK}.private")
+
+    def loadkeys():
+        pattern = re.compile(f"{zone}/IN.*next key event")
+        with ns2.watch_log_from_here() as watcher:
+            ns2.rndc(f"loadkeys {zone}", log=False)
+            watcher.wait_for_line(pattern)
+
+    ksk_only_types = ["DNSKEY", "CDNSKEY", "CDS"]
+
+    ns2 = servers["ns2"]
+    zone = "updatecheck-kskonly.secure"
+    KSK = getfrom(f"ns2/{zone}.ksk.key")
+    ZSK = getfrom(f"ns2/{zone}.zsk.key")
+    KSKID = int(getfrom(f"ns2/{zone}.ksk.id"))
+    ZSKID = int(getfrom(f"ns2/{zone}.zsk.id"))
+
+    # set key state for KSK. the ZSK rollovers below assume that there is a
+    # chain of trust established, so we tell named that the DS is in
+    # omnipresent state.
+    settime("-s", "-d", "OMNIPRESENT", "now", "-Kns2", KSK)
+
+    isctest.log.info("check state before KSK is made offline")
+    isctest.log.info("make sure certain types are signed with KSK only")
+    check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID])
+
+    isctest.log.info("check SOA is signed with ZSK only")
+    check_signing_keys(["SOA"], expect=[ZSKID], prohibit=[KSKID])
+
+    isctest.log.info("roll the ZSK")
+    ZSK2 = keygen(
+        "-qKns2",
+        "-Pnone",
+        "-Anone",
+        "-a",
+        os.environ["DEFAULT_ALGORITHM"],
+        "-b",
+        os.environ["DEFAULT_BITS"],
+        zone,
+    )
+    ZSKID2 = getkeyid(ZSK2)
+
+    isctest.log.info("prepublish new ZSK")
+    ns2.rndc(f"dnssec -rollover -key {ZSKID} {zone}", log=False)
+    isctest.run.retry_with_timeout(check_zskcount, 5)
+
+    isctest.log.info("make the new ZSK active")
+    settime("-sKns2", "-Inow", ZSK)
+    settime("-sKns2", "-Anow", "-k", "OMNIPRESENT", "now", ZSK2)
+    loadkeys()
+
+    with ns2.watch_log_from_start() as watcher:
+        watcher.wait_for_line(
+            [f"{ZSKID2} (ZSK) is now active", f"{ZSKID} (ZSK) is now inactive"]
+        )
+
+    ksk_remove()
+
+    isctest.log.info("update the zone, requiring a resign of the SOA RRset")
+    up = update.UpdateMessage(f"{zone}.")
+    up.add(f"{zone}.", 300, "TXT", "added by UPDATE")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    isctest.log.info(
+        "redo the tests now that the zone is updated and the KSK is offline"
+    )
+    isctest.log.info("make sure certain types are signed with KSK only")
+    check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID, ZSKID2])
+
+    isctest.log.info("check TXT, SOA are signed with ZSK2 only")
+
+    def check_txt_soa_zsk2():
+        return check_signing_keys(
+            ["TXT", "SOA"], expect=[ZSKID2], prohibit=[KSKID, ZSKID]
+        )
+
+    isctest.run.retry_with_timeout(check_txt_soa_zsk2, 5)
+
+    ksk_recover()
+
+    isctest.log.info("roll the ZSK again")
+    ZSK3 = keygen(
+        "-qKns2",
+        "-Pnone",
+        "-Anone",
+        "-a",
+        os.environ["DEFAULT_ALGORITHM"],
+        "-b",
+        os.environ["DEFAULT_BITS"],
+        zone,
+    )
+    ZSKID3 = getkeyid(ZSK3)
+
+    isctest.log.info("delete old ZSK, schedule ZSK2 inactive, pre-publish ZSK3")
+    settime("-sKns2", "-k", "HIDDEN", "now", "-z", "HIDDEN", "now", "-Dnow", ZSK)
+    settime("-sKns2", "-k", "OMNIPRESENT", "now", "-z", "OMNIPRESENT", "now", ZSK2)
+    loadkeys()
+    ns2.rndc(f"dnssec -rollover -key {ZSKID2} {zone}", log=False)
+
+    with ns2.watch_log_from_start() as watcher:
+        watcher.wait_for_line(f"{ZSKID3} (ZSK) is now published")
+
+    ksk_remove()
+
+    isctest.log.info("update the zone again, requiring a resign of the SOA RRset")
+    up = update.UpdateMessage(f"{zone}.")
+    up.add(f"{zone}.", 300, "TXT", "added by UPDATE again")
+    up.add(f"{zone}.", 300, "A", "1.2.3.4")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    isctest.log.info("redo the tests now that the ZSK roll has deleted the old key")
+
+    isctest.log.info("make sure certain types are signed with KSK only")
+    check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID, ZSKID2, ZSKID3])
+
+    isctest.log.info("check A, TXT, SOA are signed with ZSK2 only")
+
+    def check_a_txt_soa_zsk2():
+        return check_signing_keys(
+            ["A", "TXT", "SOA"], expect=[ZSKID2], prohibit=[KSKID, ZSKID, ZSKID3]
+        )
+
+    isctest.run.retry_with_timeout(check_a_txt_soa_zsk2, 5)
+
+    ksk_recover()
+
+    isctest.log.info("make ZSK3 active")
+    settime("-sKns2", "-Inow", ZSK2)
+    settime("-sKns2", "-k", "OMNIPRESENT", "now", "-Anow", ZSK3)
+    loadkeys()
+
+    with ns2.watch_log_from_start() as watcher:
+        watcher.wait_for_line(
+            [f"{ZSKID3} (ZSK) is now active", f"{ZSKID2} (ZSK) is now inactive"]
+        )
+
+    ksk_remove()
+
+    isctest.log.info("update the zone again, requiring a resign of the SOA RRset")
+    up = update.UpdateMessage(f"{zone}.")
+    up.add(f"{zone}.", 300, "TXT", "added by UPDATE one more time")
+    up.add(f"{zone}.", 300, "A", "4.3.2.1")
+    up.add(f"{zone}.", 300, "AAAA", "dead::beef")
+    res = isctest.query.tcp(up, "10.53.0.2")
+    isctest.check.noerror(res)
+
+    isctest.log.info("redo the tests one last time")
+    isctest.log.info("make sure certain types are signed with KSK only")
+    check_signing_keys(ksk_only_types, expect=[KSKID], prohibit=[ZSKID, ZSKID2, ZSKID3])
+
+    isctest.log.info("check A, TXT, SOA are signed with ZSK2 only")
+
+    def check_aaaa_a_txt_soa_zsk3():
+        return check_signing_keys(
+            ["AAAA", "A", "TXT", "SOA"],
+            expect=[ZSKID3],
+            prohibit=[KSKID, ZSKID, ZSKID2],
+        )
+
+    isctest.run.retry_with_timeout(check_aaaa_a_txt_soa_zsk3, 5)
index f0d132b9dca4c8ca1502804ca08b9ed7697bbc38..8c9bc78554ab8ac200d59b30b6d5d90370560d64 100644 (file)
@@ -1084,12 +1084,6 @@ def test_validating_forwarder(servers):
 
 
 def test_expired_signatures(servers):
-    # check expired signatures are still in place when updates are disabled
-    msg = isctest.query.create("expired.example", "SOA")
-    res = isctest.query.tcp(msg, "10.53.0.3")
-    soa, sigs = res.answer
-    assert sigs
-
     # check expired signatures do not validate
     msg = isctest.query.create("expired.example", "SOA")
     res = isctest.query.tcp(msg, "10.53.0.3")