I am using the following syntax to copy a public key to a host, in order to be able to log in afterwards to the host without password query:
ssh-copy-id $hostname
in which $hostname
is the hostname of the system with the username, e.g. [email protected]
. However, this command requires at least one password query and - sometimes - an additional interaction of the type:
The authenticity of host 'xxx (xxx)' can't be established.
RSA key fingerprint is xxx.
Are you sure you want to continue connecting (yes/no)?
I tried to solve my problem with expect
, and here is what I have so far (with all the comments and suggestions incorporated):
#!/usr/bin/expect
set timeout 9
set hostname [lindex $argv 0]
spawn ssh-copy-id $hostname
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $hostname\n"; exit 1 }
"*re you sure you want to continue connecting" {
send "yes\r"
exp_continue
}
"*assword*" {
send "fg4,57e4h\r"
}
}
This works so far as it 'catches' the first interaction correctly, but not the second one. It seems, that the correct password (fg4,57e4h) is being used, but when I try to log in to the host machine, I am still asked for a password. I also checked that no entry in .ssh/authorized_hosts
have been made. The used password also is absolutely correct, as I can just copy and paste it to log-in. The script does not create any error, but produces the following exp_internal 1
output:
./expect_keygen XXX
spawn ssh-copy-id XXX
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {3602}
expect: does "" (spawn_id exp6) match glob pattern "*re you sure you want to continue connecting"? no
"*assword*"? no
XXX's password:
expect: does "XXX's password: " (spawn_id exp6) match glob pattern "*re you sure you want to continue connecting"? no
"*assword*"? yes
expect: set expect_out(0,string) "XXX's password: "
expect: set expect_out(spawn_id) "exp6"
expect: set expect_out(buffer) "XXX's password: "
send: sending "fg4,57e4h\r" to { exp6 }
Although I am neither tcl nor expect expert, it seems expect sends the correct string (i.e. the password) to the ssh-copy-id
command. But still, there must be a problem as the above expect command does not copy the public key to the host.
Under normal conditions SSH toolchain asks the password from terminal, not from stdin. You can provide custom SSH_ASKPASS program to push your password with it.
Create a simple script askpass.sh:
#!/bin/sh
echo $PASSWORD
then configure it to be used in ssh:
chmod a+x askpass.sh
export SSH_ASKPASS=askpass.sh
finally run ssh-copy-id (without expect):
export DISPLAY=:0
PASSWORD=mySecurePassword setsid ssh-copy-id -o StrictHostKeyChecking=no hishost.thatwas.secure.com
setsid detaches from terminal (ssh will then panic and look for askpass program) DISPLAY is also checked by ssh (it thinks your askpass is a GUI)
Note that there might be hidden security vulnerabilities with this approach.
This should fix your problem.
#!/usr/bin/expect
set timeout 9
set hostname [lindex $argv 0]
spawn ssh-copy-id $hostname
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $hostname\n"; exit 1 }
"*re you sure you want to continue connecting" {
send "yes\r"
exp_continue
}
"*assword*" {
send "fg4,57e4h\r"
interact
exit 0
}
}
I'd like to share my tcl/expect script. It works quite well.
#!/usr/bin/env tclsh
package require Expect
set prompt {[$❯#] }
set keyfile "~/.ssh/id_rsa.pub"
set needcopy 0
if {![file exists $keyfile]} {
spawn ssh-keygen
interact
}
spawn ssh $argv
expect {
{continue connecting (yes/no)?} {
send "yes\r"
exp_continue
}
{[Pp]ass*: } {
set needcopy 1
interact "\r" {
send "\r"
exp_continue
}
}
$prompt
}
if {$needcopy} {
set fd [open $keyfile]
gets $fd pubkey
close $fd
send " mkdir -p ~/.ssh\r"
expect $prompt
send " cat >> ~/.ssh/authorized_keys <<EOF\r$pubkey\rEOF\r"
expect $prompt
}
interact
The errors you're seeing result from spawn
not using a shell to execute the command. If you want shell control characters, you need to spawn a shell:
spawn sh -c "cat $home/.ssh/id_rsa.pub | ssh $hostname 'cat >> $home/.ssh/authorized_keys'"
However, I think ssh-copy-id
will ask you the same questions, so this should be a drop-in replacement:
spawn ssh-copy-id $hostname
If you may or may not see the "continue connecting" prompt, you need a nested expect with exp_continue
spawn ssh-copy-id $hostname
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $hostname\n"; exit 1 }
"*re you sure you want to continue connecting" {
send "yes\r"
exp_continue
}
"*assword*" {
send "mysecretpassword\r"
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With