Can't get bash script to answer prompt with expect - bash

My ssh access is restricted to a Google Authenticator verification code prompt. I'd like to have a script that programmatically answers that prompt.
Context:
The variable
($1) passes correctly to the script - it's the verification code.
The sshfs command works in terminal.
The prompt Verification code: comes with space and a key symbol at the end.
[EDIT] Just to make sure we don't switch to security discussions here, please note that of course I also use SSH keys, additionally to this Google Authenticator. As the Authenticator verification code expires every x seconds it does not matter that others could intercept it.
Result:
The disk mounts (I can see it with df -h), but is empty... Kind of same behavior as when the Verification code is wrong, or maybe it doesn't have the time to execute?
Shell script:
#!/bin/bash
expect_sh=$(expect -c "
spawn /usr/local/bin/sshfs username#123.123.1.123:/path/to/folder/RAID1 /Users/username/Desktop/RAID1 -o defer_permissions -o volname=RAID1
expect \"Verification code:\"
send \"$1\r\";
")
echo "$expect_sh"
Thanks

I'm afraid, I have to answer no.
There are some issues:
Having password has argument could reveal your password to other users with a simple
ps axw
Having password stored into a variable could reveal your password to other users with a simple
ps axeww
Having passord transmited via STDIN could be easy to trace.
For this and a lot of other reason, ssh (and sftp) refuse to trasnsmit secrets via arguments, variables or STDIO.
Before asking for password, there is a lot of verification, then the use of a secured dialog (working with direct TTY or with some boxes on X DISPLAY).
So using expect or passing secret as arguments is not directly possible with ssh.
But.
You could connect ssh server by using secret key:
ssh-keygen -b 4096
Enter file in which to save the key (/home/user/.ssh/id_rsa):
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:q/2fX/Hello/World/Lorem/Ipsum/Dolor/Sit/Amet user#localhst
The key's randomart image is:
+---[RSA 4096]----+
| .=o=E.o|
| .. o= o |
| o+ +=... |
| .o+ o+o. |
| . +.oS.oo |
| . *.= . ... |
| o =. oo. |
| ... +o. |
| .ooo oooo.|
+----[SHA256]-----+
Then now, you have to send your /home/user/.ssh/id_rsa.pub to be stored in authorized_keys file in the server you try to connect to (this files is located in $HOME/.ssh/ or in /etc, but could be located elsewhere, depending on sshd.conf in server).

The reason this isn't working is that you are running expect in a subshell inside the command substitution.
This would be a regular harmless useless use of echo if it weren't for the fact that you hope and expect the process to remain alive.
Just take out the variable capture and run expect as a direct descendant of your current script. If you really require your output to be available as a variable when it's done, maybe try something like
#!/bin/bash
t=$(mktemp -t gauthssh.XXXXXXXXXX) || exit
trap 'rm -f "$t"' EXIT ERROR INT HUP TERM # clean up temp file when done
expect -c "
spawn /usr/local/bin/sshfs username#123.123.1.123:/path/to/folder/RAID1 /Users/username/Desktop/RAID1 -o defer_permissions -o volname=RAID1
expect \"Verification code:\"
send \"$1\r\";
" | tee "$t"
expect_sh=$(<"$t")

You can construct a solution with screen(1)
I tested the script below. It's not especially robust, and
you'll need to make some changes according to your enviroment.
#!/bin/sh
screen -d -m -S sshtest sh -c "ssh -l postgres localhost id > output"
pass="77c94046"
ret="$(printf '\n')"
while true; do
screen -S sshtest -X hardcopy
grep -q 'password:' hardcopy.0 && break
sleep 1
done
grep -v '^$' hardcopy.0
echo -n "$passenter" | xxd
screen -S sshtest -X stuff "$pass"
screen -S sshtest -X stuff "$(printf '\r')"
sleep 1
cat output
The idea is to set up a screen running your command that redirects
its output to a local file. Then you take screen grabs in a loop and
look for your expected prompt with grep. Once you find it, use the
'stuff' command in screen to push your password into the terminal
input (i.e. screen's pty). Then you wait a bit and collect your
output if needed. This is just proof of concept code, a robust
solution would do more error checking and cleanup, and wait for
the screen to actually exit.

Related

Clear last bash command from history from executing bash script

I have a bash script, which uses expect package to ssh into a remote server.
My script looks like this:
#!/bin/bash
while getopts "p:" option; do
case "${option}" in
p) PASSWORD=${OPTARG};;
esac
done
/usr/bin/expect -c "
spawn ssh my.login.server.com
expect {
\"Password*\" {
send \"$PASSWORD\r\"
}
}
interact
"
I run it like ./login.sh -p <my-confidential-password>
Now once you run it and log in successfully and exit from the remote server, I can hit up-arrow-key from the keyboard and can still see my command with password in the terminal. Or I simply run history it shows up. Once I exit the terminal, then it also appears in bash_history.
I need something within my script that could clear it from history and leave no trace of the command I ran (or password) anywhere.
I have tried:
Clearing it using history -c && history -r, this doesn't work as the script creates its own session.
Also, echo $HISTCMD returns 1 within script, hence I cannot clear using history -d <tag>.
P.S. I am using macOS
You could disable command history for a command:
set +o history
echo "$PASSWORD"
set -o history
Or, if your HISTCONTROL Bash variable includes ignorespace, you can indent the command with a space and it won't be added to the history:
$ HISTCONTROL=ignorespace
$ echo "Hi"
Hi
$ echo "Invisible" # Extra leading space!
Invisible
$ history | tail -n2
7 echo "Hi"
8 history | tail -n2
Notice that this isn't secure, either: the password would still be visible in any place showing running processes (such as top and friends). Consider reading it from a file with 400 permissions, or use something like pass.
You could also wrap the call into a helper function that prompts for the password, so the call containing the password wouldn't make it into command history:
runwithpw() {
IFS= read -rp 'Password: ' pass
./login.sh -p "$pass"
}

how to prevent scp/ssh from trying to read standard input

I am passing a file from one server to another using authorized keys.
However, in the event keys are no longer valid, the script is being asked for the password on and on.
I have tried
scp ${user}#$host}:/tmp ./file1 </dev/null
but I still get the prompt.
The only time when this works is if I run it off schedule like this:
echo "scp ${user}#$host}:/tmp ./file1" | at now
In this case it will correctly error out if keys are no longer valid.
But how can I create a blank input stream, that will not be prompting the user if the script is run interactively?
#David:
echo "" | scp ${user}#${host}:/tmp ./file1 </dev/null
didn't help, same response, so it may need to have an stty command to zero tty input, I'm guessing now, per Kenster's note.
But how can I create a blank input stream, that will not be prompting the user if the script is run interactively?
Instead of that disable password authentication.
scp -o BatchMode=yes -o PasswordAuthentication=no -o PubkeyAuthentication=yes ...
This might work as well
scp ${user}#${host}:/tmp ./file1 /dev/tty0 > /dev/null

Authenticating with user/password *once* for multiple commands? (session multiplexing)

I got this trick from Solaris documentation, for copying ssh public keys to remote hosts.
note: ssh-copy-id isn't available on Solaris
$ cat some_data_file | ssh user#host "cat >/tmp/some_data_file; some_shell_cmd"
I wanted to adapt it to do more involved things.
Specifically I wanted some_shell_command to be a script sent from the local host to execute on the remote host... a script would interact with the local keyboard (e.g. prompt user when the script was running on the remote host).
I experimented with ways of sending multiple things over stdin from multiple sources. But certain things that work in in local shell don't work over ssh, and some things, such as the following, didn't do what I wanted at all:
$ echo "abc" | cat <(echo "def") # echoes: def (I wanted abc\ndef)
$ echo "abc" | cat < <(echo "def") # echoes: def (I wanted abc\ndef)
$ echo "abc" | cat <<-EOF
> echo $(</dev/stdin) #echoes: echo abc (I wanted: abc)
> EOF
# messed with eval for the above but that was a problem too.
#chepner concluded it's not feasible to do all of that in a single ssh command. He suggested a theoretical alternative that didn't work as hoped, but I got it working after some research and tweaking and documented the results of that and posted it as an answer to this question.
Without that solution, having to run multiple ssh, and scp commands by default entails being prompted for password multiple times, which is a major drag.
I can't expect all the users of a script I write in a multi-user environment to configure public key authorization, nor expect they will put up with having to enter a password over and over.
OpenSSH Session Multiplexing
    This solution works even when using earlier versions of OpenSSH where the
    ControlPersistoption isn't available. (Working bash example at end of this answer)
Note: OpenSSH 3.9 introduced Session Multiplexing over a "control master connection" (in 2005), However, the ControlPersist option wasn't introduced until OpenSSH 5.6 (released in 2010).
ssh session multiplexing allows a script to authenticate once and do multiple ssh transactions over the authenticated connection. For example, if you have a script that runs several distinct tasks using ssh, scp, or sftp, each transaction can be carried out over OpenSSH 'control master session' that refers to location of its named-socket in the filesystem.
The following one-time-password authentication is useful when running a script that has to perform multiple ssh operations and one wants to avoid users having to password authenticate more than once, and is especially useful in cases where public key authentication isn't viable - e.g. not permitted, or at least not configured.
Most solutions I've seen entail using ControlPersist to tell ssh to keep the control master connection open, either indefinitely, or for some specific number of seconds.
Unfortunately, systems with OpenSSH prior to 5.6 don't have that option (wherein upgrading them might not be feasible). Unfortunately, there doesn't seem to be much documentation or discussion about that limitation online.
Reading through old release docs I discovered ControlPersist arrived late in the game for ssh session multiplexing scene. implying there may have been an alternative way to configure session multiplexing without relying on the ControlPersist option prior to it.
Initially trying to configure persistent-sessions from command line options rather than the config parameter, I ran into the problem of the ssh session terminating prematurely, closing control connection client sessions with it, or, alternatively, the connection was held open (kept ssh control master alive), terminal I/O was blocked, and the script would hang.
The following clarifies how to accomplish it.
OpenSSH option ssh flag Purpose
------------------- --------- -----------------------------
-o ControlMaster=yes -M Establishes sharable connection
-o ControlPath=path -S path Specifies path of connection's named socket
-o ControlPersist=600 Keep shareable connection open 10 min.
-o ControlPersist=yes Keep shareable connection open indefinitely
-N Don't create shell or run a command
-f Go into background after authenticating
-O exit Closes persistent connection
ControlPersist form Equivalent Purpose
------------------- ---------------- -------------------------
-o ControlPersist=yes ssh -Nf Keep control connection open indefinitely
-o ControlPersist=300 ssh -f sleep 300 Keep control connection open 5 min.
Note: scp and sftp implement -S flag differently, and -M flag not at all, so, for those commands, the -o option form is always required.
Sketchy Overview of Operations:
Note: This incomplete example doesn't execute as shown.
ctl=<path to dir to store named socket>
ssh -fNMS $ctl user#host # open control master connection
ssh -S $ctl … # example of ssh over connection
scp -o ControlPath=$ctl … # example of scp over connection
sftp -o ControlPath=$ctl … # example of sftp over connection
ssh -S $ctl -O exit # close control master connection
Session Multiplexing Demo
(Try it. You'll like it. Working example - authenticates only once):
Running this script will probably help you understand it quicker than reading it, and it is fascinating.
Note: If you lack access to remote host, just enter localhost at the "Host...?" prompt if you want to try this demo script
#!/bin/bash # This script demonstrates ssh session multiplexing
trap "[ -z "$ctl" ] || ssh -S $ctl -O exit $user#$host" EXIT # closes conn, deletes fifo
read -p "Host to connect to? " host
read -p "User to login with? " user
BOLD="\n$(tput bold)"; NORMAL="$(tput sgr0)"
echo -e "${BOLD}Create authenticated persistent control master connection:${NORMAL}"
sshfifos=~/.ssh/controlmasters
[ -d $sshfifos ] || mkdir -p $sshfifos; chmod 755 $sshfifos
ctl=$sshfifos/$user#$host:22 # ssh stores named socket ctrl conn here
ssh -fNMS $ctl $user#$host # Control Master: Prompts passwd then persists in background
lcldir=$(mktemp -d /tmp/XXXX)
echo -e "\nLocal dir: $lcldir"
rmtdir=$(ssh -S $ctl $user#$host "mktemp -d /tmp/XXXX")
echo "Remote dir: $rmtdir"
echo -e "${BOLD}Copy self to remote with scp:${NORMAL}"
scp -o ControlPath=$ctl ${BASH_SOURCE[0]} $user#$host:$rmtdir
echo -e "${BOLD}Display 4 lines of remote script, with ssh:${NORMAL}"
echo "====================================================================="
echo $rmtdir | ssh -S $ctl $user#$host "dir=$(</dev/stdin); head -4 \$dir/*"
echo "====================================================================="
echo -e "${BOLD}Do some pointless things with sftp:${NORMAL}"
sftp -o ControlPath=$ctl $user#$host:$rmtdir <<EOF
pwd
ls
lcd $lcldir
get *
quit
EOF
Using a master control socket, you can use multiple processes without having to authenticate more than once. This is just a simple example; see man ssh_config under ControlPath for advice on using a more secure socket.
It's not quite clear what you mean by sourcing somecommand locally; I'm going to assume it is a local script that you want copied over to the remote host. The simplest thing to do is just copy it over to run it.
# Copy the first file, and tell ssh to keep the connection open
# in the background after scp completes
$ scp -o ControlMaster=yes -o ControlPersist=yes -o ControlPath=%C somefile user#host:/tmp/somefile
# Copy the script on the same connection
$ scp -o ControlPath=%C somecommand user#host:
# Run the script on the same connection
$ ssh -o ControlPath=%C user#host somecommand
# Close the connection
$ ssh -o ControlPath=%C -O exit user#host
Of course, the user could use public key authentication to avoid entering their credentials at all, but ssh would still go through the authentication process each time. Here, the authentication process is only done once, by the command using ControlMaster=yes. The other two processes reuse that connection. The last commmand, with -O exit, doesn't actually connect; it just tells the local connection to close itself.
$ echo "abc" | cat <(echo "def")
The expression <(echo "def") expands to a file name, typically something like /dev/fd/63, that names a (virtual) file containing the text "def". So lets's simplify it a bit:
$ echo "def" > def.txt
$ echo "abc" | cat def.txt
This will also prints just def.
The pipe does feed the line abc to the standard input of the cat command. But because cat is given a file name on its command line, it doesn't read from its standard input. The abc is just quietly ignored, and the cat command prints the contents of the named file -- which is exactly what you told it to do.
The problem with echo abc | cat <(echo def) is that the <() wins the "providing the input" race. Luckily, bash will allow you to supply many inputs using mulitple <() constructs. So the trick is, how do you get the output of your echo abc into the <()?
How about:
$ echo abc | cat <(echo def) <(cat)
def
abc
If you need to handle the input from the pipe first, just switch the order:
$ echo abc | cat <(cat) <(echo def)
abc
def

A script command asks for username. Can I have it stored inside the script?

I have this command inside a script.
sudo openvpn --config ....
When it is executed it asks for a username, and then for a password.
Is it possible to store inside the script the username?
In other words to avoid typing it each time this script is being executed?
(I am using Linux Ubuntu)
Use the configuration directive
auth-user-pass filename
...where filename is a file with username on the first line, and password on the second. (If you don't want the password to ever touch disk, this password can be a socket on which your script passes through user input).
For instance:
#!/bin/bash
# ^- IMPORTANT: use bash, not /bin/sh
# clearing IFS and using read -r makes trailing whitespace, literal backslashes, etc. work.
username="hardcoded value"
IFS= read -r -p "Password: " password
openvpn \
--config file.ovpn \
--auth-user-pass <(printf '%s\n' "$username" "$password")
The use of printf -- a builtin -- is important here: When calling only builtins, arguments aren't placed on the argv (and thus made accessible to anything on the system inspecting the process list).
Alternately, you can use the management-query-passwords directive [or the --management-query-passwords command-line option] to allow username and password to be requested and entered via the management socket (this protocol has its own extensive documentation).
I believe it is possible. You have use the pipe | and pipe the username to the scripts beginning if it is possible. I use a command in C++, which if I remember right changes the password for the user. It looks like:
sprintf(command, "echo -e \"%s\n%s\" | passwd %s",password,password,user);
So, since this is a shell, I would guess you could do something like:
echo -e '<username>\n<password>\n' | YourScript
In your case this may work:
echo -e `<username>\n<password>\n | sudo openvpn --config...
Of course, this assumes that there are no other things it will ask for. This is also untested. Read more on piping here.
Edit:
As mentioned by Charles Duffy the above will only work with an XSI-extended system and with programs that do not rely on TTY. I'm still not 100% certain but, I ready printf was cross compatible and script -c can be used to pipe tty. Here is the information on the script command. However, trying it in CentOS7, it looks like this would work:
printf '%s\n' "username" "password" | script -c "sudo openvpn --config..."
NOTE: I tried only piping to su and it worked after POSIXLY_CORRECT was set to 1.
Also, I think I may have misunderstood exactly what you wanted to do. If you wanted to store the username and password for the duration of the script you could do something like this:
printf "Input username:"
read username
printf "Input password:"
read -s password
printf '%s\n' "$username" "$password" | script -c "sudo openvpn --config..."

Temporarily remove the ssh private key password in a shell scriptI

I am required to deploy some files from server A to server B. I connect to server A via SSH and from there, connect via ssh to server B, using a private key stored on server A, the public key of which resides in server B's authorized_keys file. The connection from A to B happens within a Bash shell script that resides on server A.
This all works fine, nice and simple, until a security-conscious admin pointed out that my SSH private key stored on server A is not passphrase protected, so that anyone who might conceivably hack into my account on server A would also have access to server B, as well as C, D, E, F, and G. He has a point, I guess.
He suggests a complicated scenario under which I would add a passphrase, then modify my shell script to add a a line at the beginning in which I would call
ssh-keygen -p -f {private key file}
answer the prompt for my old passphrase with the passphrase and the (two) prompts for my new passphrasw with just return which gets rid of the passphrase, and then at the end, after my scp command
calling
ssh-keygen -p -f {private key file}
again, to put the passphrase back
To which I say "Yecch!".
Well I can improve that a little by first reading the passphrase ONCE in the script with
read -s PASS_PHRASE
then supplying it as needed using the -N and -P parameters of ssh-keygen.
It's almost usable, but I hate interactive prompts in shell scripts. I'd like to get this down to one interactive prompt, but the part that's killing me is the part where I have to press enter twice to get rid of the passphrase
This works from the command line:
ssh-keygen -p -f {private key file} -P {pass phrase} -N ''
but not from the shell script. There, it seems I must remove the -N parameter and accept the need to type two returns.
That is the best I am able to do. Can anyone improve this? Or is there a better way to handle this? I can't believe there isn't.
Best would be some way of handling this securely without ever having to type in the passphrase but that may be asking too much. I would settle for once per script invocation.
Here is a simplified version the whole script in skeleton form
#! /bin/sh
KEYFILE=$HOME/.ssh/id_dsa
PASSPHRASE=''
unset_passphrase() {
# params
# oldpassword keyfile
echo "unset_key_password()"
cmd="ssh-keygen -p -P $1 -N '' -f $2"
echo "$cmd"
$cmd
echo
}
reset_passphrase() {
# params
# oldpassword keyfile
echo "reset_key_password()"
cmd="ssh-keygen -p -N '$1' -f $2"
echo "$cmd"
$cmd
echo
}
echo "Enter passphrase:"
read -s PASSPHRASE
unset_passphrase $PASSPHRASE $KEYFILE
# do something with ssh
reset_passphrase $PASSPHRASE $KEYFILE
Check out ssh-agent. It caches the passphrase so you can use the keyfile during a certain period regardless of how many sessions you have.
Here are more details about ssh-agent.
OpenSSH supports what's called a "control master" mode, where you can connect once, leave it running in the background, and then have other ssh instances (including scp, rsync, git, etc.) reuse that existing connection. This makes it possible to only type the password once (when setting up the control master) but execute multiple ssh commands to the same destination.
Search for ControlMaster in man ssh_config for details.
Advantages over ssh-agent:
You don't have to remember to run ssh-agent
You don't have to generate an ssh public/private key pair, which is important if the script will be run by many users (most people don't understand ssh keys, so getting a large group of people to generate them is a tiring exercise)
Depending on how it is configured, ssh-agent might time out your keys part-way through the script; this won't
Only one TCP session is started, so it is much faster if you're connecting over and over again (e.g., copying many small files one at a time)
Example usage (forgive Stack Overflow's broken syntax highlighting):
REMOTE_HOST=server
log() { printf '%s\n' "$*"; }
error() { log "ERROR: $*" >&2; }
fatal() { error "$*"; exit 1; }
try() { "$#" || fatal "'$#' failed"; }
controlmaster_start() {
CONTROLPATH=/tmp/$(basename "$0").$$.%l_%h_%p_%r
# same as CONTROLPATH but with special characters (quotes,
# spaces) escaped in a way that rsync understands
CONTROLPATH_E=$(
printf '%s\n' "${CONTROLPATH}" |
sed -e 's/'\''/"'\''"/g' -e 's/"/'\''"'\''/g' -e 's/ /" "/g'
)
log "Starting ssh control master..."
ssh -f -M -N -S "${CONTROLPATH}" "${REMOTE_HOST}" \
|| fatal "couldn't start ssh control master"
# automatically close the control master at exit, even if
# killed or interrupted with ctrl-c
trap 'controlmaster_stop' 0
trap 'exit 1' HUP INT QUIT TERM
}
controlmaster_stop() {
log "Closing ssh control master..."
ssh -O exit -S "${CONTROLPATH}" "${REMOTE_HOST}" >/dev/null \
|| fatal "couldn't close ssh control master"
}
controlmaster_start
try ssh -S "${CONTROLPATH}" "${REMOTE_HOST}" some_command
try scp -o ControlPath="${CONTROLPATH}" \
some_file "${REMOTE_HOST}":some_path
try rsync -e "ssh -S ${CONTROLPATH_E}" -avz \
some_dir "${REMOTE_HOST}":some_path
# the control master will automatically close once the script exits
I could point out an alternative solution for this. Instead of having the key stored on server A I would keep the key locally. Now I would create a local port forward to server B on port 4000.
ssh -L 4000:B:22 usernam#A
And then in a new terminal connect through the tunnel to server B.
ssh -p 4000 -i key_copied_from_a user_on_b#localhost
I don't know how feasible this is to you though.
Building up commands as a string is tricky, as you've discovered. Much more robust to use arrays:
cmd=( ssh-keygen -p -P "$1" -N "" -f "$2" )
echo "${cmd[#]}"
"${cmd[#]}"
Or even use the positional parameters
passphrase="$1"
keyfile="$2"
set -- ssh-keygen -p -P "$passphrase" -N "" -f "$keyfile"
echo "$#"
"$#"
The empty argument won't be echoed surrounded by quotes, but it's there

Resources