#!/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: remoteshellexp [-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,$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 on $node"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); next; } # #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->hard_close(); } ], ); # end prompts ########################################## # Expect error - report ########################################## if (defined($result[1])) { my $msg = $result[1]; if ($msg =~ /status 0/i) { # no error $rc=0; } else { if ($msg =~ /2:EOF/i) { # no error $rc=0; } else { my $rsp = {}; $rsp->{data}->[0] = "mkdir:$node has error,$msg"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); $rc=1; } } } $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 on $node."; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1); next; } 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->hard_close(); } ], ); # end prompts ########################################## # Expect error - report ########################################## if (defined($result[1])) { my $msg = $result[1]; if ($msg =~ /status 0/i) { # no error $rc=0; } else { if ($msg =~ /2:EOF/i) { # no error $rc=0; } else { my $rsp = {}; $rsp->{data}->[0] = "copykeys:$node has error,$msg"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); $rc=1; } } } $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->hard_close(); } ], ); # end prompts ########################################## # Expect error - report ########################################## if (defined($result[1])) { my $msg = $result[1]; if ($msg =~ /status 0/i) { # no error $rc=0; } else { if ($msg =~ /2:EOF/i) { # no error $rc=0; } else { my $rsp = {}; $rsp->{data}->[0] = "copy.sh:$node has error,$msg"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); $rc=1; } } } $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;