diff --git a/perl-xCAT/xCAT/RemoteShellExp.pm b/perl-xCAT/xCAT/RemoteShellExp.pm new file mode 100755 index 000000000..12fb63053 --- /dev/null +++ b/perl-xCAT/xCAT/RemoteShellExp.pm @@ -0,0 +1,887 @@ +#!/usr/bin/env perl +# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html +# + +package xCAT::RemoteShellExp; + +#----------------------------------------------------------------------------- + +=head1 RemoteShellExp + Uses perl Expect to set up ssh passwordless login on the input node list + Called from xdsh -K command + It works for node and devices ( such as QLogic Switch). + See man xdsh. + It works for root and non-root userids. + + Environment Variables input to drive the setup: + + DSH_REMOTE_CMD set to path to remote shell (ssh) + root password must agree on all the nodes + + XCAT_ROOT set to root of xCAT install + + DSH_REMOTE_PASSWORD - to_user password for -s option required to sendkeys) + Note this is obtained in the xdsh client frontend. + + SSH_SETUP_COMMAND - Command to be sent to the IB switch to setup SSH. + + DSH_FROM_USERID_HOME - The home directory of the userid from + where the ssh keys will be obtained + to send + + DSH_FROM_USERID - The userid from where the ssh keys will be obtained + to send + to the node, or generated and then obtained to send to the + node. + DSH_TO_USERID - The userid on the node where the ssh keys will be updated. + DSH_ENABLE_SSH - Node to node root passwordless ssh will be setup. + + + Usage: remoteshell.expect + [-t node list] test ssh connection to the node + [-k] Generates the ssh keys needed , for the user on the MN. + [-s node list] copies the ssh keys to the nodes + + exit 0 - good + exit 1 - abort + exit 2 - usage error + +Examples: +$rc=xCAT::RemoteShellExp->remoteshellexp("k",$callback,$remoteshellcmd); +$rc=xCAT::RemoteShellExp->remoteshellexp("s",$callback,$remoteshellcmd,$nodes); +$rc=xCAT::RemoteShellExp->remoteshellexp("t",$callback,$remoteshellcmd,$nodes); + +=cut + +BEGIN +{ + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; + $::XCATDIR = $ENV{'XCATDIR'} ? $ENV{'XCATDIR'} : '/etc/xcat'; +} + + +use lib "$::XCATROOT/lib/perl"; +use xCAT::Utils; +use Getopt::Long; +use xCAT::MsgUtils; +use Expect; +use strict; + +#----------------------------------------------------------------------------- +sub remoteshellexp +{ + my ($class, $flag, $callback, $remoteshell, $nodes) = @_; + my $rc=0; + $::CALLBACK = $callback; + if (!($flag)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "No flag provide to remoteshellexp."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 2); + return 2; + } + + if (($flag ne "k") && ($flag ne "t") && ($flag ne "s")) { + my $rsp = {}; + $rsp->{data}->[0] = + "Invalid flag $flag provided."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 2; + + } + + # for -s flag must have nodes and a $to_userid password + my $to_user_password; + if ($ENV{'DSH_REMOTE_PASSWORD'}) { + $to_user_password=$ENV{'DSH_REMOTE_PASSWORD'}; + } + if ($flag eq "s"){ + if (!$to_user_password) { + my $rsp = {}; + $rsp->{data}->[0] = + "The DSH_REMOTE_PASSWORD environment variable has not been set to the user id password on the node which will have their ssh keys updated (ususally root)."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 2; + } + if (!$nodes) { + my $rsp = {}; + $rsp->{data}->[0] = + "No nodes were input to update the user's ssh keys."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 2; + } + } + my $ssh_setup_cmd; + my $from_userid; + my $to_userid; + my $home; + my $remotecopy; + # if caller input a path to ssh remote command, use it + if ($ENV{'DSH_REMOTE_CMD'}) { + $remoteshell=$ENV{'DSH_REMOTE_CMD'}; + } else { + if (!$remoteshell) { + $remoteshell="/usr/bin/ssh"; + } + } + # figure out path to scp + my ($path,$ssh) = split(/ssh/,$remoteshell); + $remotecopy=$path . "scp"; + # if caller input the ssh setup command (such as for IB Switch) + if ($ENV{'SSH_SETUP_COMMAND'}) { + $ssh_setup_cmd=$ENV{'SSH_SETUP_COMMAND'}; + } + # set User on the Management node that has the ssh keys + # this id can be a local (non-root) id as well as root + if ($ENV{'DSH_FROM_USERID'}) { + $from_userid=$ENV{'DSH_FROM_USERID'}; + } else { + $from_userid="root"; + } + # set User on the node where we will send the keys + # this id can be a local id as well as root + if ($ENV{'DSH_TO_USERID'}) { + $to_userid=$ENV{'DSH_TO_USERID'}; + } else { + $to_userid="root"; + } + # set User home directory to find the ssh public key to send + # For non-root ids information may not be in /etc/passwd + # but elsewhere like LDAP + if ($ENV{'DSH_FROM_USERID_HOME'}) { + $home=$ENV{'DSH_FROM_USERID_HOME'}; + } else { + $home=xCAT::Utils->getHomeDir($from_userid); + } + + # This indicates we will generate new ssh keys for the user, + # if they are not already there + my $key="$home/.ssh/id_rsa"; + my $key2="$home/.ssh/id_rsa.pub"; + # Check to see if empty + if (-z $key) { + my $rsp = {}; + $rsp->{data}->[0] = + "The $key file is empty. Remove it and rerun the command."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 1; + + } + if (-z $key2) { + my $rsp = {}; + $rsp->{data}->[0] = + "The $key2 file is empty. Remove it and rerun the command."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 1; + + } + if (($flag eq "k") && (!(-e $key))) + { + # if the file size of the id_rsa key is 0, tell them to remove it + # and run the command again + $rc=xCAT::RemoteShellExp->gensshkeys; + } + # send ssh keys to the nodes/devices, to setup passwordless ssh + if ($flag eq "s") + { + if (!($nodes)) { + my $rsp = {}; + $rsp->{data}->[0] = + "There are no nodes defined to update the ssh keys."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 1; + } + if ($ssh_setup_cmd) { # setup ssh on devices + $rc=xCAT::RemoteShellExp->senddeviceskeys($remoteshell,$remotecopy,$to_userid,$to_user_password,$home,$ssh_setup_cmd,$nodes); + } else { #setup ssh on nodes + $rc=xCAT::RemoteShellExp->sendnodeskeys($remoteshell,$remotecopy,$to_userid,$to_user_password,$home,$ssh_setup_cmd,$nodes); + } + } + # test ssh setup on the node + if ($flag eq "t") + { + $rc=xCAT::RemoteShellExp->testkeys($remoteshell,$to_userid,$nodes); + } + return $rc; +} + +#----------------------------------------------------------------------------- + +=head3 gensshkeys + + Generates new ssh keys for the input userid on the MN, if they do not + already exist. Test for id_rsa key existence. + +=cut + +#----------------------------------------------------------------------------- + +sub gensshkeys + +{ + my ($class) = @_; + my $keygen; + my $timeout = 10; # sets Expect default timeout, 0 accepts immediately + my $keygen_sent = 0; + my $prompt1 = 'Generating public/private rsa'; + my $prompt2 = 'Enter file.*:'; + my $prompt3 = 'Enter passphrase.*:'; + my $prompt4 = 'Enter same passphrase.*:'; + my $expect_log = undef; + my $debug = 0; + if ($::VERBOSE) + { + $debug = 1; + } + $keygen = new Expect; + + # run /usr/bin/ssh-keygen -t rsa + # prompt1 = 'Generating public/private rsa'; + # prompt2 = 'Enter file.*:'; + # -re "\r" + # prompt3 = 'Enter passphrase.*:'; + # -re "\r" + # prompt4 = 'Enter same passphrase.*:'; + # -re "\r" + + + # disable command echoing + #$keygen->slave->stty(qw(sane -echo)); + + # + # exp_internal(1) sets exp_internal debugging + # to STDERR. + # + #$keygen->exp_internal(1); + $keygen->exp_internal($debug); + + # + # log_stdout(0) prevent the program's output from being shown. + # turn on if debugging error + #$keygen->log_stdout(1); + $keygen->log_stdout($debug); + + # Run the ssh key gen command + my $spawncmd = "/usr/bin/ssh-keygen -t rsa"; + unless ($keygen->spawn($spawncmd)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "Unable to run $spawncmd."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 1; + + } + + # + #ssh-keygen prompts starts here + # + + my @result = $keygen->expect( + $timeout, + [ + $prompt1, # Generating public/private rsa + sub { + $keygen->send("\r"); + $keygen->clear_accum(); + $keygen->exp_continue(); + } + ], + [ + $prompt2, # Enter file.*: + sub { + $keygen->send("\r"); + $keygen->clear_accum(); + $keygen->exp_continue(); + } + ], + [ + $prompt3, # Enter passphrase.* + sub { + $keygen->send("\r"); + $keygen->clear_accum(); + $keygen->exp_continue(); + } + ], + [ + $prompt4, # Enter same passphrase. + sub { + $keygen->send("\r"); + $keygen->clear_accum(); + $keygen->exp_continue(); + } + ] + ); # end prompts + ########################################## + # Expect error - report and quit + ########################################## + if (defined($result[1])) + { + my $msg = $result[1]; + $keygen->soft_close(); + if ($msg =~ /status 0/i) { # no error + return 0; + } else { + my $rsp = {}; + $rsp->{data}->[0] = $msg; + xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); + return 1; + } + + } else { + $keygen->soft_close(); + return 0; + } +} +#----------------------------------------------------------------------------- + +=head3 testkeys + + Test to see if the remoteshell setup worked + +=cut + +#----------------------------------------------------------------------------- + +sub testkeys + +{ + my ($class,$remoteshell,$to_userid,$nodes) = @_; + my $testkeys; + my $timeout = 10; # sets Expect default timeout, 0 accepts immediately + my $testkeys_sent = 0; + my $prompt1 = 'Are you sure you want to continue connecting (yes/no)?'; + my $prompt2 = 'ssword:'; + my $prompt3 = 'Permission denied*'; + my $prompt4 = 'test.success'; + my $expect_log = undef; + my $debug = 0; + my $rc=1; # default to error + if ($::VERBOSE) + { + $debug = 1; + } + $testkeys = new Expect; + + # run ssh -l to_userid echo test.success + # possible return + # bad + ## Are you sure you want to continue connecting (yes/no)? + ## *ssword* + ## Permission denied. + # Good + ## test.success + + # disable command echoing + #$testkeys->slave->stty(qw(sane -echo)); + + # + # exp_internal(1) sets exp_internal debugging + # to STDERR. + # + #$testkeys->exp_internal(1); + $testkeys->exp_internal($debug); + + # + # log_stdout(0) prevent the program's output from being shown. + # turn on if debugging error + #$testkeys->log_stdout(1); + $testkeys->log_stdout($debug); + + # Run the ssh key gen command + my $spawncmd = "$remoteshell $nodes -l $to_userid echo test.success"; + unless ($testkeys->spawn($spawncmd)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "Unable to run $spawncmd."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + return 1; + + } + + # + #testkeys prompts starts here + # + + my @result = $testkeys->expect( + $timeout, + [ + $prompt1, # Are you sure you want to ... + sub { + $rc= 1; + $testkeys->hard_close(); + } + ], + [ + $prompt2, # *ssword* + sub { + $rc= 1; + $testkeys->hard_close(); + } + ], + [ + $prompt3, # Permission denied + sub { + $rc= 1; + $testkeys->hard_close(); + } + ], + [ + $prompt4, # test.success + sub { + $rc= 0; + } + ] + ); # end prompts + ########################################## + # Expect error - report and quit + ########################################## + if (defined($result[1])) + { + my $msg = $result[1]; + $testkeys->soft_close(); + if ($msg =~ /status 0/i) { # no error + return 0; + } else { + my $rsp = {}; + $rsp->{data}->[0] = $msg; + xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); + return 1; + } + + } else { + $testkeys->soft_close(); + return $rc; + } +} +#------------------------------------------------------------------------------- + +=head3 sendnodeskeys + + Setup the ssh keys on the nodes + +=cut + +#----------------------------------------------------------------------------- + +sub sendnodeskeys + +{ + my ($class,$remoteshell,$remotecopy,$to_userid,$to_userpassword,$home,$nodes) = @_; + my $sendkeys; + my $timeout = 10; # sets Expect default timeout, 0 accepts immediately + my $sendkeys_sent = 0; + my $prompt1 = 'Are you sure you want to continue connecting (yes/no)?'; + my $prompt2 = 'ssword:'; + my $prompt3 = 'Permission denied*'; + my $expect_log = undef; + my $debug = 0; + my $rc=0; + if ($::VERBOSE) + { + $debug = 1; + } + # For each node + # make a temporary directory on the node + # run scp -l /bin/mkdir -p /tmp/$to_userid/.ssh + # xdsh has built an authorized_keys file for the node + # in $HOME/.ssh/tmp/authorized_keys + # copy to the node to the temp directory + # scp $HOME/.ssh/tmp/authorized_keys to_userid@:/tmp/$to_userid/.ssh + # If you are going to enable ssh to ssh between nodes, then + # scp $HOME/.ssh/id_rsa to that temp directory on the node + # copy the script $HOME/.ssh/copy.sh to the node, it will do the + # the work of setting up the user's ssh keys and clean up + # ssh (run) copy.sh on the node + my @nodelist=split(/,/,$nodes); + foreach my $node (@nodelist) { + $sendkeys = new Expect; + + # disable command echoing + #$sendkeys->slave->stty(qw(sane -echo)); + # + # exp_internal(1) sets exp_internal debugging + # to STDERR. + # + #$sendkeys->exp_internal(1); + $sendkeys->exp_internal($debug); + # + # log_stdout(0) prevent the program's output from being shown. + # turn on if debugging error + #$sendkeys->log_stdout(1); + $sendkeys->log_stdout($debug); + + # command to make the temp directory on the node + my $spawnmkdir= + "$remoteshell $node -l $to_userid /bin/mkdir -p /tmp/$to_userid/.ssh"; + # command to copy the needed files to the node + + # send mkdir command + unless ($sendkeys->spawn($spawnmkdir)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "Unable to run $spawnmkdir."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + next; # go to next node + + } + + # + #mkdir prompts starts here + # + + my @result = $sendkeys->expect( + $timeout, + [ + $prompt1, # Are you sure you want to ... + sub { + $sendkeys->send("yes\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt2, # *ssword* + sub { + $sendkeys->send("$to_userpassword\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt3, # Permission denied + sub { + $rc= 1; + $sendkeys->soft_close(); + next; # go to next node + } + ], + ); # end prompts + ########################################## + # Expect error - report + ########################################## + if (defined($result[1])) + { + my $msg = $result[1]; + if ($msg =~ /status 0/i) { # no error + $rc=0; + } else { + my $rsp = {}; + $rsp->{data}->[0] = "$node has error,$msg"; + xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); + $rc=1; + next; # go to next node + } + } + $sendkeys->soft_close(); + + # + #copy files prompts starts here + # + + $sendkeys = new Expect; + + # disable command echoing + #$sendkeys->slave->stty(qw(sane -echo)); + # + # exp_internal(1) sets exp_internal debugging + # to STDERR. + # + #$sendkeys->exp_internal(1); + $sendkeys->exp_internal($debug); + # + # log_stdout(0) prevent the program's output from being shown. + # turn on if debugging error + #$sendkeys->log_stdout(1); + $sendkeys->log_stdout($debug); + + my $spawncopyfiles; + if ($ENV{'DSH_ENABLE_SSH'}) { # we will enable node to node ssh + $spawncopyfiles= + "$remotecopy $home/.ssh/id_rsa $home/.ssh/copy.sh $home/.ssh/tmp/authorized_keys $to_userid\@$node:/tmp/$to_userid/.ssh "; + + } else { # no node to node ssh ( don't send private key) + $spawncopyfiles= + "$remotecopy $home/.ssh/copy.sh $home/.ssh/tmp/authorized_keys $to_userid\@$node:/tmp/$to_userid/.ssh "; + } + # send copy command + unless ($sendkeys->spawn($spawncopyfiles)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "Unable to run $spawncopyfiles."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + next; # go to next node + + } + + my @result = $sendkeys->expect( + $timeout, + [ + $prompt1, # Are you sure you want to ... + sub { + $sendkeys->send("yes\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt2, # *ssword* + sub { + $sendkeys->send("$to_userpassword\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt3, # Permission denied + sub { + $rc= 1; + $sendkeys->soft_close(); + next; # go to next node + + } + ], + ); # end prompts + ########################################## + # Expect error - report + ########################################## + if (defined($result[1])) + { + my $msg = $result[1]; + if ($msg =~ /status 0/i) { # no error + $rc=0; + } else { + my $rsp = {}; + $rsp->{data}->[0] = "$node has error,$msg"; + xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); + $rc=1; + next; # go to next node + } + } + $sendkeys->soft_close(); + + # + # ssh to the node to run the copy.sh to setup the keys starts here + # + $sendkeys = new Expect; + + # disable command echoing + #$sendkeys->slave->stty(qw(sane -echo)); + # + # exp_internal(1) sets exp_internal debugging + # to STDERR. + # + #$sendkeys->exp_internal(1); + $sendkeys->exp_internal($debug); + # + # log_stdout(0) prevent the program's output from being shown. + # turn on if debugging error + #$sendkeys->log_stdout(1); + $sendkeys->log_stdout($debug); + + # command to run copy.sh + my $spawnruncopy= + "$remoteshell $node -l $to_userid /tmp/$to_userid/.ssh/copy.sh"; + + # send mkdir command + unless ($sendkeys->spawn($spawnruncopy)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "Unable to run $spawnruncopy."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + next; # go to next node + + } + + # + #run copy.sh prompts starts here + # + + my @result = $sendkeys->expect( + $timeout, + [ + $prompt1, # Are you sure you want to ... + sub { + $sendkeys->send("yes\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt2, # *ssword* + sub { + $sendkeys->send("$to_userpassword\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt3, # Permission denied + sub { + $rc= 1; + $sendkeys->soft_close(); + next; # go to next node + } + ], + ); # end prompts + ########################################## + # Expect error - report + ########################################## + if (defined($result[1])) + { + my $msg = $result[1]; + if ($msg =~ /status 0/i) { # no error + $rc=0; + } else { + my $rsp = {}; + $rsp->{data}->[0] = "$node has error,$msg"; + xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); + $rc=1; + next; # go to next node + } + } + $sendkeys->soft_close(); + + + } # end foreach node + return $rc; +} +#------------------------------------------------------------------------------- + +=head3 senddeviceskeys + + Setup the ssh keys on the nodes + +=cut + +#----------------------------------------------------------------------------- + +sub senddeviceskeys + +{ + my ($class,$remoteshell,$remotecopy,$to_userid,$to_userpassword,$home,$ssh_setup_cmd,$nodes) = @_; + my $sendkeys; + my $timeout = 10; # sets Expect default timeout, 0 accepts immediately + my $sendkeys_sent = 0; + my $prompt1 = 'Are you sure you want to continue connecting (yes/no)?'; + my $prompt2 = 'ssword:'; + my $prompt3 = 'Permission denied*'; + my $expect_log = undef; + my $debug = 0; + my $rc=0; + if ($::VERBOSE) + { + $debug = 1; + } + + # quote the setup command and key "sshKey add \"slave->stty(qw(sane -echo)); + # + # exp_internal(1) sets exp_internal debugging + # to STDERR. + # + #$sendkeys->exp_internal(1); + $sendkeys->exp_internal($debug); + # + # log_stdout(0) prevent the program's output from being shown. + # turn on if debugging error + #$sendkeys->log_stdout(1); + $sendkeys->log_stdout($debug); + + # command to send key to the device + # sshKey add "key" + my $spawnaddkey= + "$remoteshell $node -l $to_userid $setupcmd "; + + # send mkdir command + unless ($sendkeys->spawn($spawnaddkey)) + { + my $rsp = {}; + $rsp->{data}->[0] = + "Unable to run $spawnaddkey."; + xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); + next; # go to next node + + } + + # + #run copy.sh prompts starts here + # + + my @result = $sendkeys->expect( + $timeout, + [ + $prompt1, # Are you sure you want to ... + sub { + $sendkeys->send("yes\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt2, # *ssword* + sub { + $sendkeys->send("$to_userpassword\r"); + $sendkeys->clear_accum(); + $sendkeys->exp_continue(); + } + ], + [ + $prompt3, # Permission denied + sub { + $rc= 1; + $sendkeys->soft_close(); + next; # go to next node + } + ], + ); # end prompts + ########################################## + # Expect error - report + ########################################## + if (defined($result[1])) + { + my $msg = $result[1]; + if ($msg =~ /status 0/i) { # no error + $rc=0; + } else { + my $rsp = {}; + $rsp->{data}->[0] = "$node has error,$msg"; + xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); + $rc=1; + next; # go to next node + } + } + $sendkeys->soft_close(); + } # end foreach node + return $rc; +} +1; +