From 135f14d1a311c9483cd49496cf4b4d0055fd92e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:28:00 -0300 Subject: [PATCH] test(kea): preserve named configuration group selection (cherry picked from commit d1555093456ab8e9ea19867a2432e07220e30358) --- xCAT-test/unit/dhcp_kea_config_group.t | 193 +++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 xCAT-test/unit/dhcp_kea_config_group.t diff --git a/xCAT-test/unit/dhcp_kea_config_group.t b/xCAT-test/unit/dhcp_kea_config_group.t new file mode 100644 index 000000000..aa1f906f7 --- /dev/null +++ b/xCAT-test/unit/dhcp_kea_config_group.t @@ -0,0 +1,193 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use File::Temp qw/tempfile/; +use Test::More; + +our ( %TEST_USERS, %TEST_GROUPS, %TEST_GROUP_NAMES ); +our ( @USER_LOOKUPS, @GROUP_LOOKUPS, @GROUP_ID_LOOKUPS ); + +BEGIN { + no warnings 'redefine'; + + *CORE::GLOBAL::getpwnam = sub { + my ($name) = @_; + push @USER_LOOKUPS, $name; + my $entry = $TEST_USERS{$name}; + return unless $entry; + return @$entry if wantarray; + return $entry->[0]; + }; + + *CORE::GLOBAL::getgrnam = sub { + my ($name) = @_; + push @GROUP_LOOKUPS, $name; + my $entry = $TEST_GROUPS{$name}; + return unless $entry; + return @$entry if wantarray; + return $entry->[0]; + }; + + *CORE::GLOBAL::getgrgid = sub { + my ($gid) = @_; + push @GROUP_ID_LOOKUPS, $gid; + my $name = $TEST_GROUP_NAMES{$gid}; + return unless defined $name; + return ( $name, 'x', $gid, '' ) if wantarray; + return $name; + }; +} + +use xCAT::DHCP::Backend::Kea; + +my $backend = xCAT::DHCP::Backend::Kea->new(); + +set_nss( + users => { + _kea => [ '_kea', 'x', 100, 300, '', '', '', '/var/empty', '/sbin/nologin' ], + }, + groups => { + kea => [ 'kea', 'x', 200, '' ], + _kea => [ '_kea', 'x', 400, '' ], + }, + group_names => { 300 => 'daemon-primary' }, +); + +my $service_account = selected_service_account($backend); +is_deeply( + $service_account, + { name => '_kea', uid => 100, gid => 300 }, + 'service account selection preserves the fallback daemon identity' +); + +reset_lookups(); +my ( $group, $gid ) = selected_config_group($backend); +is( $group, 'kea', 'the preferred named Kea group owns configuration files' ); +is( $gid, 200, 'configuration ownership is independent of the daemon primary GID' ); +isnt( $gid, $service_account->{gid}, 'configuration ownership does not inherit the daemon primary GID' ); +is_deeply( \@GROUP_LOOKUPS, ['kea'], 'named configuration groups are checked in preference order' ); +is_deeply( \@USER_LOOKUPS, [], 'configuration-group selection does not inspect service users' ); +is_deeply( \@GROUP_ID_LOOKUPS, [], 'configuration-group selection does not resolve a primary GID' ); + +set_nss( + groups => { + _kea => [ '_kea', 'x', 400, '' ], + }, +); +( $group, $gid ) = selected_config_group($backend); +is( $group, '_kea', 'the fallback named Kea group is selected when needed' ); +is( $gid, 400, 'the fallback named Kea group supplies configuration ownership' ); +is_deeply( \@GROUP_LOOKUPS, [ 'kea', '_kea' ], 'both named groups are checked before falling back' ); +is_deeply( \@USER_LOOKUPS, [], 'named-group fallback remains independent of service users' ); +is_deeply( \@GROUP_ID_LOOKUPS, [], 'named-group fallback remains independent of primary GIDs' ); + +set_nss( + users => { + kea => [ 'kea', 'x', 101, 300, '', '', '', '/var/empty', '/sbin/nologin' ], + }, + group_names => { 300 => 'daemon-primary' }, +); +( $group, $gid ) = selected_config_group($backend); +ok( !defined($group), 'no configuration group is selected when named Kea groups are absent' ); +ok( !defined($gid), 'no configuration GID is inherited from the service account' ); +is_deeply( \@GROUP_LOOKUPS, [ 'kea', '_kea' ], 'all named groups are checked before the public-mode fallback' ); +is_deeply( \@USER_LOOKUPS, [], 'the public-mode fallback does not inspect service users' ); +is_deeply( \@GROUP_ID_LOOKUPS, [], 'the public-mode fallback does not resolve a primary GID' ); + +SKIP: { + skip 'root privileges are required to verify file ownership and modes', 5 if $> != 0; + + set_nss( + users => { + _kea => [ '_kea', 'x', 100, 300, '', '', '', '/var/empty', '/sbin/nologin' ], + }, + groups => { + kea => [ 'kea', 'x', 200, '' ], + }, + group_names => { 300 => 'daemon-primary' }, + ); + my ( $named_fh, $named_path ) = tempfile(UNLINK => 1); + close($named_fh) or die "Unable to close $named_path: $!"; + my $named_result = apply_config_permissions( $backend, $named_path ); + ok( !$named_result->{error}, 'configuration permissions are applied with a named Kea group' ) + or diag $named_result->{error}; + is( ( stat $named_path )[5], 200, 'configuration file uses the named Kea group GID' ); + is( ( stat $named_path )[2] & 07777, 0640, 'configuration file is group-readable with a named Kea group' ); + + set_nss( + users => { + kea => [ 'kea', 'x', 101, 300, '', '', '', '/var/empty', '/sbin/nologin' ], + }, + group_names => { 300 => 'daemon-primary' }, + ); + my ( $public_fh, $public_path ) = tempfile(UNLINK => 1); + close($public_fh) or die "Unable to close $public_path: $!"; + my $public_result = apply_config_permissions( $backend, $public_path ); + ok( !$public_result->{error}, 'configuration permissions fall back without a named Kea group' ) + or diag $public_result->{error}; + is( ( stat $public_path )[2] & 07777, 0644, 'missing named Kea groups preserve the public-read fallback' ); +} + +done_testing(); + +sub set_nss { + my (%args) = @_; + + %TEST_USERS = %{ $args{users} || {} }; + %TEST_GROUPS = %{ $args{groups} || {} }; + %TEST_GROUP_NAMES = %{ $args{group_names} || {} }; + reset_lookups(); + + return; +} + +sub reset_lookups { + @USER_LOOKUPS = (); + @GROUP_LOOKUPS = (); + @GROUP_ID_LOOKUPS = (); + + return; +} + +sub selected_service_account { + my ($kea_backend) = @_; + + if ( my $resolver = $kea_backend->can('service_account') ) { + return $resolver->($kea_backend); + } + + my $legacy_resolver = xCAT::DHCP::Backend::Kea->can('_kea_user'); + my $name = $legacy_resolver ? $legacy_resolver->() : undef; + return unless defined $name; + my @entry = getpwnam($name); + return { + name => $entry[0], + uid => $entry[2], + gid => $entry[3], + }; +} + +sub selected_config_group { + my ($kea_backend) = @_; + + if ( my $resolver = xCAT::DHCP::Backend::Kea->can('_kea_group') ) { + return $resolver->(); + } + + my $resolver = $kea_backend->can('_service_group'); + return $resolver ? $resolver->($kea_backend) : undef; +} + +sub apply_config_permissions { + my ( $kea_backend, $path ) = @_; + + my $permissions = xCAT::DHCP::Backend::Kea->can('_set_config_permissions'); + return { error => 'Kea configuration-permission helper is unavailable' } unless $permissions; + return $permissions->($path) if xCAT::DHCP::Backend::Kea->can('_kea_group'); + return $permissions->( $kea_backend, $path ); +}