2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-26 00:26:40 +00:00
Files
xcat-core/xCAT-test/unit/credentials_service_node.t
T
2026-08-20 17:37:19 -03:00

494 lines
15 KiB
Perl

#!/usr/bin/env perl
## no critic (TestingAndDebugging::ProhibitNoStrict)
use strict;
use warnings;
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
BEGIN {
package xCAT::Table;
our $servicenode;
sub import { }
sub new { return bless {}, shift; }
sub getNodeAttribs { return { servicenode => $servicenode }; }
$INC{'xCAT/Table.pm'} = 1;
package xCAT::NodeRange;
our %ranges;
sub import {
no strict 'refs';
*{ caller() . '::noderange' } = \&noderange;
}
sub noderange {
my ($name) = @_;
return @{ $ranges{$name} || [] };
}
$INC{'xCAT/NodeRange.pm'} = 1;
package xCAT::Zone;
sub import { }
$INC{'xCAT/Zone.pm'} = 1;
package xCAT::Utils;
our $service_node;
sub import { }
sub isAIX { return 0; }
sub isServiceNode { return $service_node; }
$INC{'xCAT/Utils.pm'} = 1;
package xCAT::NetworkUtils;
our %addresses;
sub import { }
sub getipaddr {
my ($class, $endpoint) = @_;
return @{ $addresses{$endpoint} || [] };
}
$INC{'xCAT/NetworkUtils.pm'} = 1;
package xCAT::PasswordUtils;
sub import { }
$INC{'xCAT/PasswordUtils.pm'} = 1;
package xCAT::TableUtils;
our $master = '192.0.2.10';
sub import { }
sub get_site_attribute { return ($master); }
$INC{'xCAT/TableUtils.pm'} = 1;
package xCAT::MsgUtils;
our @messages;
sub import { }
sub trace { push @messages, [@_]; }
sub message {
push @messages, [@_];
my $callback = $_[3];
$callback->($_[2]) if ref($callback) eq 'CODE';
}
$INC{'xCAT/MsgUtils.pm'} = 1;
package xCAT::Client;
our @responses;
our ($exception, $request, $host, $trigger_timeout);
sub import { }
sub submit_request {
($request, my $callback) = @_;
$host = $ENV{XCATHOST};
die $exception if defined($exception);
$SIG{ALRM}->() if $trigger_timeout;
$callback->($_) for @responses;
}
$INC{'xCAT/Client.pm'} = 1;
package LWP;
sub import { }
$INC{'LWP.pm'} = 1;
package LWP::UserAgent;
sub new { return bless {}, shift; }
package HTTP::Request::Common;
sub import {
no strict 'refs';
*{ caller() . '::GET' } = sub { return $_[0]; };
}
$INC{'HTTP/Request/Common.pm'} = 1;
}
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
my $plugin = File::Spec->catfile(
$repo_root, qw(xCAT-server lib xcat plugins credentials.pm)
);
require $plugin;
sub reset_client {
@xCAT::Client::responses = ();
@xCAT::MsgUtils::messages = ();
$xCAT::Client::exception = undef;
$xCAT::Client::request = undef;
$xCAT::Client::host = undef;
$xCAT::Client::trigger_timeout = 0;
$xCAT::TableUtils::master = '192.0.2.10';
}
my $commands = xCAT_plugin::credentials::handled_commands();
is( $commands->{getcredentials}, 'credentials',
'credentials plugin still handles client requests' );
is( $commands->{signx509cert}, 'credentials',
'credentials plugin handles delegated signing' );
$xCAT::Table::servicenode = 'service-a, service-b.example.test';
%xCAT::NetworkUtils::addresses = (
'192.0.2.21' => ['192.0.2.21'],
'192.0.2.22' => ['192.0.2.22'],
'192.0.2.23' => ['192.0.2.23'],
'::ffff:192.0.2.22' => ['::ffff:192.0.2.22'],
'service-a' => ['192.0.2.21'],
'service-b' => ['192.0.2.22'],
'service-b-alias' => ['192.0.2.22'],
'service-b.example.test' => ['192.0.2.22'],
'service-c' => ['192.0.2.23'],
'service-c.example.test' => ['192.0.2.23'],
);
ok(
xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clienthost => ['service-a'],
_xcat_clientip => ['192.0.2.21'],
},
'compute-01'
),
'assigned authenticated service node may request a certificate'
);
ok(
xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clientfqdn => ['service-b.example.test'],
_xcat_clientip => ['192.0.2.22'],
},
'compute-01'
),
'assigned service node may match its FQDN'
);
ok(
xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clientip => ['192.0.2.22'],
},
'compute-01'
),
'numeric peer address works without reverse DNS'
);
ok(
xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clientip => ['::ffff:192.0.2.22'],
},
'compute-01'
),
'IPv4-mapped peer address matches the assigned IPv4 service node'
);
$xCAT::Table::servicenode = '192.0.2.22';
ok(
xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clienthost => ['service-b'],
_xcat_clientip => ['192.0.2.22'],
},
'compute-01'
),
'service node assignment may use an IP address'
);
$xCAT::Table::servicenode = 'service-b-alias';
ok(
xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clientip => ['192.0.2.22'],
},
'compute-01'
),
'service node assignment may use a resolvable alias'
);
$xCAT::Table::servicenode = 'service-b.example.test';
ok(
!xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clienthost => ['service-b'],
_xcat_clientip => ['192.0.2.23'],
},
'compute-01'
),
'reverse DNS name cannot override a different peer address'
);
ok(
!xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_authname => ['root'],
_xcat_clienthost => ['service-c'],
_xcat_clientfqdn => ['service-c.example.test'],
_xcat_clientip => ['192.0.2.23'],
},
'compute-01'
),
'resolved but unassigned service node is rejected'
);
ok(
!xCAT_plugin::credentials::_delegated_signer_allowed(
{
_xcat_clienthost => ['service-b'],
_xcat_clientip => ['192.0.2.22'],
},
'compute-01'
),
'unauthenticated request is rejected'
);
ok(
!xCAT_plugin::credentials::_delegated_signer_allowed(
{ _xcat_authname => ['root'] }, 'compute-01'
),
'authenticated request without a peer identity is rejected'
);
reset_client();
@xCAT::Client::responses = (
{
data => [
{
desc => ['x509cert'],
content => 'scalar certificate',
}
]
},
{ serverdone => [undef] },
);
my ($certificate, $error) =
xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
is( $certificate, 'scalar certificate',
'delegated signer accepts the serialized xCAT response shape' );
is( $error, undef, 'successful delegated request has no error' );
is( $xCAT::Client::host, '192.0.2.10:3001',
'delegated request targets site.master' );
is_deeply(
$xCAT::Client::request,
{
command => ['signx509cert'],
arg => ['compute-01'],
csr => ['certificate request'],
},
'delegated request carries the node and CSR'
);
reset_client();
$xCAT::TableUtils::master = '2001:db8::10';
@xCAT::Client::responses = (
{
data => [
{
desc => 'x509cert',
content => ['array certificate'],
}
]
},
);
($certificate, $error) = xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
is( $certificate, 'array certificate',
'delegated signer tolerates native array values' );
is( $xCAT::Client::host, '[2001:db8::10]:3001',
'IPv6 management address is bracketed' );
reset_client();
@xCAT::Client::responses = (
{ error => ['Delegated certificate request denied'], errorcode => [1] }
);
($certificate, $error) = xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
is( $certificate, undef, 'management node rejection returns no certificate' );
like( $error, qr/Delegated certificate request denied\z/,
'management node error is returned to the service node' );
like( $xCAT::MsgUtils::messages[-1]->[3],
qr/Delegated certificate request denied\z/,
'management node error is logged on the service node' );
reset_client();
$xCAT::Client::exception = "TLS handshake failed\n";
($certificate, $error) = xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
like( $error, qr/TLS handshake failed\z/,
'client exception is preserved in the delegated error' );
like( $xCAT::MsgUtils::messages[-1]->[3], qr/TLS handshake failed\z/,
'client exception is logged on the service node' );
reset_client();
$xCAT::Client::trigger_timeout = 1;
($certificate, $error) = xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
like( $error, qr/request timed out after 30 seconds\z/,
'stalled management node request is bounded by a timeout' );
reset_client();
($certificate, $error) = xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
like( $error, qr/management node returned no certificate\z/,
'empty management node response has a specific error' );
reset_client();
$xCAT::TableUtils::master = undef;
($certificate, $error) = xCAT_plugin::credentials::_request_x509_from_master(
'compute-01', 'certificate request'
);
is( $error, 'The management node is not configured',
'missing site.master has a specific error' );
$xCAT::Table::servicenode = 'service-a';
@xCAT::MsgUtils::messages = ();
my @callback;
{
no warnings 'redefine';
local *xCAT_plugin::credentials::_sign_x509_certificate = sub {
return 'signed certificate';
};
xCAT_plugin::credentials::process_request(
{
command => ['signx509cert'],
arg => ['compute-01'],
csr => ['certificate request'],
_xcat_authname => ['root'],
_xcat_clienthost => ['service-a'],
_xcat_clientip => ['192.0.2.21'],
},
sub { push @callback, shift; }
);
}
is_deeply(
\@callback,
[
{
data => [
{
content => ['signed certificate'],
desc => ['x509cert'],
}
]
}
],
'authorized delegated request returns only the signed certificate'
);
is_deeply(
$xCAT::MsgUtils::messages[-1],
[
'xCAT::MsgUtils',
0,
'I',
'credentials: signed certificate for compute-01 requested by 192.0.2.21',
],
'successful delegated signing records the node and service node address'
);
@callback = ();
xCAT_plugin::credentials::process_request(
{
command => ['signx509cert'],
arg => ['compute-01'],
csr => ['certificate request'],
_xcat_authname => ['root'],
_xcat_clienthost => ['service-c'],
_xcat_clientip => ['192.0.2.23'],
},
sub { push @callback, shift; }
);
is( $callback[0]->{errorcode}->[0], 1,
'unauthorized delegated request returns an error' );
%xCAT::NodeRange::ranges = ( 'compute-01' => ['compute-01'] );
$xCAT::Utils::service_node = 1;
@callback = ();
{
no warnings 'redefine';
local *xCAT_plugin::credentials::ok_with_node = sub { return 1; };
local *xCAT_plugin::credentials::_request_x509_from_master = sub {
return ('delegated certificate', undef);
};
xCAT_plugin::credentials::process_request(
{
command => ['getcredentials'],
arg => ['x509cert'],
csr => ['certificate request'],
callback_port => [123],
_xcat_clienthost => ['compute-01'],
},
sub { push @callback, shift; }
);
}
is( $callback[0]->{data}->[0]->{content}->[0], 'delegated certificate',
'service-node getcredentials path returns the delegated certificate' );
@callback = ();
{
no warnings 'redefine';
local *xCAT_plugin::credentials::ok_with_node = sub { return 1; };
local *xCAT_plugin::credentials::_request_x509_from_master = sub {
return (undef, 'management node rejected the request');
};
xCAT_plugin::credentials::process_request(
{
command => ['getcredentials'],
arg => ['x509cert'],
csr => ['certificate request'],
callback_port => [123],
_xcat_clienthost => ['compute-01'],
},
sub { push @callback, shift; }
);
}
is( $callback[0]->{error}->[0], 'management node rejected the request',
'service-node getcredentials path returns the delegated error' );
SKIP: {
my $openssl = 0;
if (open(my $version, '-|', 'openssl', 'version')) {
<$version>;
$openssl = close($version);
}
skip 'openssl is not available', 3 unless $openssl;
my $directory = tempdir(CLEANUP => 1);
my $key = File::Spec->catfile($directory, 'test.key');
open(my $saved_stderr, '>&', \*STDERR) or die "cannot save stderr";
open(STDERR, '>', File::Spec->devnull()) or die "cannot redirect stderr";
my $key_status = system('openssl', 'genrsa', '-out', $key, '2048');
open(STDERR, '>&', $saved_stderr) or die "cannot restore stderr";
close($saved_stderr);
$key_status == 0 or die "cannot create test key";
my @subjects = (
[ valid => '/CN=compute-01' ],
[ multiple => '/CN=root/CN=compute-01' ],
[ extra => '/CN=compute-01/OU=cluster' ],
);
my %requests;
foreach my $subject (@subjects) {
my ($name, $distinguished_name) = @{$subject};
my $request = File::Spec->catfile($directory, "$name.csr");
system('openssl', 'req', '-new', '-key', $key,
'-subj', $distinguished_name, '-out', $request) == 0
or die "cannot create test CSR";
$requests{$name} = $request;
}
ok( xCAT_plugin::credentials::_csr_subject_matches_node(
$requests{valid}, 'compute-01'
),
'single exact CN is accepted' );
ok( !xCAT_plugin::credentials::_csr_subject_matches_node(
$requests{multiple}, 'compute-01'
),
'multiple CN values are rejected' );
ok( !xCAT_plugin::credentials::_csr_subject_matches_node(
$requests{extra}, 'compute-01'
),
'additional subject attributes are rejected' );
}
done_testing();