How to use 'expect' to copy a public key to a host?
Asked Answered
D

5

7

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.

Dream answered 16/10, 2013 at 12:25 Comment(3)
Why don't you use ssh-copy-id? That's what it's for.Labyrinth
This simplifies the task; but will it work with my code snippet without change? What about the optional yes/no question, will this also be handled (I cannot check right now).Dream
Try to expect the prompt at the end of the script. In your script, the spawned process will end right after sending the password, and the whole ssh-id-copy procedure is not guaranteed to complete.Lactone
S
1

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
Shamble answered 22/12, 2017 at 10:37 Comment(2)
Wow I asked that question more than 4 years ago.Dream
Oh boy! I changed several companies in-between. I will see to accept one answer ;-)Dream
O
4

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.

Oracular answered 24/10, 2013 at 17:23 Comment(0)
B
3

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
    }
}
Brenneman answered 12/5, 2016 at 16:27 Comment(1)
Eyeballing it, interact and exit 0 have been added to the "assword" stanza.Plumb
L
1

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"
    }
}
Labyrinth answered 16/10, 2013 at 12:42 Comment(11)
Your suggestion does not work. I replaced the spawn command to use ssh-copy-id, but the expect script asks for a password. Maybe it is the regex? Maybe it is that expect` wait to see the 'yes/no' question which is not coming...?Dream
add exp_internal 1 to the top of your script and expect will tell you what's happening.Labyrinth
What I thought: expect wait for a pattern '*re you sure you want to continue connecting'. This pattern does not always occur. It might occur or might not occur, depending on various factors. The script needs top be changed, so that it 'expects' EITHER only the password prompt OR the yes/no prompt and the password prompt. How can this be achieved properly?Dream
It still does not work. There seem to be no error, and the password seem to be correct (except the '\r'), but still when trying to log in I am asked for a password. When I run ssh-copy-id by hand, and enter the password, I can log in afterwards without being asked for that password. So I guess, there is still a problem with the script...Dream
Suppose the password is 'res,483jf' (without the apostroph), I would put into the script: 'send "res,483jf\r"'. Is this correct?Dream
So where is the error then? Why does it not work for me? I double checked everything!!Dream
is the remote username the same as your local username?after you run the script, what changed in the remote user's ~/.ssh dir?Labyrinth
I use the command always as 'myscript [email protected]', explicitly defining the (different) username, in the exact same way I would use ssh-copy-id.Dream
And? Is your public key copied to root@remote:.ssh/authorized_keys ?Labyrinth
It does not seem so. After running the expect command and log in (with password), I cannot see a reference to my public key in authorized_keys. With ssh-copy-id this seem to work, on the contrary.Dream
hmm. I'd need to see the exp_internal output to go any further.Labyrinth
S
1

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
Shamble answered 22/12, 2017 at 10:37 Comment(2)
Wow I asked that question more than 4 years ago.Dream
Oh boy! I changed several companies in-between. I will see to accept one answer ;-)Dream
P
0

If your approach with expect fails, you still can try sshpass.

Phira answered 24/10, 2013 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.