2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-02 08:51:44 +00:00

fix(xcat-server): a Debian service node's named serves the wrong configuration

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-29 17:18:05 -03:00
parent 4997fa14a1
commit 14f0e13ec4
5 changed files with 231 additions and 12 deletions
+27
View File
@@ -2528,4 +2528,31 @@ sub searchcompressedrootimg{
}
#-----------------------------------------------------------------------------
=head3 named_service_action
Which service action brings a freshly written named configuration into effect.
Linux gets a restart, not a start. On Debian the package already runs the daemon, so a start
is a no-op and named keeps serving the configuration it read at install time: the zone
makenamed.conf has just written is never loaded, and a compute node cannot resolve its
service node. AIX keeps the start it has always used.
Arguments: the platform, 'aix' or 'linux'
Returns: 'start', 'restart', or '' for a platform with no action
=cut
#-----------------------------------------------------------------------------
sub named_service_action {
my ($platform) = @_;
$platform = '' unless defined $platform;
return 'start' if $platform eq 'aix';
return 'restart' if $platform eq 'linux';
return '';
}
1;
+9 -3
View File
@@ -788,6 +788,8 @@ sub setup_FTP
=cut
#-----------------------------------------------------------------------------
sub setup_DNS
{
my $srvclist = shift;
@@ -827,11 +829,15 @@ sub setup_DNS
#}
#my $rc = xCAT::Utils->startService($serv);
# Restart, not start. makenamed.conf has just rewritten named.conf, and a start is a no-op
# where the package already runs the daemon -- Debian does -- so the service keeps serving the
# configuration it read at install time.
my $rc = 0;
if (xCAT::Utils->isAIX()) {
my $action = xCAT::SvrUtils::named_service_action(xCAT::Utils->isAIX() ? 'aix' : 'linux');
if ($action eq 'start') {
$rc = xCAT::Utils->startService("named");
} elsif (xCAT::Utils->isLinux()) {
$rc = xCAT::Utils->startservice("named");
} elsif ($action eq 'restart') {
$rc = xCAT::Utils->restartservice("named");
}
if ($rc != 0)
+55 -9
View File
@@ -19,7 +19,7 @@ is_lsb_ubuntu ()
exit 1 # Not Ubuntu
}
' /etc/lsb-release >/dev/null 2>&1
' "${1:-/etc/lsb-release}" >/dev/null 2>&1
# Routine exit status is exit status of the last command -- the awk script.
#
@@ -28,14 +28,60 @@ is_lsb_ubuntu ()
}
DIRECTORY=/var/named
# forwarder_addresses [<resolv.conf> [<systemd-resolved uplink>]]
#
# The addresses named must forward to, one per line.
#
# A loopback address is not a forwarder. On a host managed by systemd-resolved /etc/resolv.conf
# holds the 127.0.0.53 stub, and that stub points back at this named for the link, so
# "forward only" to it answers nothing. systemd-resolved writes the real servers to its own
# uplink file, which is the fallback.
forwarder_addresses()
{
_fa_resolv=${1:-/etc/resolv.conf}
_fa_uplink=${2:-/run/systemd/resolve/resolv.conf}
_fa_addrs=$(_fa_usable "$_fa_resolv")
if [ -z "$_fa_addrs" ]; then
_fa_addrs=$(_fa_usable "$_fa_uplink")
fi
[ -n "$_fa_addrs" ] && printf '%s\n' "$_fa_addrs"
return 0
}
# check for SLES
grep -s -q sles /etc/os-release
IS_SLES=$?
if [ -f /etc/SuSE-release ] || [ $IS_SLES -eq 0 ]; then
DIRECTORY=/var/lib/named
fi
_fa_usable()
{
[ -r "$1" ] || return 0
awk '$1 == "nameserver" && $2 !~ /^127\./ && $2 != "::1" { print $2 }' "$1" 2>/dev/null
}
# named_directory [<lsb-release> [<os-release> [<SuSE-release>]]]
#
# The working directory for named. It must be writable by the user named drops to: Debian runs
# it as "bind" and ships /var/cache/bind for this, while /var/named is created root-owned. named
# writes its managed-keys database there, and when that write fails it answers NXDOMAIN to every
# query, forwarded ones included.
named_directory()
{
_nd_lsb=${1:-/etc/lsb-release}
_nd_os=${2:-/etc/os-release}
_nd_suse=${3:-/etc/SuSE-release}
if is_lsb_ubuntu "$_nd_lsb"; then
echo /var/cache/bind
return 0
fi
if [ -f "$_nd_suse" ] || grep -s -q sles "$_nd_os"; then
echo /var/lib/named
return 0
fi
echo /var/named
}
# A test loads this file for the routine above and must not run the rest, which writes
# named.conf and restarts the service.
[ "${MAKENAMED_LIB:-}" = 1 ] && return 0
DIRECTORY=$(named_directory)
FILE=/etc/named.conf
if ( is_lsb_ubuntu ); then
@@ -58,7 +104,7 @@ echo "options {
forward only;
forwarders {" >$FILE
for i in $(grep "^nameserver" /etc/resolv.conf | awk '{print $2}')
forwarder_addresses | while read -r i
do
echo " $i;"
done >>$FILE
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bats
#
# makenamed.conf builds the forwarder list of a service node's named from /etc/resolv.conf.
#
# On a host managed by systemd-resolved that file holds the 127.0.0.53 stub, and the stub points
# back at this named for the link. forward-only to it is a loop: the service node answers
# nothing, and every compute node behind it fails to resolve. Measured on xcat22-sn, where
# "dig @<sn> xcat22-sn.xcat22.lab" returned nothing while the same query to the management node
# answered. The EL service node has the real upstream in /etc/resolv.conf, which is why it works.
#
# systemd-resolved writes the real servers to /run/systemd/resolve/resolv.conf.
load 'helpers/shell_source'
setup()
{
SCRIPT="$(require_repo_file 'xCAT-server/sbin/makenamed.conf')"
RESOLV="${BATS_TEST_TMPDIR}/resolv.conf"
UPLINK="${BATS_TEST_TMPDIR}/uplink.conf"
# MAKENAMED_LIB stops the script before it writes anything, so only the routine is loaded.
MAKENAMED_LIB=1 . "$SCRIPT"
export -f forwarder_addresses _fa_usable
}
@test "a real nameserver is a forwarder" {
printf 'nameserver 192.168.222.1\nsearch xcat22.lab\n' >"$RESOLV"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$status" -eq 0 ]
[ "$output" = "192.168.222.1" ]
}
@test "the systemd-resolved stub is not a forwarder, and the uplink answers instead" {
printf 'nameserver 127.0.0.53\noptions edns0 trust-ad\n' >"$RESOLV"
printf 'nameserver 192.168.222.1\nsearch xcat22.lab\n' >"$UPLINK"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$status" -eq 0 ]
[ "$output" = "192.168.222.1" ]
}
@test "any loopback address is rejected, not only the stub" {
printf 'nameserver 127.0.0.1\nnameserver ::1\n' >"$RESOLV"
printf 'nameserver 10.0.0.1\n' >"$UPLINK"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$output" = "10.0.0.1" ]
}
@test "several real nameservers are all forwarders, in order" {
printf 'nameserver 192.168.222.1\nnameserver 10.0.0.2\n' >"$RESOLV"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$output" = "192.168.222.1
10.0.0.2" ]
}
@test "a real nameserver wins over the uplink, which is only the fallback" {
printf 'nameserver 192.168.222.1\n' >"$RESOLV"
printf 'nameserver 10.9.9.9\n' >"$UPLINK"
run forwarder_addresses "$RESOLV" "$UPLINK"
[ "$output" = "192.168.222.1" ]
}
@test "no usable address anywhere prints nothing rather than a blank forwarder" {
printf 'nameserver 127.0.0.53\n' >"$RESOLV"
: >"$UPLINK"
# Count the lines. A blank line reaches named.conf as an empty forwarder entry, which bind
# rejects as a syntax error, and $output cannot tell one from no output at all.
run bash -c "forwarder_addresses '$RESOLV' '$UPLINK' | wc -l"
[ "$status" -eq 0 ]
[ "$output" = "0" ]
}
@test "an absent uplink file is not an error" {
printf 'nameserver 127.0.0.53\n' >"$RESOLV"
run forwarder_addresses "$RESOLV" "${BATS_TEST_TMPDIR}/absent"
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
# named runs as "bind" on Debian and writes its managed-keys database into the configured
# directory. /var/named is created root-owned, so the write fails, DNSSEC initialisation fails
# with it, and the server answers NXDOMAIN to every query -- including forwarded ones, which is
# how a correct forwarder list still resolved nothing on xcat22-sn.
@test "Ubuntu names the directory its named can write" {
printf 'DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=24.04\n' >"${BATS_TEST_TMPDIR}/lsb"
run named_directory "${BATS_TEST_TMPDIR}/lsb" "${BATS_TEST_TMPDIR}/absent-os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/cache/bind" ]
}
@test "SLES keeps its own directory" {
: >"${BATS_TEST_TMPDIR}/lsb"
printf 'ID="sles"\n' >"${BATS_TEST_TMPDIR}/os"
run named_directory "${BATS_TEST_TMPDIR}/lsb" "${BATS_TEST_TMPDIR}/os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/lib/named" ]
}
@test "EL keeps /var/named" {
: >"${BATS_TEST_TMPDIR}/lsb"
printf 'ID="almalinux"\n' >"${BATS_TEST_TMPDIR}/os"
run named_directory "${BATS_TEST_TMPDIR}/lsb" "${BATS_TEST_TMPDIR}/os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/named" ]
}
@test "an absent lsb-release is not Ubuntu" {
printf 'ID="almalinux"\n' >"${BATS_TEST_TMPDIR}/os"
run named_directory "${BATS_TEST_TMPDIR}/absent-lsb" "${BATS_TEST_TMPDIR}/os" "${BATS_TEST_TMPDIR}/absent-suse"
[ "$output" = "/var/named" ]
}
@@ -0,0 +1,33 @@
#!/usr/bin/env perl
# setup_DNS writes named.conf through makenamed.conf and then brings the daemon up. It used to
# START the service. On Debian the package already runs named, so a start is a no-op: the daemon
# keeps serving the configuration it read at install time, the zone xCAT has just written is
# never loaded, and a compute node cannot resolve its service node.
#
# A minimization of this change set dropped that fix, because no fast test could see it. The
# decision lives in xCAT::SvrUtils, which a test can load; AAsn.pm cannot be loaded from a source
# tree at all, since it pulls in xCAT::Table and the rest of the server.
use strict;
use warnings;
use FindBin;
use Test::More;
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use xCAT::SvrUtils;
can_ok('xCAT::SvrUtils', 'named_service_action') or do { done_testing(); exit 1 };
is(xCAT::SvrUtils::named_service_action('linux'), 'restart',
'Linux restarts named, so the configuration just written is the one it serves');
is(xCAT::SvrUtils::named_service_action('aix'), 'start',
'AIX keeps the start it has always used');
is(xCAT::SvrUtils::named_service_action(''), '',
'an unknown platform does nothing rather than guessing an action');
is(xCAT::SvrUtils::named_service_action(undef), '',
'an undefined platform does nothing');
done_testing();