3

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
sunknudsen
  • 6,356
  • 3
  • 39
  • 76
  • 1
    Do it in expect, that way you don't even need to pass the data to the child process: https://stackoverflow.com/q/681928/7552 – glenn jackman Sep 07 '19 at 23:12
  • 1
    Hey @glennjackman. Thanks for the feedback. Trying to avoid hard coding the passphrase or leaking it on `ps ax`. I would like to enter the passphrase when needed while making it "amnesic" as the passphrase itself is encrypted in a password manager. As I’m looping through hosts in bash to run `expect` in individual tabs, I believe I have to prompt for the passphrase in bash. – sunknudsen Sep 08 '19 at 12:05
  • 1
    In general you could just pass the username and password to your expect script on `stdin`, but since you're launching the script via `osascript` in a new terminal tab, `stdin` isn't going to be connected to anything useful. You could write the credentials to a file and then read that in your expect script. – larsks Sep 08 '19 at 12:52
  • @larsks Thanks for your feedback. How do you read from stdin in `expect`? Btw, I’m open to feedback on how to achieve the end goal vs trying to make the above code work. – sunknudsen Sep 08 '19 at 13:00
  • Expect scripts are written in the [tcl](https://www.tcl.tk/) language, so something like https://wiki.tcl-lang.org/page/How+do+I+read+and+write+files+in+Tcl is a good starting point. – larsks Sep 08 '19 at 13:03
  • take a look at [sexpect (Expect for Shells)](https://github.com/clarkwang/sexpect) which you can use to write *Expect* scripts with **shell code only**. – pynexj Sep 08 '19 at 13:11
  • @larsks `set passphrase [gets stdin]` is what I am looking for, but the `expect` script exits at eof. Do you know how to prevent that? See https://stackoverflow.com/questions/57842683/how-can-i-prevent-expect-from-exiting-at-eof-when-reading-data-from-stdin – sunknudsen Sep 08 '19 at 14:14

2 Answers2

2

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

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • Thanks Glenn. Trying to avoid leaking the passphrase to the environment (which other scripts can read right?) or `ps ax` hence trying to find how to read from stdin in `expect`. Does this make any sense (I’m a an `expect` newbie). – sunknudsen Sep 08 '19 at 13:13
  • 1
    Only programs you launch from that shell script can see that environment variable. – glenn jackman Sep 08 '19 at 13:27
  • Does this approach work between tabs? Getting `no such variable` in the other tabs. – sunknudsen Sep 08 '19 at 16:11
  • It looks like this approach will _not_ work the way you're mixing shell, applescript and expect – glenn jackman Sep 09 '19 at 14:58
  • 1
    @glennjackman , *"Only programs you launch from that shell script can see that environment variable."* -- this is not true. for example on linux you can see a process' env vars by `cat /proc//environ`. – pynexj Sep 13 '19 at 12:08
2

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
sunknudsen
  • 6,356
  • 3
  • 39
  • 76