Using Expect to administer machines via SSH, but does not complete all tasks - linux

*Please do not pile on and tell me to just use SSH keys. If it bugs you that this is the way I am doing it, pretend that I am trying to telnet in instead. :-) *
I am using an expect script to run some routine commands on a set of servers under my control via ssh. The script should run set of commands (eg svn update ~/folderx\r") on each of the machines. My current script does everything I want it to do... sometimes. Other times it exits the ssh connection before it completes one of the last few commands.
Any thoughts on how I can make the connection stay until all of the commands are completed? The code below successfully logs on, successfully runs the first two commands or so (ap-get update and one of the svn updates) and then disconnects.
#!/usr/bin/expect -f
spawn ssh username#ipaddress
set timeout -1
expect "Are you sure you want to continue connecting" {send "yes\r"; exp_continue} "password:" {send "*******\r"; exp_continue
} "username#machine" {send "sudo apt-get update\r"}
expect "password" {send "*******\r"; exp_continue} "username#machine" {send "sudo svn update ~/folder1\r"}
expect "password" {send "*******\r"; exp_continue} "username#machine" {send "sudo svn update ~/folder2\r"}
expect "password" {send "*******\r"; exp_continue} "username#machine" {send "sudo svn update ~/folder3\r"}
expect "password" {send "*******\r"; exp_continue} "username#machine" {send "sudo reboot\r"}
close

Using Expect is generally the wrong way to do this kind of thing.
The right way is to set up ssh keys so that you can ssh and run commands on the remote machine without supplying a password. Here's how to do that:
0. Create public/private key pair on local machine.
(Only needs to be done once on local machine for all remote machines.)
Go to the ~/.ssh directory on your local machine and do this:
% ssh-keygen -t rsa
1. Copy the public key to the remote machine:
% scp ~/.ssh/id_rsa.pub you#foo.com:.
2. Append that key to the authorized_keys file on the remote machine:
% ssh you#foo.com 'cat id_rsa.pub >> .ssh/authorized_keys; /bin/rm id_rsa.pub'
3. Finally, in case it doesn't work, check permissions, which must be just so:
(or maybe it's just that they can't be group/world writeable)
% cd ~; ls -ld . .ssh .ssh/authorized_keys
drwxr-xr-x .
drwxr-xr-x .ssh
-rw-r--r-- .ssh/authorized_keys
Here's a script that does the above in one fell swoop:
http://jakehofman.com/code/sshkey
You can then run commands on the remote machine like so:
ssh alice#remote.com ./foo args
To run commands on the remote machine with sudo, however, may be another story.
Hopefully others can chime in about that.
But as a first step you should avoid Expect for the initial login.

It turns out that the reason it was exiting earlier was that the prompt pattern that I was matching against matched not only the prompt, but also some of the output from the svn commands I was running. I was matching against only the "username" portion of the prompt pattern (the prompt form was "username#machine:~$"). Once I altered the script to match only "username#" it began to work as expected.
p.s. the ssh script that dreeves links to above works very nicely.

Related

Expect if condition with ssh password

I am currently trying to create a script with error handling.
Basically the script tests the ssh connection with this command :
ssh -o BatchMode=yes $machine uname -a
There is 3 potential situation that i want to handle :
SSH works just fine without password
SSH is blocked because the machine isn't in the known_hosts file in .ssh
SSH is blocked because the machine isn't in the known_hosts file in .ssh AND it requires a password to continue (which means the id_rsa.pub isn't in the authorized_keys file in .ssh
I have on main script that is calling an expect script here is what the main script looks like :
ssh -o BatchMode=yes ${machine} uname -a &> temp-file.txt 2>&1
# Here we test the ssh connection just once and we store the output of the command in a temp file
if [ $? -eq 255 ]
# If the ssh didn't work
then
if grep -q "Host key verification failed." temp-file.txt
# If the error message is "Host key verification failed."
then
expect script-expect-knownhosts.exp ${machine} 2>&1 >/dev/null
And here is the script-expect-knownhosts.exp file in which i tried to make a condition :
#!/usr/bin/expect -f
set machine [lindex $argv 0]
# Here we state that the first argument used with the command will be the $machine variable
set prompt "#|%|>|\$ $"
set timeout 60
spawn ssh $machine
# We do a ssh on the machine
set prompt "#|%|>|\$ $"
expect {
"Are you sure you want to continue connecting (yes/no)? " {send "yes\r";exp_continue}
# If he asks for a yes/no answer, then answer yes to add the machine to the known_hosts file
-exact "Password: " {send -- "^C";exp_continue}
# If he asks for a password, then send a CTRL + C
-re $prompt {send "exit\r";exp_continue}
# If the prompt shows up (if after the yes/no question, we don't need to put a password in) then type exit
}
So here is what happens when i execute the expect script with a machine in case number 2 (works just fine):
spawn ssh machine
Are you sure you want to continue connecting (yes/no)? yes
machine:~ # exit
deconnection
Connection to machine closed.
And here is what happens when i execute the expect script with a machine in case number 3 :
spawn ssh machine
Are you sure you want to continue connecting (yes/no)? yes
Password:
And it stays stuck on Password until i manually do a CTRL + C
In cases 2 and 3 you don't need exp_continue because you are stopping the connection process.
For case 2, I don't think you really want to send a control-C. When you do this interactively, typing control-C has the effect of sending a signal to kill the process you are interacting with. What you really want is to stop the ssh process, so instead of send -- "^C";exp_continue you should just do close.

How to ssh from one server to multiple server and switch to root and then change root password

From server A, i want to ssh multiple server using a non root user as direct root login is disabled to all server.
then i need to su - to switch to root
perform some normal operations, like changing directory, listing file etc.
and at the end change the root password using passwd command
I have expect installed on all servers and i am able to ssh from server A to any of the other servers but stuck in switching to root user and performing other operations as listed above.
#!/bin/bash
ssh user#ip<<'ENDSSH'
./su2root
#random operations
pwd
whoami
#random operations
./changepass
ENDSSH
su2root
#!/usr/bin/expect -f
spawn su -
set password "rootpass"
expect "assword:"
send "$password\r"
changepass
#!/usr/bin/expect -f
set newpassword "newrootpass"
spawn passwd
expect "*assword*"
send "$newpassword\r"
expect "*assword*"
send "$newpassword\r"
expect eof
exit 0
expecting to accomplish it using shell scripting only. Thanks in advance.
My initial comment on your question noted that you should be looking into running expect locally and spawning ssh sessions to the remote servers.
While not exactly answering your question, here is a simple expect script that logs into a remote system (one of my systems at home named valhalla), uses sudo -i to become root and executes the id command to show the user UID/GIDs. Note that I use ssh keys for logins which is why there is no expect for the initial login password.
The remote systems do not need expect in this example.
#!/usr/bin/expect
# vi: set ts=2 sw=2 noai ic showmode showmatch:
spawn ssh valhalla
expect "valhalla:"
send "sudo -i\r"
expect "assword for *"
send "XXXXXX\r"
expect "# "
send "id\r"
expect "(root)"
exit 0

How to make scp in Shell Script ask me for password

I am making a script to securely transfer data between my two machines through scp.
But in the script, it shows an error due to no password. So how can I make my shell script to ask me for password after executing scp command?
Here is my csh script.
# ssh shahk#sj-shahk
# ls -al
echo "Source Location of Remote Server - $1"
echo "Destination Location of Local Server - $2"
echo "File/Folder to be Transferred from Remote Server - $3"
echo "File Transfer Starts"
scp -rv $1/$3 <username>#<hostname>:$2
echo "File Transfer Completed"
# exit
Now I am using the above script with ssh in following way.
ssh <username>#<hostname> "<script name> <args>"
When I use in the above manner, it does not prompt for password while executing scp command.
You can use sshpass
https://www.cyberciti.biz/faq/noninteractive-shell-script-ssh-password-provider/
I have used it once to directly scp or ssh without prompting password.
For example :
sshpass -p 'password' scp file.tar.gz root#xxx.xxx.xxx.194:/backup
As mentioned by the other answer, sshpass will do the job perfectly. In the case where you can not install new packages on your local computer, you can also use expect (installed by default on most distros) to automate your interactive session.
The basic syntax of expect is to wait for the program to display a specific string (expect mystring), which triggers a specific behaviour (send command)
The following script shows the basic structure to implement what you need :
#!/usr/bin/expect -f
# syntax to specify which command to monitor
spawn scp myfile user#remote.host:/dest_folder
# this syntax means we expect the spawned program to display "password: "
# expect can understand regex and glob as well. read the man page for more details
expect "password: "
# the \r is needed to submit the command
send "PASSWORD\r"
# expect "$ " means we wait for anything to be written.
# change if you want to handle incorrect passwords
expect "$ "
send "other_command_to_execute_on_remote\r"
expect "$ "
send "exit\r"
As a side note, you can also set up passwordless authorizations through ssh keys.
#1) create a new ssh key on your local computer
> ssh-keygen -t rsa
#2) copy your public key to your remote server
# you will need to login, but only once. Once the key is on the remote server, you'll be able to connect without password.
> ssh-copy-id -i ~/.ssh/id_rsa.pub user#ip_machine
# OR
> cat ~/.ssh/id_rsa.pub | ssh user#ip_machine "cat - >> ~/.ssh/authorized_keys"
This tutorial explains how to use the keychain tool to manage several ssh keys and users.
ssh <username>#<hostname> "<script name> <args>"
scp will only read a password from a TTY, and it doesn't have a TTY in this case. When you run ssh and specify a command to be executed on the remote system (as you're doing here), ssh by default doesn't allocate a PTY (pseudo-tty) on the remote system. Your script and all of the commands launched from it--including scp--end up running without a TTY.
You can use the ssh -t option to make it allocate a tty on the remote system:
ssh -t <username>#<hostname> "<script name> <args>"
If you get a message like "Pseudo-terminal will not be allocated because stdin is not a terminal", then add another -t:
ssh -tt <username>#<hostname> "<script name> <args>"

Using 'expect' command to pass password to SSH running script remotely

I need to create a bash script that will remotely run another script on a batch of machines. To do so I am passing a script through SSH.
ssh -p$port root#$ip 'bash -s' < /path/to/script/test.sh
I thought it would use my RSA keys but I am getting error:
"Enter password: ERROR 1045 (28000): Access denied for user 'root'#'localhost' (using password: YES)"
I tried using sshpass to no avail. So my next solution was using expect. I have never used expect before and I'm positive my syntax is way off.
ssh -p$port root#$ip 'bash -s' < /path/to/script/test.sh
/usr/bin/expect <<EOD
expect "password"
send "$spass\n"
send "\n"
EOD
I have root access to all machines and ANY solution will do as long as the code remains within bash. Just keep in mind that this will be done in a loop with global variables ($spass, $ip, $port, etc) passed from a parent script.
You are doing it wrong in two means:
If you want expect to interact with ssh, you need to start ssh from expect script and not before.
If you put the script (/path/to/script/test.sh) to stdin of ssh, you can't communicate with the ssh process any more.
You should rather copy the script to remote host using scp and then run it.
Expect script might look like this:
/usr/bin/expect <<EOF
spawn ssh -p$port root#$ip
expect "password"
send "$Spass\r"
expect "$ "
send "/path/to/script/on/remote/server/test.sh\r"
expect "$ "
interact
EOF
#!/usr/bin/expect
#Replace with remote username and remote ipaddress
spawn /usr/bin/ssh -o StrictHostKeyChecking=no username#IPAddress
#Replace with remote username and remote ipaddress
expect "username#IPAddress's password: "
#Provide remote system password
send "urpassword\n"
#add commands to be executed. Also possible to execute bash scripts
expect "$ " {send "pwd\n"} # bash command
expect "$ " {send "cd mytest\n"}
expect "$ " {send "./first.sh\n"} # bash scripts
expect "$ " {send "exit\n"}
interact

spawn_id: spawn id exp6 not open

I know that this issue is already mentioned here, but the solution does not work for me.
I have this script (let's name it myscript.sh) that spawns a process on remote environment and that should interact with it.
#!/usr/bin/expect
log_user 0
set timeout 10
spawn ssh -o PubkeyAuthentication=no [lindex $argv 0] -n [lindex $argv 1]
expect "password:" {send "mypassword\r"}
expect "Continue to run (y/n)" {send "n\r"}
interact
When I call this script on local environment...
myscript.sh user#host "command1;./command2 parameter1 parameter2"
I get the above error at line 7 (interact)
Any ideas??
I suspect the expect is not able to find out(matching) the pattern you are sending.
expect "password:" {send "mypassword\r"}
expect "Continue to run (y/n)" {send "n\r"}
Check out again whether the "password:" and "Continue to run (y/n)" are in correct CAPS.
If still getting the same error, you can try using regular expression.
Try to do a normal ssh without script. See if it works. Sometimes the remote host identification changes, and the host has a new ip or new key. Then it helps to remove the old key with ssh-keygen -f ~/.ssh/known_hosts -R old_host, or something similar.
I had this problem and it was down to using the wrong port.
/usr/bin/expect <<EOF
spawn ssh-copy-id -i $dest_user#$ip
expect {
"yes/no" {
send "yes\r";exp_continue
} "password" {
send "$passwd\r"
} eof {
exit
}
}
expect eof
EOF
I ran into this issue as well but it was due to me creating/editing the following file for an unrelated item:
~/.ssh/config
Once I deleted that, all my scripts began working and I no longer got that issue with my expect file.

Resources