Search code examples
sshexpectssh-agent

expect script with optional ssh-agent passphrase prompt?


I want to automate a database backup with help from expect: First, the script establishes a forwarded port from the remote database port to my local machine using ssh. Then I use the database utility to dump all databases.

Therefore, I have the following expect script inside a bash script:

#!/bin/sh

expect <<- DONE
  set timeout -1

  # port-forward remote database port 3306 to local port 3307
  spawn ssh -M -S /var/run/examplecom-mysql-socket -fnNT -L 3307:localhost:3306 someuser@example.com

  expect {
    # conditional prompt appears in case ssh-agent is not active
    "*.ssh/id_rsa':*" {
      send -- "$SSH_PASSPHRASE\r"
    }
    # ?!?!?!? What to expect here in case ssh-agent is active, therefore no password needed, and therefore no prompt appears?
  }

  # dump remote database by connecting to forwarded port
  spawn mysqldump --all-databases --user=root --password --protocol=TCP --host=localhost --port=3307 --verbose --result-file=${DB_DUMP_FILE}

  expect {
    # prompt appears for database password 
    "*?asswor?:*" {
    send -- "$PASSWORD_MYSQL_ROOT\r"      
    }
  }

  expect eof
DONE

This script may run within two scenarios:

  • Either ssh-agent is currently not active. In that case, the spawned ssh process will prompt Enter passphrase for key '${HOME}/.ssh/id_rsa':, which the first expect rule happily matches, and correctly sends the SSH passphrase.
  • Or ssh-agent is currently active. In that case, the spawned ssh process silently establishes the port forwarding and does not need or prompt for the SSH passphrase.

I am struggling to get both scenarios working in my expect script. In particular, I don't know how to modify the script so that the "silent" active-ssh-agent is "expected" and therefore the expect script can continue with starting the database dump.

I tried the expect multiple things approach (at above ?!?!?!? comment location) by trying out:

  • expecting a newline,
  • expecting my shell prompt,
  • expecting eof.

But none worked. I found similar problems and suggested solutions around StackExchange: * How to use expect with optional prompts? * https://superuser.com/q/412259/137881 * TCL / Expect Scripting - Using a conditional statement to attempt a secondary login password

But the solutinos seem to cause code duplications: Each conditional prompt means copy and pasting the rest of the script, which makes the script get convoluted quickly with nested conditional prompts for each ssh statement.

How can I have expect work gracefully in situations where ssh does not prompt for the passphrase/password because of an active ssh-agent?


Solution

  • Yeah, that's a funny one. Since you're using ssh -N, you don't launch any process on the remote host, and the ssh session just sits there. You would just have to sleep for a couple of seconds and hope the tunnel gets established.

    set timeout 2
    spawn ssh -M -S /var/run/examplecom-mysql-socket -fnNT -L 3307:localhost:3306 someuser@example.com
    
    expect {
        # conditional prompt appears in case ssh-agent is not active
        "*.ssh/id_rsa':*" { send -- "$SSH_PASSPHRASE\r" }
        # wait until the timeout, then carry on with the rest of the script
        timeout
    }
    
    set timeout -1
    # ...
    

    Or, remove -N, then you can expect to see the prompt on the remote host.

    spawn ssh -M -S /var/run/examplecom-mysql-socket -fnT -L 3307:localhost:3306 someuser@example.com
    # ..........................................^^^^
    
    expect {
        # conditional prompt appears in case ssh-agent is not active
        "*.ssh/id_rsa':*" { send -- "$SSH_PASSPHRASE\r" }
        # I assume your prompt ends with a dollar sign and a space
        -re {\$ $}
    }
    

    Now you can spawn the mysql command and interact with that.