2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-01 02:29:41 +00:00
Files
xcat-core/xCAT-server/lib/xcat/plugins/makentp.pm
T
Vinícius Ferrão bd260c9feb fix: harden makentp generated ntp.conf
The ntpd config generated by makentp had no access restrictions,
explicitly disabled authentication, and was vulnerable to CVE-2013-5211
amplification attacks.

Add restrict lines to block unauthorized access, add iburst for faster
initial sync, replace "disable auth" with "disable monitor". Security
hardening applies to all platforms including AIX (xntpd supports the
same restrict directives).

The chrony path (EL8+) is unaffected — it delegates to setupntp.

Fixes #2435
2026-04-26 16:19:32 -03:00

512 lines
15 KiB
Perl

#!/usr/bin/env perl
# IBM(c) 2015 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::makentp;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use Getopt::Long;
use xCAT::Usage;
use xCAT::NetworkUtils;
use xCAT::TableUtils;
use xCAT::Utils;
use XML::Simple;
no strict;
use Data::Dumper;
use Socket;
my %globalopt;
#-------------------------------------------------------------------------------
=head1 xCAT_plugin:makentp
=head2 Package Description
Handles ntp server setup on a xCAT management node.
=cut
#-------------------------------------------------------------------------------
#--------------------------------------------------------------------------------
=head3 send_msg
Invokes the callback with the specified message
Arguments:
request: request structure for plguin calls
ecode: error code. 0 for succeful.
msg: messages to be displayed.
Returns:
none
=cut
#--------------------------------------------------------------------------------
sub send_msg {
my $request = shift;
my $ecode = shift;
my $msg = shift;
my %output;
# Called from child process - send to parent
if (exists($request->{pipe})) {
my $out = $request->{pipe};
$output{errorcode} = $ecode;
$output{data} = \@_;
print $out freeze([ \%output ]);
print $out "\nENDOFFREEZE6sK4ci\n";
}
# Called from parent - invoke callback directly
elsif (exists($request->{callback})) {
my $callback = $request->{callback};
$output{errorcode} = $ecode;
$output{data}->[0] = $msg;
$callback->(\%output);
}
}
#--------------------------------------------------------------------------------
=head3 handled_commands
It returns a list of commands handled by this plugin.
Arguments:
none
Returns:
a list of commands.
=cut
#--------------------------------------------------------------------------------
sub handled_commands {
return ({ makentp => "makentp" });
}
#--------------------------------------------------------------------------------
=head3 parse_args
Parse the command line options and operands.
Arguments:
request: the request structure for plugin
Returns:
Usage string or error message.
0 if no user promp needed.
=cut
#--------------------------------------------------------------------------------
sub parse_args {
my $request = shift;
my $args = $request->{arg};
my $cmd = $request->{command};
my %opt;
# Responds with usage statement
local *usage = sub {
my $usage_string = xCAT::Usage->getUsage($cmd);
send_msg($request, 0, " $usage_string");
return ([ $_[0], $usage_string ]);
};
# No command-line arguments - use defaults
if (!defined($args)) {
return (0);
}
# Checks case in GetOptions, allows opts
# to be grouped (e.g. -vx), and terminates
# at the first unrecognized option.
@ARGV = @$args;
$Getopt::Long::ignorecase = 0;
Getopt::Long::Configure("bundling");
Getopt::Long::Configure("no_pass_through");
# Process command-line flags
if (!GetOptions(\%opt,
qw(h|help V|verbose v|version a|all))) {
return (usage());
}
# Option -V for verbose output
if (exists($opt{V})) {
$globalopt{verbose} = 1;
}
if (exists($opt{a})) {
$globalopt{a} = 1;
}
return;
}
#--------------------------------------------------------------------------------
=head3 preprocess_request
Parse the arguments and display the usage or the version string.
=cut
#--------------------------------------------------------------------------------
sub preprocess_request {
my $req = shift;
if ($req->{_xcatpreprocessed}->[0] == 1) { return [$req]; }
my $callback = shift;
my $command = $req->{command}->[0];
my $extrargs = $req->{arg};
my @exargs = ($req->{arg});
if (ref($extrargs)) {
@exargs = @$extrargs;
}
# Build hash to pass around
my %request;
$request{arg} = $extrargs;
$request{callback} = $callback;
$request{command} = $command;
my $usage_string = xCAT::Usage->parseCommand($command, @exargs);
if ($usage_string) {
$callback->({ data => [$usage_string] });
$req = {};
return;
}
my $result = parse_args(\%request);
if (ref($result) eq 'ARRAY') {
send_msg(\%request, 1, @$result);
return (1);
}
# add current request
my @result = ();
my $reqcopy = {%$req};
$reqcopy->{_xcatpreprocessed}->[0] = 1;
if (xCAT::Utils->isMN() && exists($globalopt{a})) {
$reqcopy->{_all}->[0] = 1;
}
if (exists($globalopt{verbose})) {
$reqcopy->{_verbose}->[0] = 1;
}
push @result, $reqcopy;
return \@result;
}
#--------------------------------------------------------------------------------
=head3 process_request
Pasrse the arguments and call the correspondent functions
to do switch discovery.
=cut
#--------------------------------------------------------------------------------
sub process_request {
my $req = shift;
my $callback = shift;
my $sub_req = shift;
# Build hash to pass around
my %request;
$request{arg} = $req->{arg};
$request{callback} = $callback;
$request{command} = $req->{command}->[0];
my $verbose;
if ($req->{_verbose}->[0] == 1) {
$verbose = 1;
}
my @nodeinfo = xCAT::NetworkUtils->determinehostname();
my $nodename = pop @nodeinfo;
if (xCAT::Utils->isMN()) {
send_msg(\%request, 0, "configuring management node: $nodename.");
} else {
send_msg(\%request, 0, "configuring service node: $nodename.");
}
# get site.extntpservers for mn, for sn use mn as the server
my $ntp_servers;
my $ntp_master;
my $ntp_attrib;
if (xCAT::Utils->isMN()) {
$ntp_attrib = "extntpservers";
} else {
$ntp_attrib = "ntpservers";
}
my @entries = xCAT::TableUtils->get_site_attribute($ntp_attrib);
my $ntp_servers = $entries[0];
if (!xCAT::Utils->isMN() && ((!$ntp_servers) ||
(($ntp_servers) && ($ntp_servers =~ /<xcatmaster>/)))) {
my $retdata = xCAT::ServiceNodeUtils->readSNInfo($nodename);
$ntp_servers = $retdata->{'master'};
}
# Handle chronyd here,
if (-x "/usr/sbin/chronyd" &&
(-x "/usr/bin/systemctl" || -x "/bin/systemctl")) {
send_msg(\%request, 0, "Will configure chronyd instead.");
my $cmd = "/install/postscripts/setupntp " .
join(' ', split(',', $ntp_servers));
send_msg(\%request, 0, "Calling ... " . $cmd);
my $result = xCAT::Utils->runcmd($cmd, 0);
if ($::RUNCMD_RC != 0) {
send_msg(\%request, 1, "Error from command: $cmd\n $result");
return 1;
}
send_msg(\%request, 0, "Daemon chronyd configured.");
# Cannot find a better way other than use goto statement :-/
goto HANDLE_MAKENTP_A;
}
#check if ntp is installed or not
if ($verbose) {
send_msg(\%request, 0, " ...checking if nptd is installed.");
}
if (!-f "/usr/sbin/ntpd") {
send_msg(\%request, 1, "Please make sure ntpd is installed on $nodename.");
return 1;
}
#configure the ntp configuration file
if ($verbose) {
send_msg(\%request, 0, " ...backing up the ntp configuration file /etc/ntp.conf.");
}
my $ntpcfg = "/etc/ntp.conf";
my $ntpcfgbackup = "/etc/ntp.conf.orig";
my $ntpxcatcfgbackup = "/etc/ntp.conf.xcatbackup";
if (-e $ntpcfg) {
if (!-e $ntpcfgbackup) {
# if original backup does not already exist
my $cmd = "mv $ntpcfg $ntpcfgbackup";
my $result = xCAT::Utils->runcmd($cmd, 0);
if ($::RUNCMD_RC != 0) {
send_msg(\%request, 1, "Error from command:$cmd\n $result");
return 1;
}
}
else {
# backup xcat cfg
my $cmd = "rm $ntpxcatcfgbackup;mv $ntpcfg $ntpxcatcfgbackup";
my $result = xCAT::Utils->runcmd($cmd, 0);
if ($::RUNCMD_RC != 0) {
send_msg(\%request, 1, "Error from command:$cmd\n $result.");
return 1;
}
}
}
if ($verbose) {
send_msg(\%request, 0, " ...changing the ntp configuration file /etc/ntp.conf.\n ntp servers are: $ntp_servers");
}
# create ntp server config file
open(CFGFILE, ">$ntpcfg")
or xCAT::MsgUtils->message('SE',
"Cannot open $ntpcfg for NTP update. \n");
print CFGFILE "restrict default nomodify notrap nopeer noquery\n";
print CFGFILE "restrict 127.0.0.1\n";
print CFGFILE "restrict ::1\n\n";
if (defined($ntp_servers) && $ntp_servers) {
my @npt_server_array = split(',', $ntp_servers);
# add ntp servers one by one
foreach my $ntps (@npt_server_array) {
if (!$ntp_master) { $ntp_master = $ntps; }
print CFGFILE "server $ntps iburst\n";
}
}
my $os = xCAT::Utils->osver("all");
#for sles, /var/lib/ntp/drift is a dir
if (xCAT::Utils->isAIX()) {
print CFGFILE "driftfile /etc/ntp.drift\n";
print CFGFILE "tracefile /etc/ntp.trace\n";
print CFGFILE "broadcastclient\n";
} elsif ($os =~ /sles/) {
print CFGFILE "driftfile /var/lib/ntp/drift/ntp.drift\n";
} else {
print CFGFILE "driftfile /var/lib/ntp/drift\n";
}
#add xCAT mn/sn itself as a server
print CFGFILE "server 127.127.1.0\n";
print CFGFILE "fudge 127.127.1.0 stratum 10\n";
print CFGFILE "disable monitor\n";
close CFGFILE;
my $ntp_service = "ntpserver";
#stop ntpd
if ($verbose) {
send_msg(\%request, 0, " ...stopping $ntp_service");
}
my $rc = xCAT::Utils->stopservice($ntp_service);
if ($rc != 0) {
send_msg(\%request, 1, "Failed to stop nptd on $nodename.");
return 1;
}
#update the time now
if ($ntp_master) {
my $cmd;
if ($os =~ /sles/) {
if (-f "/usr/sbin/rcntpd") {
$cmd = "/usr/sbin/rcntpd ntptimeset";
} elsif (-f "/usr/sbin/rcntp") {
$cmd = "/usr/sbin/rcntp ntptimeset";
} else {
$cmd = "sntp -P no -r $ntp_master";
}
} else {
$cmd = "ntpdate -t5 $ntp_master";
}
if ($verbose) {
send_msg(\%request, 0, " ...updating the time now. $cmd");
}
my $result = xCAT::Utils->runcmd($cmd, 0);
if ($verbose) {
send_msg(\%request, 0, " $result");
}
if ($::RUNCMD_RC != 0) {
send_msg(\%request, 1, "Error from command $cmd\n $result.");
send_msg(\%request, 1, "Please check $ntp_master, make sure time is synced (can be validated by 'ntpq -p'), then rerun makentp command again ");
return 1;
}
}
#setup the hardware clock
my $hwcmd = "/sbin/hwclock --systohc --utc";
if ($verbose) {
send_msg(\%request, 0, " ...updating the hwclock now. $hwcmd");
}
my $hwresult = xCAT::Utils->runcmd($hwcmd, 0);
if ($verbose) {
send_msg(\%request, 0, " $hwresult");
}
if ($::RUNCMD_RC != 0) {
send_msg(\%request, 1, "Error from command $hwcmd\n $hwresult.");
return 1;
}
my $grep_cmd;
my $rc;
#setup the RTC is UTC format, which will be used by os
if ($os =~ /sles/) {
$grep_cmd = "grep -i HWCLOCK /etc/sysconfig/clock";
$rc = xCAT::Utils->runcmd($grep_cmd, 0);
if ($::RUNCMD_RC == 0) {
`sed -i 's/.*HWCLOCK.*/HWCLOCK=\"-u\"/' /etc/sysconfig/clock`;
} else {
`echo HWCLOCK=\"-u\" >> /etc/sysconfig/clock`;
}
} elsif (-f "/etc/debian_version") {
`sed -i 's/.*UTC.*/UTC=\"yes\"/' /etc/default/rcS`;
} else {
if (-f "/etc/sysconfig/clock") {
$grep_cmd = "grep -i utc /etc/sysconfig/clock";
$rc = xCAT::Utils->runcmd($grep_cmd, 0);
if ($::RUNCMD_RC == 0) {
`sed -i 's/.*UTC.*/UTC=\"yes\"/' /etc/sysconfig/clock`;
} else {
`echo UTC=\"yes\" >> /etc/sysconfig/clock`;
}
} else {
`type -P timedatectl >/dev/null 2>&1`;
`timedatectl set-local-rtc 0`;
}
}
#update the hardware clock automaticly
if (-f "/etc/sysconfig/ntpd") {
$grep_cmd = "grep -i SYNC_HWCLOCK /etc/sysconfig/ntpd";
$rc = xCAT::Utils->runcmd($grep_cmd, 0);
if ($::RUNCMD_RC == 0) {
`sed -i 's/.*SYNC_HWCLOCK.*/SYNC_HWCLOCK=\"yes\"/' /etc/sysconfig/ntpd`;
} else {
`echo SYNC_HWCLOCK=\"yes\" >> /etc/sysconfig/ntpd`;
}
} elsif (-f "/etc/sysconfig/ntp") {
`sed -i 's/.*SYNC_HWCLOCK.*/NTPD_FORCE_SYNC_HWCLOCK_ON_STARTUP=\"yes\"/' /etc/sysconfig/ntp`;
`sed -i 's/^NTPD_FORCE_SYNC_ON.*/NTPD_FORCE_SYNC_ON_STARTUP=\"yes\"/' /etc/sysconfig/ntp`;
`sed -i 's/.*RUN_CHROOTED.*/NTPD_RUN_CHROOTED=\"yes\"/' /etc/sysconfig/ntp`;
} else {
my $cron_file = "/etc/cron.daily/xcatsethwclock";
if (!-f "$cron_file") {
`echo "#!/bin/sh" > $cron_file`;
`echo "/sbin/hwclock --systohc --utc" >> $cron_file`;
`chmod a+x $cron_file`;
#service cron restart
xCAT::Utils->startservice("cron");
}
}
#start ntpd
if ($verbose) {
send_msg(\%request, 0, " ...starting $ntp_service");
}
my $rc = xCAT::Utils->startservice($ntp_service);
if ($rc != 0) {
send_msg(\%request, 1, "Failed to start nptd on $nodename.");
return 1;
}
#enable ntpd for node reboot
if ($verbose) {
send_msg(\%request, 0, " ...enabling $ntp_service");
}
xCAT::Utils->enableservice($ntp_service);
HANDLE_MAKENTP_A:
#now handle sn that has ntpserver=1 set in servicenode table.
# this part is called by makentp -a.
if ($req->{_all}->[0] == 1) {
my @servicenodes = xCAT::ServiceNodeUtils->getSNList('ntpserver');
if (@servicenodes > 0) {
send_msg(\%request, 0, "configuring service nodes: @servicenodes");
my $ret =
xCAT::Utils->runxcmd(
{
command => ['updatenode'],
node => \@servicenodes,
arg => [ "-P", "setupntp" ],
},
$sub_req, -1, 1
);
my $retcode=$::RUNCMD_RC;
my $msg;
foreach my $line (@$ret) {
$msg .= "$line\n";
}
send_msg(\%request, $retcode, "$msg");
}
}
return;
}
1;