2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-21 16:39:30 +00:00

test(xcat-core): capture the Genesis root home directory left at /root

mknb writes the management node key to /.ssh/authorized_keys for the legacy
Genesis image. sshd reads that file only while the home directory of root is
/. The dracut cmdline hook makes it / by deleting the root entry the image
ships and appending its own.

The test runs that rewrite against both root entry shapes dracut writes, for
all three hooks, and reads back /etc/passwd. It also keeps a user name that
starts with root in the file, so a wider delete cannot pass unnoticed.

Six of the eighteen assertions fail today.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-05 00:36:25 -03:00
parent 772f11c257
commit 100d537a7d
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env perl
# Drive the /etc/passwd rewrite out of the Genesis dracut cmdline hooks.
#
# mknb writes the management node key to /.ssh/authorized_keys for the legacy Genesis
# image, so sshd finds it only while the home directory of root is /. The hook makes it /
# by deleting the root entry the image ships and appending its own. Run that rewrite
# against every root entry shape dracut writes and read back the result.
use strict;
use warnings;
use File::Slurper qw(read_text write_text);
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(repo_path);
my @HOOKS = (
'xCAT-genesis-builder/xcat-cmdline.sh',
'xCAT-genesis-builder/dracut_105/el/xcat-cmdline.sh',
'xCAT-genesis-builder/dracut_105/ubuntu/xcat-cmdline.sh',
);
# dracut 99base writes the root entry itself. Up to dracut 057 the password field is
# always x. From dracut 060 the x arrives only with --hostonly, and the Genesis image is
# built with -N, so el10 (dracut 107) ships an empty password field.
my %SHIPPED = (
'dracut 049/057 (el8, el9)' => "root:x:0:0::/root:/bin/sh\n",
'dracut 107 (el10)' => "root::0:0::/root:/bin/sh\n",
);
# A user name that starts with root but is not root. The delete must keep this line.
my $DECOY = "rootfsadm:x:501:501::/home/rootfsadm:/sbin/nologin\n";
plan tests => 3 * @HOOKS * scalar(keys %SHIPPED);
my $tmpdir = tempdir(CLEANUP => 1);
foreach my $hook (@HOOKS) {
my $block = extract_passwd_block(repo_path($hook), $hook);
foreach my $shape (sort keys %SHIPPED) {
my $passwd = run_rewrite($block, $SHIPPED{$shape} . $DECOY, $hook);
my @root = grep { /^root:/ } split(/\n/, $passwd);
is(scalar @root, 1,
"$hook / $shape: one root entry is left in /etc/passwd");
is($root[0], 'root:x:0:0::/:/bin/bash',
"$hook / $shape: the home directory of root is /");
like($passwd, qr/^\Qrootfsadm:x:501:501::\/home\/rootfsadm:\/sbin\/nologin\E$/m,
"$hook / $shape: a user name that starts with root is kept");
}
}
#---
# extract_passwd_block: lift the /etc/passwd rewrite out of a hook that cannot be sourced.
# The hook mounts filesystems, starts udev and ends in an endless loop.
# Bails out when the block stops being extractable, so a rewrite fails loudly instead of
# leaving the test asserting nothing.
#---
sub extract_passwd_block {
my ($path, $label) = @_;
my $text = read_text($path);
my ($block) = $text =~ m{^(sed [^\n]*/etc/passwd\ncat >>/etc/passwd <<"__ENDL"\n.*?^__ENDL)$}ms;
BAIL_OUT("$label: the /etc/passwd rewrite was not found") unless defined $block;
return $block;
}
#---
# run_rewrite: run the extracted block against a scratch passwd file and return it.
# The block names /etc/passwd literally, so the path is redirected into the scratch tree
# first. Bails out if any reference to the real file survives, because the block runs as
# root under CI.
#---
sub run_rewrite {
my ($block, $shipped, $label) = @_;
my $dir = tempdir(DIR => $tmpdir, CLEANUP => 1);
my $passwd = "$dir/passwd";
write_text($passwd, $shipped);
my $script = $block;
my $hits = ($script =~ s{/etc/passwd}{$passwd}g);
BAIL_OUT("$label: expected 2 references to /etc/passwd, found $hits") unless $hits == 2;
BAIL_OUT("$label: a reference to the real /etc/passwd survived") if index($script, '/etc/passwd') >= 0;
write_text("$dir/rewrite.sh", "set -e\n$script\n");
system('/bin/bash', "$dir/rewrite.sh") == 0
or BAIL_OUT("$label: the /etc/passwd rewrite failed to run");
return read_text($passwd);
}