diff --git a/bin/tests/system/kasp.sh b/bin/tests/system/kasp.sh deleted file mode 100644 index be0f6082b7..0000000000 --- a/bin/tests/system/kasp.sh +++ /dev/null @@ -1,1299 +0,0 @@ -#!/bin/sh - -# 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. - -# -# Common configuration data for kasp system tests, to be sourced into -# other shell scripts. -# - -# shellcheck source=conf.sh -. ../conf.sh - -############################################################################### -# Constants # -############################################################################### -DEFAULT_TTL=300 - -############################################################################### -# Query properties # -############################################################################### -TSIG="" -SHA1="FrSt77yPTFx6hTs4i2tKLB9LmE0=" -SHA224="hXfwwwiag2QGqblopofai9NuW28q/1rH4CaTnA==" -SHA256="R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=" -VIEW1="YPfMoAk6h+3iN8MDRQC004iSNHY=" -VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8=" -VIEW3="C1Azf+gGPMmxrUg/WQINP6eV9Y0=" -MINDEPTH=1 -MAXDEPTH=3 - -############################################################################### -# Key properties # -############################################################################### -# ID -# BASEFILE -# EXPECT -# ROLE -# KSK -# ZSK -# FLAGS -# LIFETIME -# ALG_NUM -# ALG_STR -# ALG_LEN -# CREATED -# PUBLISHED -# ACTIVE -# RETIRED -# REVOKED -# REMOVED -# GOAL -# STATE_DNSKEY -# STATE_ZRRSIG -# STATE_KRRSIG -# STATE_DS -# EXPECT_ZRRSIG -# EXPECT_KRRSIG -# LEGACY -# PRIVATE -# PRIVKEY_STAT -# PUBKEY_STAT -# STATE_STAT -# FLAGS -# KEYDIR - -key_key() { - echo "${1}__${2}" -} - -key_get() { - eval "echo \${$(key_key "$1" "$2")}" -} - -key_set() { - eval "$(key_key "$1" "$2")='$3'" -} - -key_stat() { - $PERL -e 'print((stat @ARGV[0])[9] . "\n");' "$1" -} - -# Save certain values in the KEY array. -key_save() { - # Save key id. - key_set "$1" ID "$KEY_ID" - key_set "$1" RID "$KEY_RID" - # Save base filename. - key_set "$1" BASEFILE "$BASE_FILE" - # Save creation date. - key_set "$1" CREATED "${KEY_CREATED}" - # Save key change time. - key_set "$1" PRIVKEY_STAT $(key_stat "${BASE_FILE}.private") - key_set "$1" PUBKEY_STAT $(key_stat "${BASE_FILE}.key") - key_set "$1" STATE_STAT $(key_stat "${BASE_FILE}.state") -} - -# Clear key state. -# -# This will update either the KEY1, KEY2, or KEY3 array. -key_clear() { - key_set "$1" "ID" 'no' - key_set "$1" "RID" 'no' - key_set "$1" "IDPAD" 'no' - key_set "$1" "EXPECT" 'no' - key_set "$1" "ROLE" 'none' - key_set "$1" "KSK" 'no' - key_set "$1" "ZSK" 'no' - key_set "$1" "FLAGS" '0' - key_set "$1" "LIFETIME" 'none' - key_set "$1" "ALG_NUM" '0' - key_set "$1" "ALG_STR" 'none' - key_set "$1" "ALG_LEN" '0' - key_set "$1" "CREATED" '0' - key_set "$1" "PUBLISHED" 'none' - key_set "$1" "SYNCPUBLISH" 'none' - key_set "$1" "ACTIVE" 'none' - key_set "$1" "RETIRED" 'none' - key_set "$1" "REVOKED" 'none' - key_set "$1" "REMOVED" 'none' - key_set "$1" "GOAL" 'none' - key_set "$1" "STATE_DNSKEY" 'none' - key_set "$1" "STATE_KRRSIG" 'none' - key_set "$1" "STATE_ZRRSIG" 'none' - key_set "$1" "STATE_DS" 'none' - key_set "$1" "EXPECT_ZRRSIG" 'no' - key_set "$1" "EXPECT_KRRSIG" 'no' - key_set "$1" "LEGACY" 'no' - key_set "$1" "PRIVATE" 'yes' - key_set "$1" "PRIVKEY_STAT" '0' - key_set "$1" "PUBKEY_STAT" '0' - key_set "$1" "STATE_STAT" '0' - key_set "$1" "KEYDIR" 'none' -} - -# Start clear. -# There can be at most 4 keys at the same time during a rollover: -# 2x KSK, 2x ZSK -key_clear "KEY1" -key_clear "KEY2" -key_clear "KEY3" -key_clear "KEY4" - -############################################################################### -# Utilities # -############################################################################### - -# Call dig with default options. -_dig_with_opts() { - - if [ -n "$TSIG" ]; then - "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" -y "$TSIG" "$@" - else - "$DIG" +tcp +noadd +nosea +nostat +nocmd +dnssec -p "$PORT" "$@" - fi -} - -# RNDC. -_rndccmd() { - "$RNDC" -c ../_common/rndc.conf -p "$CONTROLPORT" -s "$@" -} - -# Print IDs of keys used for generating RRSIG records for RRsets of type $1, -# matching algorithm number $2, found in dig output file $3. -# If $2 is equal to 0, any algorithm matches. -get_keys_which_signed() { - _qtype=$1 - _alg=$2 - _output=$3 - # The key ID is the 11th column of the RRSIG record line. - if [ "$_alg" = "0" ]; then - awk -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt {print $11}' <"$_output" - else - awk -v alg="$_alg" -v qt="$_qtype" '$4 == "RRSIG" && $5 == qt && $6 == alg {print $11}' <"$_output" - fi -} - -# Get the key ids from key files for zone $2 in directory $1. -get_keyids() { - _dir=$1 - _zone=$2 - _regex="K${_zone}.+*+*.key" - - find "${_dir}" -mindepth $MINDEPTH -maxdepth $MAXDEPTH -name "${_regex}" | sed "s,.*/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2," -} - -# By default log errors and don't quit immediately. -_log=1 -_log_error() { - test $_log -eq 1 && echo_i "error: $1" - ret=$((ret + 1)) -} -disable_logerror() { - _log=0 -} -enable_logerror() { - _log=1 -} - -# Set server key-directory ($1) and address ($2) for testing keys. -set_server() { - DIR=$1 - SERVER=$2 -} -# Set zone name for testing keys. -set_zone() { - ZONE=$1 - DYNAMIC="no" -} -# By default zones are considered static. -# When testing dynamic zones, call 'set_dynamic' after 'set_zone'. -set_dynamic() { - DYNAMIC="yes" -} - -# Set policy settings (name $1, number of keys $2, dnskey ttl $3). -set_policy() { - POLICY=$1 - NUM_KEYS=$2 - DNSKEY_TTL=$3 - KEYFILE_TTL=$3 - CDS_DELETE="no" - CDS_SHA256="yes" - CDS_SHA384="no" - CDNSKEY="yes" -} -# By default policies are considered to be secure. -# If a zone sets its policy to "insecure", call 'set_cdsdelete' to tell the -# system test to expect a CDS and CDNSKEY Delete record. -set_cdsdelete() { - CDS_DELETE="yes" -} - -# Set key properties for testing keys. -# $1: Key to update (KEY1, KEY2, ...) -# $2: Value -set_keyrole() { - key_set "$1" "EXPECT" "yes" - key_set "$1" "ROLE" "$2" - key_set "$1" "KSK" "no" - key_set "$1" "ZSK" "no" - key_set "$1" "FLAGS" "0" - - test "$2" = "ksk" && key_set "$1" "KSK" "yes" - test "$2" = "ksk" && key_set "$1" "FLAGS" "257" - - test "$2" = "zsk" && key_set "$1" "ZSK" "yes" - test "$2" = "zsk" && key_set "$1" "FLAGS" "256" - - test "$2" = "csk" && key_set "$1" "KSK" "yes" - test "$2" = "csk" && key_set "$1" "ZSK" "yes" - test "$2" = "csk" && key_set "$1" "FLAGS" "257" - - return 0 -} -set_keylifetime() { - key_set "$1" "EXPECT" "yes" - key_set "$1" "LIFETIME" "$2" -} -# The algorithm value consists of three parts: -# $2: Algorithm (number) -# $3: Algorithm (string-format) -# $4: Algorithm length -set_keyalgorithm() { - key_set "$1" "EXPECT" "yes" - key_set "$1" "ALG_NUM" "$2" - key_set "$1" "ALG_STR" "$3" - key_set "$1" "ALG_LEN" "$4" -} -set_keysigning() { - key_set "$1" "EXPECT" "yes" - key_set "$1" "EXPECT_KRRSIG" "$2" -} -set_zonesigning() { - key_set "$1" "EXPECT" "yes" - key_set "$1" "EXPECT_ZRRSIG" "$2" -} - -# Set key timing metadata. Set to "none" to unset. -# $1: Key to update (KEY1, KEY2, ...) -# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED). -# $3: Value -set_keytime() { - key_set "$1" "EXPECT" "yes" - key_set "$1" "$2" "$3" -} - -# Set key timing metadata to a value plus additional time. -# $1: Key to update (KEY1, KEY2, ...) -# $2: Time to update (PUBLISHED, SYNCPUBLISH, ACTIVE, RETIRED, REVOKED, or REMOVED). -# $3: Value -# $4: Additional time. -set_addkeytime() { - # Convert "%Y%m%d%H%M%S" format to epoch seconds. - # Then, add the additional time (can be negative). - _value=$3 - _plus=$4 - $PYTHON >python.out.$ZONE.$1.$2 <"${ZONE}.${KEY_ID}.${_alg_num}.created" || _log_error "mismatch created comment in $KEY_FILE" - KEY_CREATED=$(awk '{print $3}' <"${ZONE}.${KEY_ID}.${_alg_num}.created") - - if [ "$_private" = "yes" ]; then - grep "Created: ${KEY_CREATED}" "$PRIVATE_FILE" >/dev/null || _log_error "mismatch created in $PRIVATE_FILE" - fi - if [ "$_legacy" = "no" ]; then - grep "Generated: ${KEY_CREATED}" "$STATE_FILE" >/dev/null || _log_error "mismatch generated in $STATE_FILE" - fi - - test $_log -eq 1 && echo_i "check key file $BASE_FILE" - - # Check the public key file. - grep "This is a ${_role2} key, keyid ${_key_id}, for ${_zone}." "$KEY_FILE" >/dev/null || _log_error "mismatch top comment in $KEY_FILE" - grep "${_zone}\. ${_dnskey_ttl} IN DNSKEY ${_flags} 3 ${_alg_num}" "$KEY_FILE" >/dev/null || _log_error "mismatch DNSKEY record in $KEY_FILE" - # Now check the private key file. - if [ "$_private" = "yes" ]; then - grep "Private-key-format: v1.3" "$PRIVATE_FILE" >/dev/null || _log_error "mismatch private key format in $PRIVATE_FILE" - grep "Algorithm: ${_alg_num} (${_alg_string})" "$PRIVATE_FILE" >/dev/null || _log_error "mismatch algorithm in $PRIVATE_FILE" - fi - # Now check the key state file. - if [ "$_legacy" = "no" ]; then - grep "This is the state of key ${_key_id}, for ${_zone}." "$STATE_FILE" >/dev/null || _log_error "mismatch top comment in $STATE_FILE" - if [ "$_lifetime" = "none" ]; then - grep "Lifetime: " "$STATE_FILE" >/dev/null && _log_error "unexpected lifetime in $STATE_FILE" - else - grep "Lifetime: ${_lifetime}" "$STATE_FILE" >/dev/null || _log_error "mismatch lifetime in $STATE_FILE" - fi - grep "Algorithm: ${_alg_num}" "$STATE_FILE" >/dev/null || _log_error "mismatch algorithm in $STATE_FILE" - grep "Length: ${_length}" "$STATE_FILE" >/dev/null || _log_error "mismatch length in $STATE_FILE" - grep "KSK: ${_ksk}" "$STATE_FILE" >/dev/null || _log_error "mismatch ksk in $STATE_FILE" - grep "ZSK: ${_zsk}" "$STATE_FILE" >/dev/null || _log_error "mismatch zsk in $STATE_FILE" - - # Check key states. - if [ "$_goal" = "none" ]; then - grep "GoalState: " "$STATE_FILE" >/dev/null && _log_error "unexpected goal state in $STATE_FILE" - else - grep "GoalState: ${_goal}" "$STATE_FILE" >/dev/null || _log_error "mismatch goal state in $STATE_FILE" - fi - - if [ "$_state_dnskey" = "none" ]; then - grep "DNSKEYState: " "$STATE_FILE" >/dev/null && _log_error "unexpected dnskey state in $STATE_FILE" - grep "DNSKEYChange: " "$STATE_FILE" >/dev/null && _log_error "unexpected dnskey change in $STATE_FILE" - else - grep "DNSKEYState: ${_state_dnskey}" "$STATE_FILE" >/dev/null || _log_error "mismatch dnskey state in $STATE_FILE" - grep "DNSKEYChange: " "$STATE_FILE" >/dev/null || _log_error "mismatch dnskey change in $STATE_FILE" - fi - - if [ "$_state_zrrsig" = "none" ]; then - grep "ZRRSIGState: " "$STATE_FILE" >/dev/null && _log_error "unexpected zrrsig state in $STATE_FILE" - grep "ZRRSIGChange: " "$STATE_FILE" >/dev/null && _log_error "unexpected zrrsig change in $STATE_FILE" - else - grep "ZRRSIGState: ${_state_zrrsig}" "$STATE_FILE" >/dev/null || _log_error "mismatch zrrsig state in $STATE_FILE" - grep "ZRRSIGChange: " "$STATE_FILE" >/dev/null || _log_error "mismatch zrrsig change in $STATE_FILE" - fi - - if [ "$_state_krrsig" = "none" ]; then - grep "KRRSIGState: " "$STATE_FILE" >/dev/null && _log_error "unexpected krrsig state in $STATE_FILE" - grep "KRRSIGChange: " "$STATE_FILE" >/dev/null && _log_error "unexpected krrsig change in $STATE_FILE" - else - grep "KRRSIGState: ${_state_krrsig}" "$STATE_FILE" >/dev/null || _log_error "mismatch krrsig state in $STATE_FILE" - grep "KRRSIGChange: " "$STATE_FILE" >/dev/null || _log_error "mismatch krrsig change in $STATE_FILE" - fi - - if [ "$_state_ds" = "none" ]; then - grep "DSState: " "$STATE_FILE" >/dev/null && _log_error "unexpected ds state in $STATE_FILE" - grep "DSChange: " "$STATE_FILE" >/dev/null && _log_error "unexpected ds change in $STATE_FILE" - else - grep "DSState: ${_state_ds}" "$STATE_FILE" >/dev/null || _log_error "mismatch ds state in $STATE_FILE" - grep "DSChange: " "$STATE_FILE" >/dev/null || _log_error "mismatch ds change in $STATE_FILE" - fi - fi - - return 0 -} - -# Check the key timing metadata for key $1. -check_timingmetadata() { - _dir=$(key_get "$1" KEYDIR) - if [ "$_dir" = "none" ]; then - _dir="$DIR" - fi - _zone="$ZONE" - _key_idpad=$(key_get "$1" ID) - _key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//') - _alg_num=$(key_get "$1" ALG_NUM) - _alg_numpad=$(printf "%03d" "$_alg_num") - - _published=$(key_get "$1" PUBLISHED) - _active=$(key_get "$1" ACTIVE) - _retired=$(key_get "$1" RETIRED) - _revoked=$(key_get "$1" REVOKED) - _removed=$(key_get "$1" REMOVED) - - _goal=$(key_get "$1" GOAL) - _state_dnskey=$(key_get "$1" STATE_DNSKEY) - _state_zrrsig=$(key_get "$1" STATE_ZRRSIG) - _state_krrsig=$(key_get "$1" STATE_KRRSIG) - _state_ds=$(key_get "$1" STATE_DS) - - _base_file=$(key_get "$1" BASEFILE) - _key_file="${_base_file}.key" - _private_file="${_base_file}.private" - _state_file="${_base_file}.state" - _legacy=$(key_get "$1" LEGACY) - _private=$(key_get "$1" PRIVATE) - - _published=$(key_get "$1" PUBLISHED) - _syncpublish=$(key_get "$1" SYNCPUBLISH) - _active=$(key_get "$1" ACTIVE) - _retired=$(key_get "$1" RETIRED) - _revoked=$(key_get "$1" REVOKED) - _removed=$(key_get "$1" REMOVED) - - # Check timing metadata. - n=$((n + 1)) - echo_i "check key timing metadata for key $1 id ${_key_id} zone ${ZONE} ($n)" - ret=0 - - if [ "$_published" = "none" ]; then - grep "; Publish:" "${_key_file}" >/dev/null && _log_error "unexpected publish comment in ${_key_file}" - if [ "$_private" = "yes" ]; then - grep "Publish:" "${_private_file}" >/dev/null && _log_error "unexpected publish in ${_private_file}" - fi - if [ "$_legacy" = "no" ]; then - grep "Published: " "${_state_file}" >/dev/null && _log_error "unexpected publish in ${_state_file}" - fi - else - grep "; Publish: $_published" "${_key_file}" >/dev/null || _log_error "mismatch publish comment in ${_key_file} (expected ${_published})" - if [ "$_private" = "yes" ]; then - grep "Publish: $_published" "${_private_file}" >/dev/null || _log_error "mismatch publish in ${_private_file} (expected ${_published})" - fi - if [ "$_legacy" = "no" ]; then - grep "Published: $_published" "${_state_file}" >/dev/null || _log_error "mismatch publish in ${_state_file} (expected ${_published})" - fi - fi - - if [ "$_syncpublish" = "none" ]; then - grep "; SyncPublish:" "${_key_file}" >/dev/null && _log_error "unexpected syncpublish comment in ${_key_file}" - if [ "$_private" = "yes" ]; then - grep "SyncPublish:" "${_private_file}" >/dev/null && _log_error "unexpected syncpublish in ${_private_file}" - fi - if [ "$_legacy" = "no" ]; then - grep "PublishCDS: " "${_state_file}" >/dev/null && _log_error "unexpected syncpublish in ${_state_file}" - fi - else - grep "; SyncPublish: $_syncpublish" "${_key_file}" >/dev/null || _log_error "mismatch syncpublish comment in ${_key_file} (expected ${_syncpublish})" - if [ "$_private" = "yes" ]; then - grep "SyncPublish: $_syncpublish" "${_private_file}" >/dev/null || _log_error "mismatch syncpublish in ${_private_file} (expected ${_syncpublish})" - fi - if [ "$_legacy" = "no" ]; then - grep "PublishCDS: $_syncpublish" "${_state_file}" >/dev/null || _log_error "mismatch syncpublish in ${_state_file} (expected ${_syncpublish})" - fi - fi - - if [ "$_active" = "none" ]; then - grep "; Activate:" "${_key_file}" >/dev/null && _log_error "unexpected active comment in ${_key_file}" - if [ "$_private" = "yes" ]; then - grep "Activate:" "${_private_file}" >/dev/null && _log_error "unexpected active in ${_private_file}" - fi - if [ "$_legacy" = "no" ]; then - grep "Active: " "${_state_file}" >/dev/null && _log_error "unexpected active in ${_state_file}" - fi - else - grep "; Activate: $_active" "${_key_file}" >/dev/null || _log_error "mismatch active comment in ${_key_file} (expected ${_active})" - if [ "$_private" = "yes" ]; then - grep "Activate: $_active" "${_private_file}" >/dev/null || _log_error "mismatch active in ${_private_file} (expected ${_active})" - fi - if [ "$_legacy" = "no" ]; then - grep "Active: $_active" "${_state_file}" >/dev/null || _log_error "mismatch active in ${_state_file} (expected ${_active})" - fi - fi - - if [ "$_retired" = "none" ]; then - grep "; Inactive:" "${_key_file}" >/dev/null && _log_error "unexpected retired comment in ${_key_file}" - if [ "$_private" = "yes" ]; then - grep "Inactive:" "${_private_file}" >/dev/null && _log_error "unexpected retired in ${_private_file}" - fi - if [ "$_legacy" = "no" ]; then - grep "Retired: " "${_state_file}" >/dev/null && _log_error "unexpected retired in ${_state_file}" - fi - else - grep "; Inactive: $_retired" "${_key_file}" >/dev/null || _log_error "mismatch retired comment in ${_key_file} (expected ${_retired})" - if [ "$_private" = "yes" ]; then - grep "Inactive: $_retired" "${_private_file}" >/dev/null || _log_error "mismatch retired in ${_private_file} (expected ${_retired})" - fi - if [ "$_legacy" = "no" ]; then - grep "Retired: $_retired" "${_state_file}" >/dev/null || _log_error "mismatch retired in ${_state_file} (expected ${_retired})" - fi - fi - - if [ "$_revoked" = "none" ]; then - grep "; Revoke:" "${_key_file}" >/dev/null && _log_error "unexpected revoked comment in ${_key_file}" - if [ "$_private" = "yes" ]; then - grep "Revoke:" "${_private_file}" >/dev/null && _log_error "unexpected revoked in ${_private_file}" - fi - if [ "$_legacy" = "no" ]; then - grep "Revoked: " "${_state_file}" >/dev/null && _log_error "unexpected revoked in ${_state_file}" - fi - else - grep "; Revoke: $_revoked" "${_key_file}" >/dev/null || _log_error "mismatch revoked comment in ${_key_file} (expected ${_revoked})" - if [ "$_private" = "yes" ]; then - grep "Revoke: $_revoked" "${_private_file}" >/dev/null || _log_error "mismatch revoked in ${_private_file} (expected ${_revoked})" - fi - if [ "$_legacy" = "no" ]; then - grep "Revoked: $_revoked" "${_state_file}" >/dev/null || _log_error "mismatch revoked in ${_state_file} (expected ${_revoked})" - fi - fi - - if [ "$_removed" = "none" ]; then - grep "; Delete:" "${_key_file}" >/dev/null && _log_error "unexpected removed comment in ${_key_file}" - if [ "$_private" = "yes" ]; then - grep "Delete:" "${_private_file}" >/dev/null && _log_error "unexpected removed in ${_private_file}" - fi - if [ "$_legacy" = "no" ]; then - grep "Removed: " "${_state_file}" >/dev/null && _log_error "unexpected removed in ${_state_file}" - fi - else - grep "; Delete: $_removed" "${_key_file}" >/dev/null || _log_error "mismatch removed comment in ${_key_file} (expected ${_removed})" - if [ "$_private" = "yes" ]; then - grep "Delete: $_removed" "${_private_file}" >/dev/null || _log_error "mismatch removed in ${_private_file} (expected ${_removed})" - fi - if [ "$_legacy" = "no" ]; then - grep "Removed: $_removed" "${_state_file}" >/dev/null || _log_error "mismatch removed in ${_state_file} (expected ${_removed})" - fi - fi - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -check_keytimes() { - # The script relies on Python to set keytimes. - if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - check_timingmetadata "KEY1" - fi - if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - check_timingmetadata "KEY2" - fi - if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - check_timingmetadata "KEY3" - fi - if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - check_timingmetadata "KEY4" - fi -} - -# Check the key with key id $1 and see if it is unused. -# This requires environment variables to be set. -# -# This will set the following environment variables for testing: -# BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" -# KEY_FILE="${BASE_FILE}.key" -# PRIVATE_FILE="${BASE_FILE}.private" -# STATE_FILE="${BASE_FILE}.state" -# KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//') -key_unused() { - _dir="$DIR" - _zone="$ZONE" - _key_idpad="$1" - _key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//') - _alg_num="$2" - _alg_numpad=$(printf "%03d" "$_alg_num") - - BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" - KEY_FILE="${BASE_FILE}.key" - PRIVATE_FILE="${BASE_FILE}.private" - STATE_FILE="${BASE_FILE}.state" - KEY_ID="${_key_id}" - - test $_log -eq 1 && echo_i "key unused $KEY_ID?" - - # Check file existence. - [ -s "$KEY_FILE" ] || ret=1 - [ -s "$PRIVATE_FILE" ] || ret=1 - [ -s "$STATE_FILE" ] || ret=1 - [ "$ret" -eq 0 ] || return 0 - - # Treat keys that have been removed from the zone as unused. - _check_removed=1 - grep "; Created:" "$KEY_FILE" >created.key-${KEY_ID}.test${n} || _check_removed=0 - grep "; Delete:" "$KEY_FILE" >unused.key-${KEY_ID}.test${n} || _check_removed=0 - if [ "$_check_removed" -eq 1 ]; then - _created=$(awk '{print $3}' /dev/null && _log_error "unexpected publish comment in $KEY_FILE" - grep "; Activate:" "$KEY_FILE" >/dev/null && _log_error "unexpected active comment in $KEY_FILE" - grep "; Inactive:" "$KEY_FILE" >/dev/null && _log_error "unexpected retired comment in $KEY_FILE" - grep "; Revoke:" "$KEY_FILE" >/dev/null && _log_error "unexpected revoked comment in $KEY_FILE" - grep "; Delete:" "$KEY_FILE" >/dev/null && _log_error "unexpected removed comment in $KEY_FILE" - - grep "Publish:" "$PRIVATE_FILE" >/dev/null && _log_error "unexpected publish in $PRIVATE_FILE" - grep "Activate:" "$PRIVATE_FILE" >/dev/null && _log_error "unexpected active in $PRIVATE_FILE" - grep "Inactive:" "$PRIVATE_FILE" >/dev/null && _log_error "unexpected retired in $PRIVATE_FILE" - grep "Revoke:" "$PRIVATE_FILE" >/dev/null && _log_error "unexpected revoked in $PRIVATE_FILE" - grep "Delete:" "$PRIVATE_FILE" >/dev/null && _log_error "unexpected removed in $PRIVATE_FILE" - - grep "Published: " "$STATE_FILE" >/dev/null && _log_error "unexpected publish in $STATE_FILE" - grep "Active: " "$STATE_FILE" >/dev/null && _log_error "unexpected active in $STATE_FILE" - grep "Retired: " "$STATE_FILE" >/dev/null && _log_error "unexpected retired in $STATE_FILE" - grep "Revoked: " "$STATE_FILE" >/dev/null && _log_error "unexpected revoked in $STATE_FILE" - grep "Removed: " "$STATE_FILE" >/dev/null && _log_error "unexpected removed in $STATE_FILE" - - return 0 -} - -# Test: dnssec-verify zone $1. -dnssec_verify() { - n=$((n + 1)) - echo_i "dnssec-verify zone ${ZONE} ($n)" - ret=0 - _dig_with_opts "$ZONE" "@${SERVER}" AXFR >dig.out.axfr.test$n || _log_error "dig ${ZONE} AXFR failed" - $VERIFY -z -o "$ZONE" dig.out.axfr.test$n >verify.out.$ZONE.test$n || _log_error "dnssec verify zone $ZONE failed" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Wait for the zone to be signed. -# The apex NSEC record indicates that it is signed. -_wait_for_nsec() { - _dig_with_opts "@${SERVER}" "$ZONE" NSEC >"dig.out.nsec.test$n" || return 1 - grep "NS SOA" "dig.out.nsec.test$n" >/dev/null || return 1 - grep "${ZONE}\..*IN.*RRSIG" "dig.out.nsec.test$n" >/dev/null || return 1 - return 0 -} -wait_for_nsec() { - n=$((n + 1)) - ret=0 - echo_i "wait for ${ZONE} to be signed ($n)" - retry_quiet 10 _wait_for_nsec || _log_error "wait for ${ZONE} to be signed failed" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -check_numkeys() { - _numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l) - test "$_numkeys" -eq "$NUM_KEYS" || return 1 - return 0 -} - -_check_keys() { - ret=0 - _ret=0 - - # Clear key ids. - if [ "$1" != "keep" ]; then - key_set KEY1 ID "no" - key_set KEY2 ID "no" - key_set KEY3 ID "no" - key_set KEY4 ID "no" - fi - - # Check key files. - _ids=$(get_keyids "$DIR" "$ZONE") - for _id in $_ids; do - # There are multiple key files with the same algorithm. - # Check them until a match is found. - ret=0 - echo_i "check key id $_id" - - if [ "no" = "$(key_get KEY1 ID)" ] && [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - ret=0 - check_key "KEY1" "$_id" - test "$ret" -eq 0 && key_save KEY1 && continue - fi - if [ "no" = "$(key_get KEY2 ID)" ] && [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - ret=0 - check_key "KEY2" "$_id" - test "$ret" -eq 0 && key_save KEY2 && continue - fi - if [ "no" = "$(key_get KEY3 ID)" ] && [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - ret=0 - check_key "KEY3" "$_id" - test "$ret" -eq 0 && key_save KEY3 && continue - fi - if [ "no" = "$(key_get KEY4 ID)" ] && [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - ret=0 - check_key "KEY4" "$_id" - test "$ret" -eq 0 && key_save KEY4 && continue - fi - - # This may be an unused key. Assume algorithm of KEY1. - ret=0 && key_unused "$_id" "$(key_get KEY1 ALG_NUM)" - test "$ret" -eq 0 && continue - - # If ret is still non-zero, none of the files matched. - echo_i "failed" - _ret=1 - done - - return $_ret -} - -# Check keys for a configured zone. This verifies: -# 1. The right number of keys exist in the key pool ($1). -# 2. The right number of keys is active. Checks KEY1, KEY2, KEY3, and KEY4. -# -# It is expected that KEY1, KEY2, KEY3, and KEY4 arrays are set correctly. -# Found key identifiers are stored in the right key array. -# Keys are found if they are stored inside $DIR or in a subdirectory up to -# three levels deeper. -# -# If $1 is set, we keep keys that are already found and don't look for them -# again. -check_keys() { - n=$((n + 1)) - echo_i "check keys are created for zone ${ZONE} ($n)" - ret=0 - - echo_i "check number of keys for zone ${ZONE} in dir ${DIR} ($n)" - retry_quiet 10 check_numkeys || ret=1 - if [ $ret -ne 0 ]; then - _numkeys=$(get_keyids "$DIR" "$ZONE" | wc -l) - _log_error "bad number of key files ($_numkeys) for zone $ZONE (expected $NUM_KEYS)" - status=$((status + ret)) - fi - - # Temporarily don't log errors because we are searching multiple files. - disable_logerror - - retry_quiet 3 _check_keys $1 || ret=1 - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - # Turn error logs on again. - enable_logerror - - ret=0 - if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - echo_i "KEY1 ID $(key_get KEY1 ID) ALG $(key_get KEY1 ALG_STR)" - test "no" = "$(key_get KEY1 ID)" && _log_error "No KEY1 found for zone ${ZONE}" - fi - if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - echo_i "KEY2 ID $(key_get KEY2 ID) ALG $(key_get KEY2 ALG_STR)" - test "no" = "$(key_get KEY2 ID)" && _log_error "No KEY2 found for zone ${ZONE}" - fi - if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - echo_i "KEY3 ID $(key_get KEY3 ID) ALG $(key_get KEY3 ALG_STR)" - test "no" = "$(key_get KEY3 ID)" && _log_error "No KEY3 found for zone ${ZONE}" - fi - if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - echo_i "KEY4 ID $(key_get KEY4 ID) ALG $(key_get KEY4 ALG_STR)" - test "no" = "$(key_get KEY4 ID)" && _log_error "No KEY4 found for zone ${ZONE}" - fi - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Call rndc dnssec -status on server $1 for zone $3 in view $4 with policy $2 -# and check output. This is a loose verification, it just tests if the right -# policy name is returned, and if all expected keys are listed. The rndc -# dnssec -status output also lists whether a key is published, -# used for signing, is retired, or is removed, and if not when -# it is scheduled to do so, and it shows the states for the various -# DNSSEC records. -check_dnssecstatus() { - _server=$1 - _policy=$2 - _zone=$3 - _view=$4 - - n=$((n + 1)) - echo_i "check rndc dnssec -status output for ${_zone} (policy: $_policy) ($n)" - ret=0 - - _rndccmd $_server dnssec -status $_zone in $_view >rndc.dnssec.status.out.$_zone.$n || _log_error "rndc dnssec -status zone ${_zone} failed" - - if [ "$_policy" = "none" ]; then - grep "Zone does not have dnssec-policy" rndc.dnssec.status.out.$_zone.$n >/dev/null || log_error "bad dnssec status for unsigned zone ${_zone}" - else - grep "dnssec-policy: ${_policy}" rndc.dnssec.status.out.$_zone.$n >/dev/null || _log_error "bad dnssec status for signed zone ${_zone}" - if [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - grep "key: $(key_get KEY1 ID)" rndc.dnssec.status.out.$_zone.$n >/dev/null || _log_error "missing key $(key_get KEY1 ID) from dnssec status" - fi - if [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - grep "key: $(key_get KEY2 ID)" rndc.dnssec.status.out.$_zone.$n >/dev/null || _log_error "missing key $(key_get KEY2 ID) from dnssec status" - fi - if [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - grep "key: $(key_get KEY3 ID)" rndc.dnssec.status.out.$_zone.$n >/dev/null || _log_error "missing key $(key_get KEY3 ID) from dnssec status" - fi - if [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - grep "key: $(key_get KEY4 ID)" rndc.dnssec.status.out.$_zone.$n >/dev/null || _log_error "missing key $(key_get KEY4 ID) from dnssec status" - fi - fi - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Call rndc zonestatus on server $1 for zone $2 in view $3 and check output if -# inline-signing is enabled. -check_inlinesigning() { - _server=$1 - _zone=$2 - _view=$3 - - _rndccmd $_server zonestatus $_zone in $_view >rndc.zonestatus.out.$_zone.$n || return 1 - grep "inline signing: yes" rndc.zonestatus.out.$_zone.$n >/dev/null || return 1 -} - -# Call rndc zonestatus on server $1 for zone $2 in view $3 and check output if -# the zone is dynamic. -check_isdynamic() { - _server=$1 - _zone=$2 - _view=$3 - - _rndccmd $_server zonestatus $_zone in $_view >rndc.zonestatus.out.$_zone.$n || return 1 - grep "dynamic: yes" rndc.zonestatus.out.$_zone.$n >/dev/null || return 1 -} - -# Check if RRset of type $1 in file $2 is signed with the right keys. -# The right keys are the ones that expect a signature and matches the role $3. -_check_signatures() { - _qtype=$1 - _file=$2 - _role=$3 - - numsigs=0 - - if [ "$_role" = "KSK" ]; then - _expect_type=EXPECT_KRRSIG - elif [ "$_role" = "ZSK" ]; then - _expect_type=EXPECT_ZRRSIG - fi - - if [ "$(key_get KEY1 "$_expect_type")" = "yes" ] && [ "$(key_get KEY1 "$_role")" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY1 ALG_NUM)" "$_file" | grep "^$(key_get KEY1 ID)$" >/dev/null || return 1 - numsigs=$((numsigs + 1)) - elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY1 ALG_NUM)" "$_file" | grep "^$(key_get KEY1 ID)$" >/dev/null && return 1 - fi - - if [ "$(key_get KEY2 "$_expect_type")" = "yes" ] && [ "$(key_get KEY2 "$_role")" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY2 ALG_NUM)" "$_file" | grep "^$(key_get KEY2 ID)$" >/dev/null || return 1 - numsigs=$((numsigs + 1)) - elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY2 ALG_NUM)" "$_file" | grep "^$(key_get KEY2 ID)$" >/dev/null && return 1 - fi - - if [ "$(key_get KEY3 "$_expect_type")" = "yes" ] && [ "$(key_get KEY3 "$_role")" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY3 ALG_NUM)" "$_file" | grep "^$(key_get KEY3 ID)$" >/dev/null || return 1 - numsigs=$((numsigs + 1)) - elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY3 ALG_NUM)" "$_file" | grep "^$(key_get KEY3 ID)$" >/dev/null && return 1 - fi - - if [ "$(key_get KEY4 "$_expect_type")" = "yes" ] && [ "$(key_get KEY4 "$_role")" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY4 ALG_NUM)" "$_file" | grep "^$(key_get KEY4 ID)$" >/dev/null || return 1 - numsigs=$((numsigs + 1)) - elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - get_keys_which_signed "$_qtype" "$(key_get KEY4 ALG_NUM)" "$_file" | grep "^$(key_get KEY4 ID)$" >/dev/null && return 1 - fi - - lines=$(get_keys_which_signed "${_qtype}" "0" "${_file}" | wc -l) - test "$lines" -eq "$numsigs" || echo_i "bad number of signatures for $_qtype (got $lines, expected $numsigs)" - test "$lines" -eq "$numsigs" || return 1 - - return 0 -} -check_signatures() { - retry_quiet 3 _check_signatures $1 $2 $3 || _log_error "RRset $1 in zone $ZONE incorrectly signed" -} - -response_has_cds_for_key() { - awk -v zone="${ZONE%%.}." \ - -v ttl="${DNSKEY_TTL}" \ - -v qtype="CDS" \ - -v keyid="$(key_get "${2}" ID)" \ - -v keyalg="$(key_get "${2}" ALG_NUM)" \ - -v hashalg="$1" \ - 'BEGIN { ret=1; } - $1 == zone && $2 == ttl && $4 == qtype && $5 == keyid && $6 == keyalg && $7 == hashalg { ret=0; exit; } - END { exit ret; }' \ - "$3" -} - -response_has_cdnskey_for_key() ( - - awk -v zone="${ZONE%%.}." \ - -v ttl="${DNSKEY_TTL}" \ - -v qtype="CDNSKEY" \ - -v flags="$(key_get "${1}" FLAGS)" \ - -v keyalg="$(key_get "${1}" ALG_NUM)" \ - 'BEGIN { ret=1; } - $1 == zone && $2 == ttl && $4 == qtype && $5 == flags && $7 == keyalg { ret=0; exit; } - END { exit ret; }' \ - "$2" -) - -check_cds_digests() { - if [ "$CDS_SHA256" = "yes" ]; then - response_has_cds_for_key 2 $1 "${2}.cds" || _log_error "missing CDS 2 record in response for key $(key_get $1 ID)" - else - response_has_cds_for_key 2 $1 "${2}.cds" && _log_error "unexpected CDS 2 record in response for key $(key_get $1 ID)" - fi - - if [ "$CDS_SHA384" = "yes" ]; then - response_has_cds_for_key 4 $1 "${2}.cds" || _log_error "missing CDS 4 record in response for key $(key_get $1 ID)" - else - response_has_cds_for_key 4 $1 "${2}.cds" && _log_error "unexpected CDS 4 record in response for key $(key_get $1 ID)" - fi - - if [ "$CDNSKEY" = "yes" ]; then - response_has_cdnskey_for_key $1 "${2}.cdnskey" || _log_error "missing CDNSKEY record in response for key $(key_get $1 ID)" - else - response_has_cdnskey_for_key $1 "${2}.cdnskey" && _log_error "unexpected CDNSKEY record in response for key $(key_get $1 ID)" - fi - - return 0 -} - -check_cds_digests_invert() { - response_has_cds_for_key 2 $1 "${2}.cds" && _log_error "unexpected CDS 2 record in response for key $(key_get $1 ID)" - response_has_cds_for_key 4 $1 "${2}.cds" && _log_error "unexpected CDS 4 record in response for key $(key_get $1 ID)" - # The key should not have an associated CDNSKEY, but there may be - # one for another key. Since the CDNSKEY has no field for key - # id, it is hard to check what key the CDNSKEY may belong to - # so let's skip this check for now. - - return 0 -} - -# Test CDS and CDNSKEY publication. -check_cds() { - - n=$((n + 1)) - echo_i "check CDS and CDNSKEY rrset are signed correctly for zone ${ZONE} ($n)" - ret=0 - - _checksig=0 - - _dig_with_opts "$ZONE" "@${SERVER}" "CDS" >"dig.out.$DIR.test$n.cds" || _log_error "dig ${ZONE} CDS failed" - grep "status: NOERROR" "dig.out.$DIR.test$n.cds" >/dev/null || _log_error "mismatch status in DNS response" - - _dig_with_opts "$ZONE" "@${SERVER}" "CDNSKEY" >"dig.out.$DIR.test$n.cdnskey" || _log_error "dig ${ZONE} CDNSKEY failed" - grep "status: NOERROR" "dig.out.$DIR.test$n.cdnskey" >/dev/null || _log_error "mismatch status in DNS response" - - if [ "$CDS_DELETE" = "no" ]; then - grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" >/dev/null && _log_error "unexpected CDS DELETE record in DNS response" - grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" >/dev/null && _log_error "unexpected CDNSKEY DELETE record in DNS response" - else - grep "CDS.*0 0 0 00" "dig.out.$DIR.test$n.cds" >/dev/null || _log_error "missing CDS DELETE record in DNS response" - grep "CDNSKEY.*0 3 0 AA==" "dig.out.$DIR.test$n.cdnskey" >/dev/null || _log_error "missing CDNSKEY DELETE record in DNS response" - _checksig=1 - fi - - if [ "$(key_get KEY1 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DS)" = "omnipresent" ]; then - check_cds_digests KEY1 "dig.out.$DIR.test$n" - _checksig=1 - elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - check_cds_digests_invert KEY1 "dig.out.$DIR.test$n" - fi - - if [ "$(key_get KEY2 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DS)" = "omnipresent" ]; then - check_cds_digests KEY2 "dig.out.$DIR.test$n" - _checksig=1 - elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - check_cds_digests_invert KEY2 "dig.out.$DIR.test$n" - fi - - if [ "$(key_get KEY3 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DS)" = "omnipresent" ]; then - check_cds_digests KEY3 "dig.out.$DIR.test$n" - _checksig=1 - elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - check_cds_digests_invert KEY3 "dig.out.$DIR.test$n" - fi - - if [ "$(key_get KEY4 STATE_DS)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DS)" = "omnipresent" ]; then - check_cds_digests KEY4 "dig.out.$DIR.test$n" - _checksig=1 - elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - check_cds_digests_invert KEY4 "dig.out.$DIR.test$n" - fi - - test "$_checksig" -eq 0 || check_signatures "CDS" "dig.out.$DIR.test$n.cds" "KSK" - - if [ "$CDNSKEY" = "yes" ]; then - test "$_checksig" -eq 0 || check_signatures "CDNSKEY" "dig.out.$DIR.test$n.cdnskey" "KSK" - fi - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -_find_dnskey() { - _owner="${ZONE}." - _alg="$(key_get $1 ALG_NUM)" - _flags="$(key_get $1 FLAGS)" - _key_file="$(key_get $1 BASEFILE).key" - - awk '$1 == "'"$_owner"'" && $2 == "'"$KEYFILE_TTL"'" && $3 == "IN" && $4 == "DNSKEY" && $5 == "'"$_flags"'" && $6 == "3" && $7 == "'"$_alg"'" { print $8 }' <"$_key_file" -} - -# Test DNSKEY query. -_check_apex_dnskey() { - _dig_with_opts "$ZONE" "@${SERVER}" "DNSKEY" >"dig.out.$DIR.test$n" || return 1 - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || return 1 - - _checksig=0 - - if [ "$(key_get KEY1 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY1 STATE_DNSKEY)" = "omnipresent" ]; then - _pubkey=$(_find_dnskey KEY1) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null || return 1 - _checksig=1 - elif [ "$(key_get KEY1 EXPECT)" = "yes" ]; then - _pubkey=$(_find_dnskey KEY1) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null && return 1 - fi - - if [ "$(key_get KEY2 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY2 STATE_DNSKEY)" = "omnipresent" ]; then - _pubkey=$(_find_dnskey KEY2) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null || return 1 - _checksig=1 - elif [ "$(key_get KEY2 EXPECT)" = "yes" ]; then - _pubkey=$(_find_dnskey KEY2) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null && return 1 - fi - - if [ "$(key_get KEY3 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY3 STATE_DNSKEY)" = "omnipresent" ]; then - _pubkey=$(_find_dnskey KEY3) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null || return 1 - _checksig=1 - elif [ "$(key_get KEY3 EXPECT)" = "yes" ]; then - _pubkey=$(_find_dnskey KEY3) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null && return 1 - fi - - if [ "$(key_get KEY4 STATE_DNSKEY)" = "rumoured" ] || [ "$(key_get KEY4 STATE_DNSKEY)" = "omnipresent" ]; then - _pubkey=$(_find_dnskey KEY4) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null || return 1 - _checksig=1 - elif [ "$(key_get KEY4 EXPECT)" = "yes" ]; then - _pubkey=$(_find_dnskey KEY4) - test -z "$_pubkey" && return 1 - grep -F "$_pubkey" "dig.out.$DIR.test$n" >/dev/null && return 1 - fi - - test "$_checksig" -eq 0 && return 0 - - _check_signatures "DNSKEY" "dig.out.$DIR.test$n" "KSK" || return 1 - - return 0 -} - -# Test the apex of a configured zone. This checks that the SOA and DNSKEY -# RRsets are signed correctly and with the appropriate keys. -check_apex() { - - # Test DNSKEY query. - n=$((n + 1)) - echo_i "check DNSKEY rrset is signed correctly for zone ${ZONE} ($n)" - ret=0 - retry_quiet 10 _check_apex_dnskey || ret=1 - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - # We retry the DNSKEY query for at most ten seconds to avoid test - # failures due to timing issues. If the DNSKEY query check passes this - # means the zone is resigned and further apex checks (SOA, CDS, CDNSKEY) - # don't need to be retried quietly. - - # Test SOA query. - n=$((n + 1)) - echo_i "check SOA rrset is signed correctly for zone ${ZONE} ($n)" - ret=0 - _dig_with_opts "$ZONE" "@${SERVER}" "SOA" >"dig.out.$DIR.test$n" || _log_error "dig ${ZONE} SOA failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || _log_error "mismatch status in DNS response" - grep "${ZONE}\..*${DEFAULT_TTL}.*IN.*SOA.*" "dig.out.$DIR.test$n" >/dev/null || _log_error "missing SOA record in response" - check_signatures "SOA" "dig.out.$DIR.test$n" "ZSK" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) - - # Test CDS and CDNSKEY publication. - check_cds -} - -# Test an RRset below the apex and verify it is signed correctly. -check_subdomain() { - _qtype="A" - n=$((n + 1)) - echo_i "check ${_qtype} a.${ZONE} rrset is signed correctly for zone ${ZONE} ($n)" - ret=0 - _dig_with_opts "a.$ZONE" "@${SERVER}" $_qtype >"dig.out.$DIR.test$n" || _log_error "dig a.${ZONE} ${_qtype} failed" - grep "status: NOERROR" "dig.out.$DIR.test$n" >/dev/null || _log_error "mismatch status in DNS response" - grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*${_qtype}.*10\.0\.0\.1" "dig.out.$DIR.test$n" >/dev/null || _log_error "missing a.${ZONE} ${_qtype} record in response" - lines=$(get_keys_which_signed $_qtype 0 "dig.out.$DIR.test$n" | wc -l) - check_signatures $_qtype "dig.out.$DIR.test$n" "ZSK" - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Check if "CDS/CDNSKEY Published" is logged. -check_cdslog() { - _dir=$1 - _zone=$2 - _key=$3 - - _alg=$(key_get $_key ALG_STR) - _id=$(key_get $_key ID) - - n=$((n + 1)) - echo_i "check CDS/CDNSKEY publication is logged in ${_dir}/named.run for key ${_zone}/${_alg}/${_id} ($n)" - ret=0 - - if [ "$CDS_SHA256" = "yes" ]; then - grep "CDS (SHA-256) for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" >/dev/null || ret=1 - fi - if [ "$CDS_SHA384" = "yes" ]; then - grep "CDS (SHA-384) for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" >/dev/null || ret=1 - fi - if [ "$CDNSKEY" = "yes" ]; then - grep "CDNSKEY for key ${_zone}/${_alg}/${_id} is now published" "${_dir}/named.run" >/dev/null || ret=1 - fi - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Tell named that the DS for the key in given zone has been seen in the -# parent (this does not actually has to be true, we just issue the command -# to make named believe it can continue with the rollover). -rndc_checkds() { - _server=$1 - _dir=$2 - _key=$3 - _when=$4 - _what=$5 - _zone=$6 - _view=$7 - - _keycmd="" - if [ "${_key}" != "-" ]; then - _keyid=$(key_get $_key ID) - _keycmd=" -key ${_keyid}" - fi - - _whencmd="" - if [ "${_when}" != "now" ]; then - _whencmd=" -when ${_when}" - fi - - n=$((n + 1)) - echo_i "calling rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} in ${_view} ($n)" - ret=0 - - _rndccmd $_server dnssec -checkds $_keycmd $_whencmd $_what $_zone in $_view >rndc.dnssec.checkds.out.$_zone.$n || _log_error "rndc dnssec -checkds${_keycmd}${_whencmd} ${_what} zone ${_zone} failed" - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Tell named to schedule a key rollover. -rndc_rollover() { - _server=$1 - _dir=$2 - _keyid=$3 - _when=$4 - _zone=$5 - _view=$6 - - _whencmd="" - if [ "${_when}" != "now" ]; then - _whencmd="-when ${_when}" - fi - - n=$((n + 1)) - echo_i "calling rndc dnssec -rollover key ${_keyid} ${_whencmd} zone ${_zone} ($n)" - ret=0 - - _rndccmd $_server dnssec -rollover -key $_keyid $_whencmd $_zone in $_view >rndc.dnssec.rollover.out.$_zone.$n || _log_error "rndc dnssec -rollover (key ${_keyid} when ${_when}) zone ${_zone} failed" - - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} diff --git a/bin/tests/system/multisigner/ns3/setup.sh b/bin/tests/system/multisigner/ns3/setup.sh index 123528f35c..80327a1300 100644 --- a/bin/tests/system/multisigner/ns3/setup.sh +++ b/bin/tests/system/multisigner/ns3/setup.sh @@ -28,8 +28,6 @@ KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out. ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) $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 -# ZSK will be added to the other provider with nsupdate. -cat "${ZSK}.key" | grep -v ";.*" >"${zone}.zsk" zone="model2.secondary" echo_i "setting up zone: $zone" diff --git a/bin/tests/system/multisigner/ns4/setup.sh b/bin/tests/system/multisigner/ns4/setup.sh index dc3fc7cebd..c58ddce495 100644 --- a/bin/tests/system/multisigner/ns4/setup.sh +++ b/bin/tests/system/multisigner/ns4/setup.sh @@ -28,8 +28,6 @@ KSK=$($KEYGEN -a $DEFAULT_ALGORITHM -f KSK -L 3600 $ksktimes $zone 2>keygen.out. ZSK=$($KEYGEN -a $DEFAULT_ALGORITHM -L 3600 $zsktimes $zone 2>keygen.out.$zone.2) $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 -# ZSK will be added to the other provider with nsupdate. -cat "${ZSK}.key" | grep -v ";.*" >"${zone}.zsk" zone="model2.secondary" echo_i "setting up zone: $zone" diff --git a/bin/tests/system/multisigner/ns5/named.conf.in b/bin/tests/system/multisigner/ns5/named.conf.in index 0a8e4f556c..eeae1309c5 100644 --- a/bin/tests/system/multisigner/ns5/named.conf.in +++ b/bin/tests/system/multisigner/ns5/named.conf.in @@ -27,6 +27,7 @@ options { recursion no; key-directory "."; dnssec-validation no; + notify-delay 0; }; key rndc_key { diff --git a/bin/tests/system/multisigner/tests.sh b/bin/tests/system/multisigner/tests.sh deleted file mode 100644 index abe19ff215..0000000000 --- a/bin/tests/system/multisigner/tests.sh +++ /dev/null @@ -1,771 +0,0 @@ -#!/bin/sh - -# 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. - -set -e - -# shellcheck source=conf.sh -. ../conf.sh -# shellcheck source=kasp.sh -. ../kasp.sh - -dig_with_opts() { - $DIG +tcp +noadd +nosea +nostat +nocmd +dnssec -p $PORT "$@" -} - -start_time="$(TZ=UTC date +%s)" -status=0 -n=0 - -set_zone "model2.multisigner" -set_policy "model2" "2" "3600" - -# Key properties and states. -key_clear "KEY1" -set_keyrole "KEY1" "ksk" -set_keylifetime "KEY1" "0" -set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY1" "yes" -set_zonesigning "KEY1" "no" -set_keystate "KEY1" "GOAL" "omnipresent" -set_keystate "KEY1" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY1" "STATE_KRRSIG" "omnipresent" -set_keystate "KEY1" "STATE_DS" "omnipresent" - -key_clear "KEY2" -set_keyrole "KEY2" "zsk" -set_keylifetime "KEY2" "0" -set_keyalgorithm "KEY2" "13" "ECDSAP256SHA256" "256" -set_keysigning "KEY2" "no" -set_zonesigning "KEY2" "yes" -set_keystate "KEY2" "GOAL" "omnipresent" -set_keystate "KEY2" "STATE_DNSKEY" "omnipresent" -set_keystate "KEY2" "STATE_ZRRSIG" "omnipresent" - -key_clear "KEY3" -key_clear "KEY4" - -set_keytimes_model2() { - # The first KSK is immediately published and activated. - created=$(key_get KEY1 CREATED) - set_keytime "KEY1" "PUBLISHED" "${created}" - set_keytime "KEY1" "ACTIVE" "${created}" - set_keytime "KEY1" "SYNCPUBLISH" "${created}" - - # The first ZSKs are immediately published and activated. - created=$(key_get KEY2 CREATED) - set_keytime "KEY2" "PUBLISHED" "${created}" - set_keytime "KEY2" "ACTIVE" "${created}" -} - -set_server "ns3" "10.53.0.3" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_model2 -check_keytimes -check_apex -dnssec_verify - -set_server "ns4" "10.53.0.4" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_model2 -check_keytimes -check_apex -dnssec_verify - -# -# Update DNSKEY RRset. -# - -# Check that the ZSKs from the other provider are published. -zsks_are_published() { - dig_with_opts "$ZONE" "@${SERVER}" DNSKEY >"dig.out.$DIR.test$n" || return 1 - cat dig.out.$DIR.test$n | tr [:blank:] ' ' >dig.out.$DIR.test$n.tr || return 1 - # We should have two ZSKs. - lines=$(grep "256 3 13" dig.out.$DIR.test$n.tr | wc -l) - test "$lines" -eq 2 || return 1 - # Both ZSKs are published. - grep "$(cat ns3/${ZONE}.zsk | tr [:blank:] ' ')" dig.out.$DIR.test$n.tr >/dev/null || return 1 - grep "$(cat ns4/${ZONE}.zsk | tr [:blank:] ' ')" dig.out.$DIR.test$n.tr >/dev/null || return 1 - # And one KSK. - lines=$(grep "257 3 13" dig.out.$DIR.test$n.tr | wc -l) - test "$lines" -eq 1 || return 1 -} - -# Test to make sure no DNSSEC records end up in the raw journal. -no_dnssec_in_journal() { - n=$((n + 1)) - ret=0 - echo_i "check zone ${ZONE} raw journal has no DNSSEC ($n)" - $JOURNALPRINT "${DIR}/${ZONE}.db.jnl" >"${DIR}/${ZONE}.journal.out.test$n" - rrset_exists NSEC "${DIR}/${ZONE}.journal.out.test$n" && ret=1 - rrset_exists NSEC3 "${DIR}/${ZONE}.journal.out.test$n" && ret=1 - rrset_exists NSEC3PARAM "${DIR}/${ZONE}.journal.out.test$n" && ret=1 - rrset_exists RRSIG "${DIR}/${ZONE}.journal.out.test$n" && ret= 1 - test "$ret" -eq 0 || echo_i "failed" - status=$((status + ret)) -} - -# Check if a certain RRtype is present in the journal file. -rrset_exists() ( - rrtype=$1 - file=$2 - lines=$(awk -v rt="${rrtype}" '$5 == rt {print}' ${file} | wc -l) - test "$lines" -gt 0 -) - -n=$((n + 1)) -echo_i "add dnskey record: update zone ${ZONE} at ns3 with ZSK from provider ns4 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the new DNSKEY RRset. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the logs for find zone keys errors. -n=$((n + 1)) -echo_i "make sure we did not try to sign with the keys added with nsupdate for zone ${ZONE} ($n)" -ret=0 -grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Verify again. -dnssec_verify - -n=$((n + 1)) -echo_i "add dnskey record: - update zone ${ZONE} at ns4 with ZSK from provider ns3 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "ns3/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the new DNSKEY RRset. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Check the logs for find zone keys errors. -n=$((n + 1)) -echo_i "make sure we did not try to sign with the keys added with nsupdate for zone ${ZONE} ($n)" -ret=0 -grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Verify again. -dnssec_verify -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove dnskey record: - try to remove ns3 ZSK from provider ns3 (should fail) ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns3/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Both ZSKs should still be published. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after failed update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove dnskey record: remove ns4 ZSK from provider ns3 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# We should have only the KSK and ZSK from provider ns3. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -check_keys -check_apex -dnssec_verify - -n=$((n + 1)) -echo_i "remove dnskey record: try to remove ns4 ZSK from provider ns4 (should fail) ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Both ZSKs should still be published. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after failed update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove dnskey record: remove ns3 ZSK from provider ns4 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns3/${ZONE}.zsk") - echo send -) | $NSUPDATE -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# We should have only the KSK and ZSK from provider ns4. -n=$((n + 1)) -echo_i "check zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -check_keys -check_apex -dnssec_verify -no_dnssec_in_journal - -# -# Update CDNSKEY RRset. -# - -# Check that the CDNSKEY from both providers are published. -records_published() { - _rrtype=$1 - _expect=$2 - - dig_with_opts "$ZONE" "@${SERVER}" "${_rrtype}" >"dig.out.$DIR.test$n" || return 1 - lines=$(awk -v rt="${_rrtype}" '$4 == rt {print}' dig.out.$DIR.test$n | wc -l) - test "$lines" -eq "$_expect" || return 1 -} - -# Retrieve CDNSKEY records from the other provider. -dig_with_opts ${ZONE} @10.53.0.3 CDNSKEY >dig.out.ns3.cdnskey -awk '$4 == "CDNSKEY" {print}' dig.out.ns3.cdnskey >cdnskey.ns3 -dig_with_opts ${ZONE} @10.53.0.4 CDNSKEY >dig.out.ns4.cdnskey -awk '$4 == "CDNSKEY" {print}' dig.out.ns4.cdnskey >cdnskey.ns4 - -n=$((n + 1)) -echo_i "add cdnskey record: update zone ${ZONE} at ns3 with CDNSKEY from provider ns4 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -# Initially there should be one CDNSKEY. -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cdnskey.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDNSKEY records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "add cdnskey record: update zone ${ZONE} at ns4 with CDNSKEY from provider ns3 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -# Initially there should be one CDNSKEY. -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cdnskey.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDNSKEY records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove cdnskey record: remove ns4 CDNSKEY from provider ns3 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cdnskey.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDNSKEY record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove cdnskey record: remove ns3 CDNSKEY from provider ns4 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cdnskey.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDNSKEY record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDNSKEY RRset after update ($n)"ret=0 -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -# -# Update CDS RRset. -# - -# Retrieve CDS records from the other provider. -dig_with_opts ${ZONE} @10.53.0.3 CDS >dig.out.ns3.cds -awk '$4 == "CDS" {print}' dig.out.ns3.cds >cds.ns3 -dig_with_opts ${ZONE} @10.53.0.4 CDS >dig.out.ns4.cds -awk '$4 == "CDS" {print}' dig.out.ns4.cds >cds.ns4 - -n=$((n + 1)) -echo_i "add cds record: update zone ${ZONE} at ns3 with CDS from provider ns4 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -# Initially there should be one CDS. -retry_quiet 10 records_published CDS 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cds.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDS records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "add cds record: update zone ${ZONE} at ns4 with CDS from provider ns3 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -# Initially there should be one CDS. -retry_quiet 10 records_published CDS 1 || ret=1 -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "cds.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDS records (we test that BIND does not -# skip it during DNSSEC maintenance). -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove cds record: remove ns4 CDS from provider ns3 ($n)" -ret=0 -set_server "ns3" "10.53.0.3" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cds.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDS record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove cds record: remove ns3 CDS from provider ns4 ($n)" -ret=0 -set_server "ns4" "10.53.0.4" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "cds.ns3") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDS record again. -n=$((n + 1)) -echo_i "check zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# No DNSSEC in raw journal. -no_dnssec_in_journal - -# -# Check secondary server behaviour. -# -set_zone "model2.secondary" -set_policy "model2" "2" "3600" - -set_server "ns3" "10.53.0.3" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_model2 -check_keytimes -check_apex -dnssec_verify - -set_server "ns4" "10.53.0.4" -check_keys -check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" -set_keytimes_model2 -check_keytimes -check_apex -dnssec_verify - -# -# Update DNSKEY RRset. -# -n=$((n + 1)) -echo_i "add dnskey record: update zone ${ZONE} at ns5 with ZSKs from providers ns3 and ns4 ($n)" -ret=0 -set_server "ns5" "10.53.0.5" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "ns3/${ZONE}.zsk") - echo update add $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# NS3 -n=$((n + 1)) -set_server "ns3" "10.53.0.3" -echo_i "check server ${DIR} zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal -grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# NS4 -n=$((n + 1)) -set_server "ns4" "10.53.0.4" -echo_i "check server ${DIR} zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 zsks_are_published || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal -grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "remove dnskey record: remove ns3 and ns4 DNSKEY records from primary ns5 ($n)" -ret=0 -set_server "ns5" "10.53.0.5" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "ns3/${ZONE}.zsk") - echo update del $(cat "ns4/${ZONE}.zsk") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one DNSKEY record again. -# While we did remove both DNSKEY records, the bump in the wire signer, i.e -# the secondary inline-signing zone, should add back the DNSKEY belonging to -# its own KSK when re-signing the zone. -# -# NS3 -n=$((n + 1)) -set_server "ns3" "10.53.0.3" -echo_i "check server ${DIR} zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -check_keys -check_apex -dnssec_verify -no_dnssec_in_journal -# NS4 -n=$((n + 1)) -set_server "ns4" "10.53.0.4" -echo_i "check server ${DIR} zone ${ZONE} DNSKEY RRset after update ($n)" -ret=0 -check_keys -check_apex -dnssec_verify -no_dnssec_in_journal - -# -# Update CDNSKEY RRset. -# - -# Retrieve CDNSKEY records from the providers. -n=$((n + 1)) -echo_i "check initial CDSNKEY response for zone ${ZONE} at ns3 and ns4 ($n)" -ret=0 -dig_with_opts ${ZONE} @10.53.0.3 CDNSKEY >dig.out.ns3.secondary.cdnskey -awk '$4 == "CDNSKEY" {print}' dig.out.ns3.secondary.cdnskey >secondary.cdnskey.ns3 -dig_with_opts ${ZONE} @10.53.0.4 CDNSKEY >dig.out.ns4.secondary.cdnskey -awk '$4 == "CDNSKEY" {print}' dig.out.ns4.secondary.cdnskey >secondary.cdnskey.ns4 -# Initially there should be one CDNSKEY. -set_server "ns3" "10.53.0.3" -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -set_server "ns4" "10.53.0.4" -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "add cdnskey record: update zone ${ZONE} at ns5 with CDNSKEY records from providers ns3 and ns4 ($n)" -ret=0 -set_server "ns5" "10.53.0.5" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "secondary.cdnskey.ns3") - echo update add $(cat "secondary.cdnskey.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDNSKEY records (we test that BIND does not -# skip it during DNSSEC maintenance). -# -# NS3 -n=$((n + 1)) -set_server "ns3" "10.53.0.3" -echo_i "check server ${DIR} zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal -# NS4 -n=$((n + 1)) -set_server "ns4" "10.53.0.4" -echo_i "check server ${DIR} zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove cdnskey record: remove ns3 and ns4 CDNSKEY records from primary ns5 ($n)" -ret=0 -set_server "ns5" "10.53.0.5" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "secondary.cdnskey.ns3") - echo update del $(cat "secondary.cdnskey.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDNSKEY record again. -# While we did remove both CDNSKEY records, the bump in the wire signer, i.e -# the secondary inline-signing zone, should add back the CDNSKEY belonging to -# its own KSK when re-signing the zone. -# -# NS3 -n=$((n + 1)) -set_server "ns3" "10.53.0.3" -echo_i "check server ${DIR} zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal -# NS4 -n=$((n + 1)) -set_server "ns4" "10.53.0.4" -echo_i "check server ${DIR} zone ${ZONE} CDNSKEY RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDNSKEY 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal - -# -# Update CDS RRset. -# - -# Retrieve CDS records from the other provider. -n=$((n + 1)) -echo_i "check initial CDS response for zone ${ZONE} at ns3 and ns4 ($n)" -ret=0 -dig_with_opts ${ZONE} @10.53.0.3 CDS >dig.out.ns3.secondary.cds -awk '$4 == "CDS" {print}' dig.out.ns3.secondary.cds >secondary.cds.ns3 -dig_with_opts ${ZONE} @10.53.0.4 CDS >dig.out.ns4.secondary.cds -awk '$4 == "CDS" {print}' dig.out.ns4.secondary.cds >secondary.cds.ns4 -# Initially there should be one CDS. -set_server "ns3" "10.53.0.3" -retry_quiet 10 records_published CDS 1 || ret=1 -set_server "ns4" "10.53.0.4" -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) - -n=$((n + 1)) -echo_i "add cds record: update zone ${ZONE} at ns5 with CDS from provider ns4 ($n)" -ret=0 -set_server "ns5" "10.53.0.5" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update add $(cat "secondary.cds.ns3") - echo update add $(cat "secondary.cds.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be two CDS records (we test that BIND does not -# skip it during DNSSEC maintenance). -# -# NS3 -n=$((n + 1)) -set_server "ns3" "10.53.0.3" -echo_i "check server ${DIR} zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal -# NS4 -n=$((n + 1)) -set_server "ns4" "10.53.0.4" -echo_i "check server ${DIR} zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 2 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal - -n=$((n + 1)) -echo_i "remove cds record: remove ns3 and ns4 CDS records from primary ns5 ($n)" -ret=0 -set_server "ns5" "10.53.0.5" -( - echo zone "${ZONE}" - echo server "${SERVER}" "${PORT}" - echo update del $(cat "secondary.cds.ns3") - echo update del $(cat "secondary.cds.ns4") - echo send -) | $NSUPDATE || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -# Now there should be one CDS record again. -# While we did remove both CDS records, the bump in the wire signer, i.e -# the secondary inline-signing zone, should add back the CDS belonging to -# its own KSK when re-signing the zone. -# -# NS3 -n=$((n + 1)) -set_server "ns3" "10.53.0.3" -echo_i "check server ${DIR} zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal -# NS4 -n=$((n + 1)) -set_server "ns4" "10.53.0.4" -echo_i "check server ${DIR} zone ${ZONE} CDS RRset after update ($n)" -ret=0 -retry_quiet 10 records_published CDS 1 || ret=1 -test "$ret" -eq 0 || echo_i "failed" -status=$((status + ret)) -dnssec_verify -no_dnssec_in_journal - -echo_i "exit status: $status" -[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/multisigner/tests_multisigner.py b/bin/tests/system/multisigner/tests_multisigner.py new file mode 100644 index 0000000000..695f2420b9 --- /dev/null +++ b/bin/tests/system/multisigner/tests_multisigner.py @@ -0,0 +1,650 @@ +# 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 datetime import timedelta +import os +import re + +import pytest + +pytest.importorskip("dns", minversion="2.0.0") +import dns +import dns.update + +import isctest + +pytestmark = pytest.mark.extra_artifacts( + [ + "*.axfr", + "*.created", + "cdnskey.ns*", + "cds.ns*", + "dig.out.*", + "rndc.dnssec.status.out.*", + "secondary.cdnskey.ns*", + "secondary.cds.ns*", + "unused.*", + "verify.out.*", + "ns*/K*", + "ns*/db-*", + "ns*/keygen.out.*", + "ns*/*.jbk", + "ns*/*.jnl", + "ns*/*.zsk", + "ns*/*.signed", + "ns*/*.journal.out.*", + "ns*/settime.out.*", + "ns*/model2.secondary.db", + ] +) + +ALGORITHM = os.environ["DEFAULT_ALGORITHM_NUMBER"] +SIZE = os.environ["DEFAULT_BITS"] +CONFIG = { + "dnskey-ttl": timedelta(hours=1), + "ds-ttl": timedelta(days=1), + "max-zone-ttl": timedelta(days=1), + "parent-propagation-delay": timedelta(hours=1), + "publish-safety": timedelta(hours=1), + "retire-safety": timedelta(hours=1), + "signatures-refresh": timedelta(days=5), + "signatures-validity": timedelta(days=14), + "zone-propagation-delay": timedelta(minutes=5), +} +TTL = 3600 + + +def dsfromkey(key): + dsfromkey_command = [ + os.environ.get("DSFROMKEY"), + "-T", + str(TTL), + "-a", + "SHA-256", + "-C", + "-w", + str(key.keyfile), + ] + out = isctest.run.cmd(dsfromkey_command) + return out.stdout.decode("utf-8").split() + + +def check_dnssec(server, zone, keys, expected): + ksks = [k for k in keys if k.is_ksk()] + zsks = [k for k in keys if not k.is_ksk()] + + isctest.kasp.check_keys(zone, keys, expected) + + for kp in expected: + kp.set_expected_keytimes(CONFIG) + kp.set_expected_keytimes(CONFIG) + start = kp.key.get_timing("Created") + kp.timing["Published"] = start + kp.timing["Active"] = start + if kp.role != "zsk": + kp.timing["PublishCDS"] = start + + isctest.kasp.check_dnssec_verify(server, zone) + isctest.kasp.check_apex(server, zone, ksks, zsks) + + +def check_no_dnssec_in_journal(server, zone): + journalprint = [ + os.environ.get("JOURNALPRINT"), + f"{server.identifier}/{zone}.db.jnl", + ] + + out = isctest.run.cmd(journalprint) + contents = out.stdout.decode("utf-8") + pattern = re.compile( + r"^\s*(?:\S+\s+){4}(NSEC|NSEC3|NSEC3PARAM|RRSIG)", flags=re.MULTILINE + ) + match = pattern.search(contents) + assert not match, f"{match.group(1)} record found in journal" + + +def wait_for_serial(primary, server, zone): + if primary.identifier == server.identifier: + # No need to check if the transfer has been done. + return + + def check_serial(): + response = isctest.query.tcp( + query, primary.ip, primary.ports.dns, timeout=3, attempts=1 + ) + assert response.rcode() == dns.rcode.NOERROR + soa = response.get_rrset( + response.answer, + dns.name.from_text(fqdn), + dns.rdataclass.IN, + dns.rdatatype.SOA, + ) + serial1 = soa[0].serial + + response = isctest.query.tcp( + query, server.ip, server.ports.dns, timeout=3, attempts=1 + ) + assert response.rcode() == dns.rcode.NOERROR + soa = response.get_rrset( + response.answer, + dns.name.from_text(fqdn), + dns.rdataclass.IN, + dns.rdatatype.SOA, + ) + serial2 = soa[0].serial + + return ( + f"zone {zone}/IN (signed): serial {serial2} (unsigned {serial1})" + in server.log + ) + + fqdn = f"{zone}." + query = isctest.query.create(fqdn, dns.rdatatype.SOA) + + isctest.run.retry_with_timeout(check_serial, timeout=10) + + +def check_add_zsk(server, zone, keys, expected, extra_keys, extra, primary=None): + if primary is None: + primary = server + + isctest.log.info("add dnskey record:") + + isctest.log.info( + f"- zone {zone} {primary.identifier}: update zone with 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.add(f"{zone}.", TTL, "DNSKEY", rdata) + primary.nsupdate(update_msg) + + wait_for_serial(primary, server, zone) + + # Check the new DNSKEY RRset. + isctest.log.info( + f"- zone {zone} {server.identifier}: check DNSKEY RRset after update add" + ) + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + # Check the logs for find zone keys errors. + isctest.log.info( + f"- zone {zone} {server.identifier}: make sure we did not try to sign with the keys added with nsupdate" + ) + server.log.prohibit(f"dns_zone_findkeys: error reading ./K{zone}") + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + extra_keys, expected + extra) + server.log.prohibit(f"dns_zone_findkeys: error reading ./K{zone}") + + +def _check_remove_zsk_fail( + server, zone, keys, expected, extra_keys, extra, primary=None +): + if primary is None: + primary = server + + isctest.log.info( + f"- zone {zone} {primary.identifier}: try to remove own ZSK (should fail)" + ) + + zsks = [k for k in keys if not k.is_ksk()] + dnskey = str(zsks[0].dnskey).split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "DNSKEY", rdata) + with primary.watch_log_from_here() as watcher: + primary.nsupdate(update_msg) + watcher.wait_for_line( + f"updating zone '{zone}/IN': attempt to delete in use DNSKEY ignored" + ) + + # Both ZSKs should still be published. + isctest.log.info( + f"- zone {zone} {server.identifier}: check DNSKEY RRset after ignored remove" + ) + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + +def check_remove_zsk( + server, zone, keys, expected, extra_keys, extra, primary=None, check_fail=False +): + isctest.log.info("remove dnskey record:") + + if primary is None: + primary = server + + if check_fail: + _check_remove_zsk_fail( + 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) + + wait_for_serial(primary, server, zone) + + # We should have only the KSK and ZSK from server. + isctest.log.info( + f"- zone {zone} {server.identifier}: check DNSKEY RRset after update remove" + ) + check_dnssec(server, zone, keys, expected) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys, expected) + + +def check_add_cdnskey(server, zone, keys, expected, extra_keys, extra, primary=None): + if primary is None: + primary = server + + isctest.log.info("add cdnskey record:") + + isctest.log.info( + f"- zone {zone} {primary.identifier}: update zone with CDNSKEY from other providers" + ) + + # Update the server with the CDNSKEY record from the other providers. + update_msg = dns.update.UpdateMessage(zone) + for ksk in extra_keys: + dnskey = str(ksk.dnskey).split() + rdata = " ".join(dnskey[4:]) + update_msg.add(f"{zone}.", TTL, "CDNSKEY", rdata) + primary.nsupdate(update_msg) + + wait_for_serial(primary, server, zone) + + # Now there should be two CDNSKEY records. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDNSKEY RRset after update add" + ) + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + +def _check_remove_cdnskey_fail( + server, zone, keys, expected, extra_keys, extra, primary=None +): + if primary is None: + primary = server + + isctest.log.info( + f"- zone {zone} {primary.identifier}: try to remove own CDNSKEY (should fail)" + ) + + ksks = [k for k in keys if not k.is_ksk()] + dnskey = str(ksks[0].dnskey).split() + rdata = " ".join(dnskey[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDNSKEY", rdata) + with primary.watch_log_from_here() as watcher: + primary.nsupdate(update_msg) + watcher.wait_for_line( + f"updating zone '{zone}/IN': attempt to delete in use CDNSKEY ignored" + ) + + # Both CDNSKEY records should still be published. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDNSKEY RRset after ignored remove" + ) + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + +def check_remove_cdnskey( + server, zone, keys, expected, extra_keys, extra, primary=None, check_fail=False +): + isctest.log.info("remove cdnskey record:") + + if primary is None: + primary = server + + if check_fail: + _check_remove_cdnskey_fail( + 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" + ) + + 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) + + # Now there should be one CDNSKEY record again. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDNSKEY RRset after update remove" + ) + check_dnssec(server, zone, keys, expected) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys, expected) + + +def check_add_cds(server, zone, keys, expected, extra_keys, extra, primary=None): + isctest.log.info("add cds record:") + + if primary is None: + primary = server + + isctest.log.info( + f"- zone {zone} {primary.identifier}: update zone with CDS from other providers" + ) + + # Update the server with the CDS record from the other providers. + update_msg = dns.update.UpdateMessage(zone) + for ksk in extra_keys: + ds = dsfromkey(ksk) + rdata = " ".join(ds[4:]) + update_msg.add(f"{zone}.", TTL, "CDS", rdata) + primary.nsupdate(update_msg) + + wait_for_serial(primary, server, zone) + + # Now there should be two CDS records. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDS RRset after update add" + ) + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + +def _check_remove_cds_fail( + server, zone, keys, expected, extra_keys, extra, primary=None +): + if primary is None: + primary = server + + isctest.log.info( + f"- zone {zone} {primary.identifier}: try to remove own CDS (should fail)" + ) + + ksks = [k for k in keys if not k.is_ksk()] + ds = dsfromkey(ksks[0]) + rdata = " ".join(ds[4:]) + update_msg = dns.update.UpdateMessage(zone) + update_msg.delete(f"{zone}.", "CDS", rdata) + with primary.watch_log_from_here() as watcher: + primary.nsupdate(update_msg) + watcher.wait_for_line( + f"updating zone '{zone}/IN': attempt to delete in use CDS ignored" + ) + + # Both CDS records should still be published. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDS RRset after ignored remove" + ) + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys + extra_keys, expected + extra) + + +def check_remove_cds( + server, zone, keys, expected, extra_keys, extra, primary=None, check_fail=False +): + isctest.log.info("remove cds record:") + + if primary is None: + primary = server + + if check_fail: + _check_remove_cds_fail( + 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" + ) + + 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) + + # Now there should be one CDS record again. + isctest.log.info( + f"- zone {zone} {server.identifier}: check CDS RRset after update remove" + ) + check_dnssec(server, zone, keys, expected) + + # Trigger keymgr. + with server.watch_log_from_here() as watcher: + server.rndc(f"loadkeys {zone}", log=False) + watcher.wait_for_line(f"keymgr: {zone} done") + + # Check again. + isctest.log.info(f"- zone {zone} {server.identifier}: check again after keymgr run") + check_dnssec(server, zone, keys, expected) + + +def test_multisigner(ns3, ns4): + zone = "model2.multisigner" + keyprops = [ + f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 0 {ALGORITHM} {SIZE} 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) + + 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 {ALGORITHM} {SIZE}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False # noqa + extra[0].legacy = True # noqa + + 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, check_fail=True) + check_remove_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra, check_fail=True) + check_no_dnssec_in_journal(ns4, zone) + + # Add CDNSKEY RRset. + newprops = [f"ksk unlimited {ALGORITHM} {SIZE}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False # noqa + extra[0].legacy = True # noqa + + 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, check_fail=True + ) + check_remove_cdnskey( + ns4, zone, keys4, expected4, [ksks3[0]], extra, check_fail=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, check_fail=True) + check_remove_cds(ns4, zone, keys4, expected4, [ksks3[0]], extra, check_fail=True) + check_no_dnssec_in_journal(ns4, zone) + + +def test_multisigner_secondary(ns3, ns4, ns5): + zone = "model2.secondary" + keyprops = [ + f"ksk 0 {ALGORITHM} {SIZE} goal:omnipresent dnskey:omnipresent krrsig:omnipresent ds:omnipresent", + f"zsk 0 {ALGORITHM} {SIZE} 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) + + 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 {ALGORITHM} {SIZE}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False # noqa + extra[0].legacy = True # noqa + + check_add_zsk(ns3, zone, keys3, expected3, [zsks4[0]], extra, primary=ns5) + check_add_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra, primary=ns5) + check_no_dnssec_in_journal(ns3, zone) + check_no_dnssec_in_journal(ns4, zone) + + # Remove DNSKEY from RRset. + check_remove_zsk(ns3, zone, keys3, expected3, [zsks4[0]], extra, primary=ns5) + check_remove_zsk(ns4, zone, keys4, expected4, [zsks3[0]], extra, primary=ns5) + check_no_dnssec_in_journal(ns3, zone) + check_no_dnssec_in_journal(ns4, zone) + + # Add CDNSKEY RRset. + newprops = [f"ksk unlimited {ALGORITHM} {SIZE}"] + extra = isctest.kasp.policy_to_properties(ttl=TTL, keys=newprops) + extra[0].private = False # noqa + extra[0].legacy = True # noqa + + check_add_cdnskey(ns3, zone, keys3, expected3, [ksks4[0]], extra, primary=ns5) + check_add_cdnskey(ns4, zone, keys4, expected4, [ksks3[0]], extra, primary=ns5) + check_no_dnssec_in_journal(ns3, zone) + check_no_dnssec_in_journal(ns4, zone) + + # Remove CDNSKEY RRset. + check_remove_cdnskey(ns3, zone, keys3, expected3, [ksks4[0]], extra, primary=ns5) + check_remove_cdnskey(ns4, zone, keys4, expected4, [ksks3[0]], extra, primary=ns5) + check_no_dnssec_in_journal(ns3, zone) + check_no_dnssec_in_journal(ns4, zone) + + # Update CDS RRset. + check_add_cds(ns3, zone, keys3, expected3, [ksks4[0]], extra, primary=ns5) + check_add_cds(ns4, zone, keys4, expected4, [ksks3[0]], extra, primary=ns5) + check_no_dnssec_in_journal(ns3, zone) + check_no_dnssec_in_journal(ns4, zone) + + # Remove CDS RRset. + check_remove_cds(ns3, zone, keys3, expected3, [ksks4[0]], extra, primary=ns5) + check_remove_cds(ns4, zone, keys4, expected4, [ksks3[0]], extra, primary=ns5) + check_no_dnssec_in_journal(ns3, zone) + check_no_dnssec_in_journal(ns4, zone) diff --git a/bin/tests/system/multisigner/tests_sh_multisigner.py b/bin/tests/system/multisigner/tests_sh_multisigner.py deleted file mode 100644 index 6269823872..0000000000 --- a/bin/tests/system/multisigner/tests_sh_multisigner.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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. - -import pytest - -pytestmark = pytest.mark.extra_artifacts( - [ - "*.created", - "cdnskey.ns*", - "cds.ns*", - "created.*", - "dig.out.*", - "rndc.dnssec.status.out.*", - "secondary.cdnskey.ns*", - "secondary.cds.ns*", - "unused.*", - "verify.out.*", - "ns*/K*", - "ns*/db-*", - "ns*/keygen.out.*", - "ns*/*.jbk", - "ns*/*.jnl", - "ns*/*.zsk", - "ns*/*.signed", - "ns*/*.journal.out.*", - "ns*/settime.out.*", - "ns*/model2.secondary.db", - ] -) - - -def test_multisigner(run_tests_sh): - run_tests_sh()