sunknudsen
sunknudsen

Reputation: 7250

Can expect scripts read data from stdin?

When executing expect scripts, arguments are visible on ps ax which can be a security vulnerability if they are sensitive.

Trying to automate opening a tab on iTerm2, running ssh admin@host and entering the passphrase when asked Enter passphrase for key '/Users/admin/.ssh/key' (the key is encrypted using that passphrase).

Host host
HostName 1.2.3.4
IdentityFile ~/.ssh/key

I would like to supply the passphrase to bash using read -sp 'Passphrase: ' passphrase and then pipe it to expect (which isn’t perfect at all from an OPSEC perspective but much better than having the passphrase leaked on ps ax).

Perhaps there is a better way?

Bellow is some code that works but leaks the passphrase on ps ax. Commented out is what I wish was possible (piping the passphrase to expect).

batch.sh

#!/bin/bash

function new_tab() {
  command=${1//\"/\\\"}
  osascript \
    -e "tell application \"iTerm2\"" \
    -e "tell current window" \
    -e "create tab with default profile" \
    -e "delay 1" \
    -e "tell current session" \
    -e "write text \"$command\"" \
    -e "end tell" \
    -e "end tell" \
    -e "end tell" > /dev/null
}

hostnames=(
  "hostname-1"
  "hostname-2"
)

read -sp 'Passphrase: ' passphrase

for hostname in "${hostnames[@]}"; do
  # new_tab "echo $passphrase | expect $(pwd)/expect.exp \"$hostname\""
  new_tab "expect $(pwd)/expect.exp \"$hostname\" \"$passphrase\""
done

expect.exp

#!/usr/bin/expect

set hostname [lindex $argv 0]
set passphrase [lindex $argv 1]

spawn ssh admin@$hostname
expect "passphrase"
send "$passphrase\r"
interact

Upvotes: 4

Views: 2249

Answers (2)

sunknudsen
sunknudsen

Reputation: 7250

Yes, expect can read from stdin but there is a caveat: reading from stdin isn’t compatible with interact.

See https://stackoverflow.com/a/57847199/4579271

Reading a single variable

#!/usr/bin/expect

set passphrase [gets stdin]

Reading multiple variables

#!/usr/bin/expect

set data [gets stdin]
scan $data "%s %s" hostname passphrase

Another approach is to use environment variables (as suggested by Glenn) but there is another caveat: environment variables are only available to the shell in which they are defined and its children.

Environment variables defined in batch.sh would therefore not be available in the iTerm2 tabs created using osascript.

So the only secure option I have is to drop osascript altogether and have all the code (batch.sh and expect.exp) execute in the same shell and use environment variables to pass variables between bash and expect.

batch.sh

#!/bin/bash

hostnames=(
  "hostname-1"
  "hostname-2"
)

read -sp 'SSH key passphrase: ' passphrase

echo ""

export PASSPHRASE=$passphrase

for hostname in "${hostnames[@]}"; do
  export HOSTNAME=$hostname
  expect "$(dirname "$0")/expect.exp"
done

expect.exp

#!/usr/bin/expect

set timeout 10

spawn ssh admin@$env(HOSTNAME)

expect {
  default {
    puts "\nCould not connect to $env(HOSTNAME)"
    exit 1
  }
  "passphrase" {
    send "$env(PASSPHRASE)\r"
  }
}

expect {
  default {
    puts "\nWrong passphrase"
    exit 1
  }
  "admin@$env(HOSTNAME)" {
    # Add automation commands here, then exit SSH session to close expect script moving on to the next hostname
    send "exit\r"
  }
}

interact

Upvotes: 2

glenn jackman
glenn jackman

Reputation: 246774

In the bashscript, read the password and then export the variable. In expect, access it from the environment with $env(passphrase)

Upvotes: 3

Related Questions