Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use 'expect' to copy a public key to a host?

Tags:

ssh

expect

tcl

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.

like image 467
Alex Avatar asked Oct 16 '13 12:10

Alex


4 Answers

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.

like image 79
Basilevs Avatar answered Nov 01 '22 16:11

Basilevs


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
    }
}
like image 23
Feng Xi Avatar answered Nov 01 '22 16:11

Feng Xi


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
like image 30
Amos Avatar answered Nov 01 '22 15:11

Amos


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"
    }
}
like image 30
glenn jackman Avatar answered Nov 01 '22 16:11

glenn jackman