ssh expect script doesn't recognize connect string - ssh

I tried my hand at an expect script that's supposed to copy an ssh public key up to a remote server and copy it into place so that you'll be able to ssh into it wihout having to enter in a password next time.
I'm trying to allow for a host connecting for the first time to expect the complaint about an unrecognized host key:
#!/usr/bin/expect -f
set PUB "~/.ssh/id_rsa.pub"
spawn scp $PUB openplatform#100.114.114.106:
expect {
"(Are you sure you want to continue connecting yes/no)?" { send "yes\r"; exp_continue }
password: {send secret!\r; exp_continue}
}
expect "password: "
send "secret!\r"
This is what happens when I actually run the script:
#./bin/ssh-login.ssh
spawn scp ~/.ssh/id_rsa.pub openplatform#100.114.114.106:
spawn ssh openplatform#100.114.116.106 /bin/mkdir .ssh && /bin/chmod 700 .ssh && /bin/cat id_rsa.pub >> .ssh/authorized_keys && /bin/chmod 600 .ssh/authorized_keys
The authenticity of host '100.114.116.106 (100.114.116.106)' can't be established.
RSA key fingerprint is 3b:04:73:25:24:f3:aa:99:75:a7:98:62:5d:dd:1b:38.
Are you sure you want to continue connecting (yes/no)? secret!
Please type 'yes' or 'no':
Basically the problem is that the line I'm trying to setup to expect the "Are you sure you want to continue connecting yes/no" string is not being recognized by the script.
Any suggestions on how I can improve that so that those lines match?
Thanks

You have a typo in your script - the opening bracket on the "Are you sure..." line should be before the "yes/no", not at the start. The fixed version:
#!/usr/bin/expect -f
set PUB "~/.ssh/id_rsa.pub"
spawn scp $PUB openplatform#100.114.114.106:
expect {
"Are you sure you want to continue connecting (yes/no)?" { send "yes\r"; exp_continue }
password: {send secret!\r; exp_continue}
}
expect "password: "
send "secret!\r"
...works fine for me

Related

How does ssh read the password that I type in?

My understanding is that if I type the following command in my xterm:
$ ssh ir#localhost
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
ir#localhost's password:
Then the stdin and stdout of the ssh process are both connected to the pty. So when I type in the password, ssh just reads it from stdin.
But my mental model fails to explain this:
$ yes | ssh ir#localhost
Pseudo-terminal will not be allocated because stdin is not a terminal.
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
ir#localhost's password:
...
...
zsh: command not found: y
...
Here, yes's stdin is connected to the pty, and yes's stdout is piped to ssh's stdin. So ssh should be getting a deluge of ys, but it is smart enough to tell that its stdin is not a tty, and that the contents of stdin should not be interpreted as the password. Instead, the ys are buffered, and once the login succeeds, they are delivered directly to the bash process on the remote end.
But then how is ssh able to get the password that I am typing in? The pty sends my password to yes, which drops it on the floor.
Also, ssh's claim to not allocate a pty appears to be a lie. The following snippet prints out whether or not stdin is tty:
$ [ -t 0 ] && echo true || echo false;
true
When I pipe this command to ssh, it initially prints out "false", as expected:
$ echo "[ -t 0 ] && echo true || echo false;" | ssh ir#localhost
Pseudo-terminal will not be allocated because stdin is not a terminal.
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
ir#localhost's password:
...
false
$ [ -t 0 ] && echo true || echo false;
true
But when I run the same command on the remote shell, it prints out "true". I can even open up vim, and when I resize my local terminal, the vim resizes the text that it is displaying appropriately. This can only be possible if the ssh client sends information about the resizing over the wire, and if sshd notifies the vim process, just like a pty would.
Interestingly, when I hit Ctrl+C, the ssh session is immediately terminated. My explanation for this is that the pty intercepts the Ctrl+C and sends a SIGINT to both yes and ssh. If ssh had allocated a pty, it would intercept the signal and transmit it over the wire to the remote host, and whatever process running remotely would be the one interrupted. But since ssh did not allocate a pty, it simply died. So this part is expected...but I still don't understand why [ -t 0 ] passes on the remote shell, and how ssh is able to read the password even when yes is piped to it.

Automated ssh-copy-id for a non-root user using expect

I'm a newbie to Linux, Bash and Expect
I'm currently working on a script which automatizes the ssh-keygen and ssh-copy-id processes
This is the wanted behavior:
Create SSH keys from root, for a non-root user( the rsa one )
Copying them on a given remote server
By SSH, ssh-copy-id the remote-host's keys back to the non-root user
Basically, to create a password-less ssh connection between them, all automatized on the main server.
I'd like to avoid the "su nagios" command, because it will interrupt the script waiting for a command ( or if it's not true, help me to use it correctly ).
The expect script:
#!/usr/bin/expect
set user [lindex $argv 0]
set server [lindex $argv 1]
send_user $user
send_user $server
spawn ssh-copy-id $user#$server
expect {
"(yes/no)? " {
send "yes\r"
exp_continue
}
"password: " {
send "nagios\r"
send_user "\n"
}
}
expect "$ "
spawn ssh -v $user#$server "ssh-copy-id nagios#192.168.174.142"
expect {
"(yes/no)? " {
send "yes\r"
exp_continue
}
"password: " {
send "nagios\r"
send_user "\n"
}
}
expect "$ "
exit
What I'm trying to do:
to create the keys in the right folder:
ssh-keygen -t rsa -N "" -f /home/nagios/.ssh/id_rsa
to change the text regarding user and domain
sed -i s/"root#localhost.localdomain"/"nagios#localhost.localdomain"/g /home/nagios/.ssh/id_rsa.pub
then I just copy them for the ssh-copy-id command..
cp -Rfv /home/nagios/.ssh/id_rsa /root/.ssh/
cp -Rfv /home/nagios/.ssh/id_rsa.pub /root/.ssh/
Are the keys generated binded to the user who created them?
Is that "root#localhost.localdomain" just a "comment" as I've read while browsing on internet?
Is there a way to use ssh-copy-id from root for the non-root user?
Errors I'm facing :
/usr/bin/ssh-copy-id: ERROR: Host key verification failed.
expect: spawn id exp5 not open
while executing
"expect "$ ""
(file "./copyRemoteKeys.sh" line 36)
May someone help me to debug, fix, or even help me to understand better how these things works?

ssh to cisco ASA and store output to a file

I am having trouble writing a shell-script for ssh into cisco ASA and store command output in a text file.
1.key exchange not needed in the script as it is not first time log in.
2. from my centOS server it should log into cisco ASA with ssh usr#serverip, run "en", send en password
and then run some command say "show version" and store the output to a text file in my server. I tried both shell script and the use of expect, not successful in either. Please help.
Thanks a lot in advance.
This is a small code written in python which does the work for you. Python is by default installed in your CentOS. Just save the code in file name it as .py and run it with "python .py". Let me know if this helps.
import pexpect
try:
hostname= 'yourhostname/ip of firewall'
username= 'your username'
commandTorun = 'Enter your command here'
password= 'your password'
enable= 'your enable password'
ssh = 'ssh ' + username + '#' +hostname
s=pexpect.spawn(ssh)
s.expect('word')
s.sendline(password)
s.expect('>');
s.sendline('en')
s.expect('word')
s.sendline(enable)
s.expect('#')
s.sendline('configure terminal')
s.expect('#')
s.sendline('pager 0')
s.expect('#')
s.sendline(commandTorun)
s.expect('#')
output = s.before
#You can save the output however you want here. I am printing it to the CLI.
s.sendline('exit')
s.expect('#')
s.sendline('exit')
s.expect(pexpect.EOF)
print output
except Exception, e:
print "The Script failed to login"
print str(e)
I'm not familiar with CentOS, but I have done this on Windows using Plink - a command line version of PuTTY. http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html (Edit: just checked and it appears plink exists as a .rpm file and is available on the repositories. The link provided also has source code if manual compile is your thing.)
The option -m allows you to specify a command script, -ssh forces ssh protocol, -l is the remote user and -pw is the remote password.
Here is the command:
plink.exe -ssh -l user -pw pass -m "path_to_script/script.txt" ip_of_asa > outputfile.txt
In the script.txt file you simply a list of commands including the enable command and hardcoded password:
en
enable_password
show ver
exit
Note, there are spaces in there to get the full "show ver" if multiple pages. The output uses the ">" to redirect output to file.
Hope this helps!
This works.
#!/usr/bin/expect
set remote_server [lrange $argv 0 0]
set timeout 10
spawn ssh -M username#$remote_server
while 1 {
expect {
"no)?" {send "yes\r"}
"denied" {
log_file /var/log/expect_msg.log
send_log "Can't login to $remote_server. Check username and password\n";
exit 1
}
"telnet:" {
log_file /var/log/expect_msg.log
send_log "Can't connect to $remote_server via SSH or Telnet. Something went definitely wrong\n";
exit 2
}
"failed" {
log_file /var/log/expect_msg.log
send_log "Host $remote_server exists. Check ssh_hosts file\n";
exit 3
}
timeout {
log_file /var/log/expect_msg.log
send_log "Timeout problem. Host $remote_server doesn't respond\n";
exit 4
}
"refused" {
log_file /var/log/expect_msg.log
send_log "Host $remote_server refused to SSH. That is insecure.\n"
log_file
spawn telnet $remote_server
}
"sername:" {send "username\r"}
"assword:" {send "password\r"}
">" {enable}
"#" {break}
}
}
send "terminal length 0\r"
expect "#"
send "show running-config\r"
log_file /opt/config-poller/tmp/poller.trash/$remote_server
expect "#"
send "exit\n"; exit 0
If you are accessing your router through your local machine, then you can create an expect script and configure tftp in your local machine.
The script would be like.
#!/usr/bin/expect-f
spawn telnet Router_IP
expect "word"
sent "password"
expect "word"
sent "copy running-config tftp://$tftp/$config\n\n" #edit this line wrt system's tftp configurations.
expect "word"
send "\n"

How to allow entering password when issuing commands on remote server?

Here's my script:
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Usage: $0 SERVER"
exit 255
fi
ssh $1 'su;apachectl restart'
I just want it to log in to the server and restart apache, but it needs super-user priviledges to do that. However, after it issues the su command it doesn't stop and wait for me to enter my password. Can I get it to do that?
See if this works for you! This solution takes the password before doing the SSH though...
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Usage: $0 SERVER"
exit 255
fi
read -s -p "Password: " PASSWORD
ssh $1 'echo $PASSWORD>su;apachectl restart'
The -s option prevents echoing of the password while reading it from the user.
Take a look at expect. The best way to perform such operations is through an expect script. Here is a sample that I just typed up to give you a head start, but all it does right now is show you how to handle a password in expect.
#!/usr/bin/expect -f
set timeout 60
set pswd [lindex $argv 0]
send "su\r"
match_max 2000
expect {
-re "assword:$" {
sleep 1
set send_human {.2 .15 .25 .2 .25}
send -h -- "$pswd\r"
exp_continue
} "login failed." {
send "exit\r"
log_user 1
send_user "\r\nLogin failed.\r\n"
exit 4
} timeout {
send "exit\r"
log_user 1
send_user "\r\nCommand timed out\r\n"
exit 1
} -re "(#|%)\\s*$" {
# If we get here, then su succeeded
sleep 1
send "apachectl restart\r"
expect {
-re "(#|%)\\s*$" {
send "exit\r"
log_user 1
send_user "\r\nApache restart Successful\r\n"
exit 0
} timeout {
send "exit\r"
log_user 1
send_user "\r\nCommand timed out\r\n"
exit 1
}
}
}
}
Modifying your command to:
ssh -t $1 'sudo apachectl restart'
will open a TTY and allow sudo to interact with the remote system to ask for the user account's password without storing it locally in memory.
You could probably also modify your sudo config on the remote system to allow for execution without a password. You can do this by adding the following to /etc/sudoers (execute visudo and insert this line, substituting <username> appropriately.)
<username> ALL=NOPASSWD: ALL
Also, I'm a security guy and I really hope you understand the implication of allowing an SSH connection (presuming without a passphrase on the key) and remote command execution as root. If not, you should really, really, really rethink your setup here.
[Edit] Better still, edit your sudoers file to allow only apachectl to run without a password. Insert the following and modify %staff to match your user's group, or change that to your username without the percent sign.
%staff ALL=(ALL) NOPASSWD: /usr/sbin/apachectl
Then your command should simply be changed to:
ssh $1 'sudo apachectl restart'

sudo cmd via ssh with expect twice consecutively

I used expect to execute some command with sudo in remote host via ssh. The script lies as follow:
spawn ssh -q ${user}#${host}
expect "*?assword:"
send "${pass}\r"
expect "${user}#"
send "sudo ls\r"
expect "*?assword:"
send "${pass}\r"
expect "${user}#"
send "exit\r"
interac
It runs perfectly the first time, but when I executed it consecutively some error occurred. That's because sudo won't expire right away, so if sudo some command twice in a short time, the second maybe not need the password, thus the second send "${pass}\r" in above script failed!
So how can we detect that and avoid sending password when sudo does not expire? thanks!
Modify the script so that expecting the sudo password is optional:
send "sudo ls\r"
expect {
"*?assword:" {
send "$pass\r"
exp_continue
}
"$user#" {
send "exit\r"
}
}
interac
Clean the sudo authentication cache running sudo -k just after logging.