Merge #12 Introduce test checking iterative resolver

This commit is contained in:
Petr Sklenar 2024-08-04 18:16:52 +00:00
commit 0b4c79d740
2 changed files with 402 additions and 0 deletions

View file

@ -0,0 +1,31 @@
summary: Configure bind as caching and validating iterative resolver
test: ./test.sh
framework: beakerlib
description: |
Configures named as a caching iterative resolver. Keep dnssec-validation enabled and try to
start resolution from root servers hints.
Test signed zones are validated and include ad bit. Check unsigned zones are
missing ad bit in responses. Requires public root-servers.net and other zone nameservers are directly accessible.
recommend+:
- sed
- awk
adjust:
- recommend+:
- bind
- bind-utils
environment+:
PACKAGE: bind
when: component is not defined or component == bind
- recommend+:
- bind9.16
- bind9.16-utils
environment+:
PACKAGE: bind9.16
when: component == bind9.16
- recommend+:
- bind9-next
- bind9-next-utils
environment+:
PACKAGE: bind9-next
when: component == bind9-next

View file

@ -0,0 +1,371 @@
#!/bin/bash
# vim: dict+=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
. /usr/share/beakerlib/beakerlib.sh || exit 1
: ${CLEAN_ANCHORS:=y}
: ${NAMED_OPTIONS:=}
# How many times to check basic root check
: ${BASIC_TRIES:=3}
# How many times to check host checks
: ${HOSTS_TRIES:=3}
# On which host check pass require successful responses?
: ${HOSTS_PASSING:=2}
# Delay after named service start
: ${DELAY_START:=5}
# Delay between tests
: ${DELAY_TEST:=5}
bu_FALLBACK_SERVERS="8.8.8.8 8.8.4.4 9.9.9.9"
bu_DELV=$(type -p delv 2>/dev/null)
bu_DIG=$(type -p dig 2>/dev/null)
# Servers which failed DNSSEC-awareness check
bu_FAILED_SERVERS=""
bu_ROOT_HINTS=/var/named/named.ca
buGetRootServerAddresses4() {
local HINTS="${1:-$bu_ROOT_HINTS}"
# a.root-servers.net. 518400 IN A 198.41.0.4
# A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4
awk -v IGNORECASE=1 -- '$1 ~ /[a-m]\.root-servers\.net\./ && $3 == "A" { print $4 } $3 == "IN" && $4 == "A" { print $5 }' "$HINTS"
}
buGetRootServerAddresses6() {
local HINTS="${1:-$bu_ROOT_HINTS}"
awk -v IGNORECASE=1 -- '$1 ~ /[a-m]\.root-servers\.net\./ && $3 == "AAAA" { print $4 } $3 == "IN" && $4 == "AAAA" { print $5 }' "$HINTS"
}
# Get list of nameservers from resolv.conf file on standard output
buGetServersConf() {
local RESOLV_CONF=${1:-/etc/resolv.conf}
awk '$1 == "nameserver" { printf "%s%s", DL, $2; DL=" " }' "$RESOLV_CONF"
}
# Get list of resolv-conf (like) files, which might contain useful DNS servers
# Ordered in preference, tries to avoid DNSSEC-unaware servers
buGetCandidateResolvConf() {
local -a CONF_FILES=()
systemctl is-active --quiet NetworkManager && CONF_FILES+=("/run/NetworkManager/no-stub-resolv.conf")
systemctl is-active --quiet systemd-resolved && CONF_FILES+=("/run/systemd/resolve/resolv.conf")
CONF_FILES+=("/etc/resolv.conf")
echo "${CONF_FILES[@]}"
}
# Print list of nameservers addresses, space separated.
# No check on them is done.
buGetNameservers() {
# avoids systemd-resolved breaking dnssec, prefer communication with remote servers directly
for CONF in $(buGetCandidateResolvConf)
do
local SERVERS
# intentionally do not prefer local resolv.conf, because systemd-resolved is breaking it often
if [ -r "$CONF" ] && SERVERS=$(buGetServersConf "$CONF") && [ -n "$SERVERS" ]; then
echo "$SERVERS"
break
fi
done
}
# Test server IP addresses give as parameters to find those, who
# are security-aware.
# Param1: space separated IP addresses of DNS servers
buCheckSecureNameservers() {
local SERVERS="$1"
bu_SECURE_SERVERS=""
if [ -z "$bu_DELV" ] && [ -z "$bu_DIG" ]; then
rlFail "Both delv and dig from bind-utils are missing!"
return 1
fi
for NS in ${SERVERS}; do
# use tcp to fail faster on ipv6 global route not available
if [ -n "$bu_DELV" ]; then
if $bu_DELV +tcp @$NS | grep -q '^; fully validated'; then
bu_SECURE_SERVERS+="$NS "
else
bu_FAILED_SERVERS+="$NS "
fi
elif [ -n "$bu_DIG" ]; then
if $bu_DIG +tcp +noall +answer +dnssec @$NS | grep -qw RRSIG; then
bu_SECURE_SERVERS+="$NS "
else
bu_FAILED_SERVERS+="$NS "
fi
fi
done
[ -n "$bu_SECURE_SERVERS" ]
}
# Try to find DNSSEC capable network provided forwarders.
# If that fails try to verify $bu_FALLBACK_SERVERS works and use them.
# If no working server is found, call rlDie to stop the test.
# Outputs found servers into bu_SECURE_SERVERS and bu_FAILED_SERVERS
# variables
buGetSecureNameservers() {
bu_FAILED_SERVERS=""
local SERVERS=""
# avoids systemd-resolved breaking dnssec, prefer communication with remote servers directly
for CONF in $(buGetCandidateResolvConf)
do
# intentionally do not prefer local resolv.conf, because systemd-resolved is breaking it often
if [ -r "$CONF" ] && SERVERS=$(buGetServersConf "$CONF") && [ -n "$SERVERS" ]; then
rlLogDebug "Checking servers from $CONF..."
buCheckSecureNameservers "$SERVERS" && break
fi
done
if [ -z "${bu_SECURE_SERVERS}${bu_FAILED_SERVERS}" ]; then
rlDie "No nameservers obtained, tried files: $(buGetCandidateResolvConf)"
return 1
fi
if [ -z "$bu_SECURE_SERVERS" ]; then
rlLog "Found resolv files..."
for CONF in $(buGetCandidateResolvConf)
do
rlRun -l "cat $CONF"
done
rlLog "Versions of network provided nameservers..."
for NS in ${SERVERS}
do
rlRun -l "dig @$NS txt ch version.bind" 0-255
done
if [ -n "$bu_FALLBACK_SERVERS" ]; then
# If we have access to public DNS servers, use them instead. They are known to support DNSSEC.
buCheckSecureNameservers "$bu_FALLBACK_SERVERS"
rlLogWarning "No network provided servers (${SERVERS}) support DNSSEC! Fix the infrastructure!"
SERVERS+=" $bu_FALLBACK_SERVERS"
fi
fi
if [ -z "$bu_SECURE_SERVERS" ]; then
rlDie "No servers from ${SERVERS} support DNSSEC! Fix the infrastructure!"
return 1
fi
if [ -n "$bu_FAILED_SERVERS" ]; then
rlLogWarning "Servers not supporting DNSSEC: ${bu_FAILED_SERVERS}"
fi
rlLogInfo "Found security-aware servers: $bu_SECURE_SERVERS"
}
# Create bind forwarder configuration from servers entered as parameters
buMakeForwarders()
{
echo 'forwarders {';
for NS in "$@"
do
printf "\t%s;\n" $NS
done
echo '}; # autogenerated'
}
# Prints formatted used options in bind config
buPrintOptions()
{
named-checkconf -px "$@" | sed -ne '/^options {/,/^};/ p'
}
# Check whether option in $1 is used in options {} global block
buHasOption()
{
local OPTION="$1"
buPrintOptions | grep -qw "^\s*${OPTION}"
}
# Filter dig to print only desired section
# Input is dig output
buDigGetSection()
{
local SECTION="${1:-ANSWER}"
sed -ne "/^;; ${SECTION} SECTION:/,/^$/ p" | grep -vE '^(\s*$|;.*$)'
}
# Filter dig to print only desired value from double comment lines
# Input is dig output
buDigGetField()
{
local FIELD="$1"
grep "^;;.*\s${FIELD}:" | sed -e "s/.*\s${FIELD}:\s*\([^;,]*\)\([;,].*\|$\)/\1/"
}
# Filter dig input to print pseudosection contents only
buDigPseudosection()
{
sed -ne "/^;; OPT PSEUDOSECTION:/,/^;; QUESTION SECTION/ p" | grep -vE '^;; (OPT PSEUDO|QUESTION )SECTION:'
}
# Filter dig to print only desired value from single comment lines
# Useful for pseudosection
# Input is dig output
buDigGetField1()
{
local FIELD="$1"
grep "^;\s\(.*\s\)\?${FIELD}:" | sed -e "s/.*\s${FIELD}:\s*\([^;,]*\)\([;,].*\|$\)/\1/"
}
# just receive any response, no matter what status
buDig()
{
rlRun -s "dig $*"
}
# Ensure reply has noerror status
buDigSuccess()
{
rlRun -s "dig $*"
local STATUS="$(buDigGetField status < $rlRun_LOG)"
rlAssertEquals "Check result was positive" "$STATUS" NOERROR
}
# Ensure reply is signed and verified
buDigSuccessSecure()
{
rlRun -s "dig $*"
local STATUS="$(buDigGetField status < $rlRun_LOG)"
rlAssertEquals "Check dig result was positive" "$STATUS" NOERROR
local FLAGS="$(buDigGetField flags < $rlRun_LOG)"
rlRun "echo $FLAGS | grep -w ad" 0 "Check dig result has AD bit set"
}
# Ensure reply is positive but insecure
buDigSuccessInsecure()
{
rlRun -s "dig $*"
local STATUS="$(buDigGetField status < $rlRun_LOG)"
rlAssertEquals "Check dig result was positive" "$STATUS" NOERROR
local FLAGS="$(buDigGetField flags < $rlRun_LOG)"
rlRun "echo $FLAGS | grep -vw ad" 0 "Check dig result has AD bit unset"
}
# Extract KSK key id from dig
buDigKskId()
{
dig +nocrypto +short -t dnskey "$@" | awk '$1 == 257 { sub("]", "", $7); print $7 }'
}
rlJournalStart
rlPhaseStartSetup
rlRun "tmp=\$(mktemp -d)" 0 "Create tmp directory"
rlRun "pushd $tmp"
rlRun "set -o pipefail"
rlRun "named -V"
rlRun "dig -v"
rlFileBackup /etc/named.conf
rlRun "named-checkconf" 0 "Test generated configuration is acccepted"
rlRun "rlServiceStop named"
if [ "$CLEAN_ANCHORS" = y ]; then
rlFileBackup --clean /var/named/dynamic/managed-keys.bind{,.jnl}
rlRun "rm -f /var/named/dynamic/managed-keys.bind{,.jnl}"
fi
rlAssertExists $bu_ROOT_HINTS
# use buCheckSecureNameservers
rlRun "HINTS4=\"$(buGetRootServerAddresses4 $bu_ROOT_HINTS)\""
rlRun "HINTS6=\"$(buGetRootServerAddresses6 $bu_ROOT_HINTS)\""
rlRun "HINTS4_NUM=$(echo \"$HINTS4\" | wc -l)"
rlRun "HINTS6_NUM=$(echo \"$HINTS6\" | wc -l)"
HINTS4_WORKS=''
HINTS6_WORKS=''
if [ "$NAMED_OPTIONS" = auto ]; then
# TODO: is this complication desirable?
if [ "$HINTS4_NUM" -gt 0 ] && buCheckSecureNameservers "$HINTS4"; then
SECURE_SERVERS4="$bu_SECURE_SERVERS"
FAILED_SERVERS4="$bu_FAILED_SERVERS"
HINTS4_WORKS=y
fi
if [ "$HINTS6_NUM" -gt 0 ] && buCheckSecureNameservers "$HINTS6"; then
SECURE_SERVERS6="$bu_SECURE_SERVERS"
FAILED_SERVERS6="$bu_FAILED_SERVERS"
[ -n "$FAILED_SERVERS4" ] && bu_FAILED_SERVERS+=" $FAILED_SERVERS4"
[ -n "$SECURE_SERVERS4" ] && bu_SECURE_SERVERS+=" $SECURE_SERVERS4"
HINTS6_WORKS=y
fi
if [ "$HINTS4_WORKS" = y ] && [ -z "$HINTS6_WORKS" ]; then
rlRun "NAMED_OPTIONS='-4'"
elif [ "$HINTS6_WORKS" = y ] && [ -z "$HINTS4_WORKS" ]; then
rlRun "NAMED_OPTIONS='-6'"
else
rlRun "NAMED_OPTIONS=''"
fi
else
rlRun "buCheckSecureNameservers \"$HINTS4 $HINTS6\"" && HINTS4_WORKS=y && HINTS4_WORKS=y
fi
if [ -n "$NAMED_OPTIONS" ]; then
rlFileBackup /etc/sysconfig/named
echo "OPTIONS+=\"$NAMED_OPTIONS\"" >> /etc/sysconfig/named
rlRun "grep OPTIONS /etc/sysconfig/named"
fi
SKIP_TEST=''
if [ -z "$HINTS6_WORKS" ] && [ -z "$HINTS4_WORKS" ]
then
SKIP_TEST=y
rlLogWarning "No root-servers reachable, skipping the test."
fi
WORKING_NUM=0
for NSIP in ${bu_SECURE_SERVERS}; do
WORKING_NUM=$((WORKING_NUM+1))
done
rlAssertGreater "Check we have at least some working root servers" "$WORKING_NUM" 5
rlLog "Working root-servers: $bu_SECURE_SERVERS"
[ -n "$bu_FAILED_SERVERS" ] && rlLogWarning "Failed root-servers: $bu_FAILED_SERVERS"
HAS_MANAGED_KEYS=''
rndc -h 2>&1 | grep -q 'managed-keys status' && HAS_MANAGED_KEYS='y'
rlPhaseEnd
if [ "$SKIP_TEST" != y ]; then
for I in {1..3}; do
rlPhaseStartTest "Basic test #$I"
rlRun "rlServiceStart named"
# give it chance to warm up
sleep ${DELAY_START}
buDigSuccessSecure @localhost . DNSKEY
buDigSuccessSecure @localhost
KEYID=$(buDigKskId @localhost .)
rlAssertNotEquals "Check keyId is not empty." "$KEYID" ""
rlAssertGreater "Check keyId is non-zero" "$KEYID" 0
rlRun "rndc secroots"
rlRun "grep \"^./RSASHA256/$KEYID\" /var/named/data/named.secroots" 0 "Check trust anchor is trusted"
[ "$HAS_MANAGED_KEYS" = y ] && rlRun "rndc managed-keys status"
rlPhaseEnd
sleep ${DELAY_TEST}
done
DIG_SEC=buDig
DIG_INSEC=buDig
for I in $(seq $HOSTS_TRIES); do
rlPhaseStartTest "Host tests #$I"
if [ "$I" = "$HOSTS_PASSING" ]; then
DIG_SEC=buDigSuccessSecure
DIG_INSEC=buDigSuccessInsecure
fi
for H in example.{org,com,net} fedoraproject.org isc.org
do
$DIG_SEC @localhost $H A
$DIG_SEC @localhost $H AAAA
done
for H in {org,com,net}
do
$DIG_SEC @localhost $H NS
$DIG_SEC @localhost $H DS
done
for H in {a,d,f}.root-servers.net ipv4only.arpa
do
$DIG_INSEC @localhost $H A
$DIG_INSEC @localhost $H AAAA
done
rlPhaseEnd
sleep ${DELAY_TEST}
done
fi
rlPhaseStartCleanup
[ "$DEBUG" = y ] && PS1="test-debug $PS1" $SHELL -i
rlRun "popd"
rlRun "rm -r $tmp" 0 "Remove tmp directory"
rlFileRestore
rlRun "rlServiceRestore named"
rlPhaseEnd
rlJournalEnd