How can I use sshpass with a blank password coming from a variable? [duplicate] - bash

This question already has answers here:
When to wrap quotes around a shell variable?
(5 answers)
Closed 6 years ago.
I would like to write a script that can ssh into a linux target without any user interaction. The machine my script runs from as well as the targets that I'm trying to ssh into get reformatted every night so I can't use ssh-keygen (as far as I've seen) because I'm required to enter in the target's password manually when the keys are being set up. So instead I'm trying to use sshpass. Here's a simple example of how I'd like to use it:
#!/bin/bash
host=$1
user=$2
pass=$3
sshpass -p $pass scp myfiles.tar.gz $user#$host:/
sshpass -p $pass ssh $user#$host "tar xvf myfiles.tar.gz"
This works fine EXCEPT when the target's password is blank. If instead I write:
sshpass -p "" scp myfiles.tar.gz $user#$host:/
sshpass -p "" ssh $user#$host "tar xvf myfiles.tar.gz"
The commands work fine for that target. So I'd like to be able to do this somehow with the 'pass' variable that I was using. I've tried things like
pass=""
pass="\"\""
pass="''"
pass="\n"
but none of them seem to work. Does anyone know what I need to set my 'pass' variable to?

Always quote your variables:
...
pass=""
sshpass -p "$pass" scp myfiles.tar.gz "$user#$host:/"
sshpass -p "$pass" ssh "$user#$host" "tar xvf myfiles.tar.gz"
This prevents empty strings from disappearing, and also prevents other issues like passwords with spaces splitting up and passwords with asterisks turning into filenames.
PS: shellcheck automatically points this out.

Related

Ansible Using shell module, how do I ssh and remain in the same directory? [duplicate]

I already have an ssh agent set up, and I can run commands on an external server in Bash script doing stuff like:
ssh blah_server "ls; pwd;"
Now, what I'd really like to do is run a lot of long commands on an external server. Enclosing all of these in between quotation marks would be quite ugly, and I'd really rather avoid ssh'ing multiple times just to avoid this.
So, is there a way I can do this in one go enclosed in parentheses or something? I'm looking for something along the lines of:
ssh blah_server (
ls some_folder;
./someaction.sh;
pwd;
)
Basically, I'll be happy with any solution as long as it's clean.
Edit
To clarify, I'm talking about this being part of a larger bash script. Other people might need to deal with the script down the line, so I'd like to keep it clean. I don't want to have a bash script with one line that looks like:
ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"
because it is extremely ugly and difficult to read.
How about a Bash Here Document:
ssh otherhost << EOF
ls some_folder;
./someaction.sh 'some params'
pwd
./some_other_action 'other params'
EOF
To avoid the problems mentioned by #Globalz in the comments, you may be able to (depending what you're doing on the remote site) get away with replacing the first line with
ssh otherhost /bin/bash << EOF
Note that you can do variable substitution in the Here document, but you may have to deal with quoting issues. For instance, if you quote the "limit string" (ie. EOF in the above), then you can't do variable substitutions. But without quoting the limit string, variables are substituted. For example, if you have defined $NAME above in your shell script, you could do
ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF
and it would create a file on the destination otherhost with the name of whatever you'd assigned to $NAME. Other rules about shell script quoting also apply, but are too complicated to go into here.
Edit your script locally, then pipe it into ssh, e.g.
cat commands-to-execute-remotely.sh | ssh blah_server
where commands-to-execute-remotely.sh looks like your list above:
ls some_folder
./someaction.sh
pwd;
To match your sample code, you can wrap your commands inside single or double qoutes. For example
ssh blah_server "
ls
pwd
"
I see two ways:
First you make a control socket like this:
ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>
and run your commands
ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>
This way you can write an ssh command without actually reconnecting to the server.
The second would be to dynamically generate the script, scping it and running.
This can also be done as follows.
Put your commands in a script, let's name it commands-inc.sh
#!/bin/bash
ls some_folder
./someaction.sh
pwd
Save the file
Now run it on the remote server.
ssh user#remote 'bash -s' < /path/to/commands-inc.sh
Never failed for me.
Put all the commands on to a script and it can be run like
ssh <remote-user>#<remote-host> "bash -s" <./remote-commands.sh
Not sure if the cleanest for long commands but certainly the easiest:
ssh user#host "cmd1; cmd2; cmd3"
This works well for creating scripts, as you do not have to include other files:
#!/bin/bash
ssh <my_user>#<my_host> "bash -s" << EOF
# here you just type all your commmands, as you can see, i.e.
touch /tmp/test1;
touch /tmp/test2;
touch /tmp/test3;
EOF
# you can use '$(which bash) -s' instead of my "bash -s" as well
# but bash is usually being found in a standard location
# so for easier memorizing it i leave that out
# since i dont fat-finger my $PATH that bad so it cant even find /bin/bash ..
SSH and Run Multiple Commands in Bash.
Separate commands with semicolons within a string, passed to echo, all piped into the ssh command. For example:
echo "df -k;uname -a" | ssh 192.168.79.134
Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda2 18274628 2546476 14799848 15% /
tmpfs 183620 72 183548 1% /dev/shm
/dev/sda1 297485 39074 243051 14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
The posted answers using multiline strings and multiple bash scripts did not work for me.
Long multiline strings are hard to maintain.
Separate bash scripts do not maintain local variables.
Here is a functional way to ssh and run multiple commands while keeping local context.
LOCAL_VARIABLE=test
run_remote() {
echo "$LOCAL_VARIABLE"
ls some_folder;
./someaction.sh 'some params'
./some_other_action 'other params'
}
ssh otherhost "$(set); run_remote"
For anyone stumbling over here like me - I had success with escaping the semicolon and the newline:
First step: the semicolon. This way, we do not break the ssh command:
ssh <host> echo test\;ls
^ backslash!
Listed the remote hosts /home directory (logged in as root), whereas
ssh <host> echo test;ls
^ NO backslash
listed the current working directory.
Next step: breaking up the line:
v another backslash!
ssh <host> echo test\;\
ls
This again listed the remote working directory - improved formatting:
ssh <host>\
echo test\;\
ls
If really nicer than here document or quotes around broken lines - well, not me to decide...
(Using bash, Ubuntu 14.04 LTS.)
The easiest way to configure your system to use single ssh sessions by default with multiplexing.
This can be done by creating a folder for the sockets:
mkdir ~/.ssh/controlmasters
And then adding the following to your .ssh configuration:
Host *
ControlMaster auto
ControlPath ~/.ssh/controlmasters/%r#%h:%p.socket
ControlMaster auto
ControlPersist 10m
Now, you do not need to modify any of your code. This allows multiple calls to ssh and scp without creating multiple sessions, which is useful when there needs to be more interaction between your local and remote machines.
Thanks to #terminus's answer, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ and https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing.
What is the cleanest way to ssh and run multiple commands in Bash?
I recommend using this escaping function. The function takes one argument - a function to escape. Then sshqfunc outputs declare -f of the function and then outputs a string that will call the function with "$#" arguments properly quoted. Then the whole is "%q" quoted and bash -c is added. In case the remote does not have bash, you could change bash to sh.
sshqfunc() { echo "bash -c $(printf "%q" "$(declare -f "$#"); $1 \"\$#\"")"; };
Then define a function with the work you want to do on the remote. The function is defined normally, so it will be properly "clean". You can test such function locally. After defining, properly escaped function is passed to the remote.
work() {
ls
pwd
echo "Some other command"
}
ssh host#something "$(sshqfunc work)"
Passing You can also pass arguments, and they will be passed to your function as positional arguments. The right next argument after the function will be assigned to $0 - usually a placeholder like -- or _ is used to separate arguments from call.
work() {
file=$1
num=$2
ls "$file"
echo "num is $num"
}
ssh host#something "$(sshqfunc work)" -- /this/file 5
But note that arguments should also be properly quoted if there are any magic characters:
ssh host#something "$(sshqfunc work)" -- "$(printf "%q" "$var1" "$var2")"
For simple commands you can use:
ssh <ssh_args> command1 '&&' command2
or
ssh <ssh_args> command1 \&\& command2

Navigating in ssh server through a local bash script [duplicate]

I already have an ssh agent set up, and I can run commands on an external server in Bash script doing stuff like:
ssh blah_server "ls; pwd;"
Now, what I'd really like to do is run a lot of long commands on an external server. Enclosing all of these in between quotation marks would be quite ugly, and I'd really rather avoid ssh'ing multiple times just to avoid this.
So, is there a way I can do this in one go enclosed in parentheses or something? I'm looking for something along the lines of:
ssh blah_server (
ls some_folder;
./someaction.sh;
pwd;
)
Basically, I'll be happy with any solution as long as it's clean.
Edit
To clarify, I'm talking about this being part of a larger bash script. Other people might need to deal with the script down the line, so I'd like to keep it clean. I don't want to have a bash script with one line that looks like:
ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"
because it is extremely ugly and difficult to read.
How about a Bash Here Document:
ssh otherhost << EOF
ls some_folder;
./someaction.sh 'some params'
pwd
./some_other_action 'other params'
EOF
To avoid the problems mentioned by #Globalz in the comments, you may be able to (depending what you're doing on the remote site) get away with replacing the first line with
ssh otherhost /bin/bash << EOF
Note that you can do variable substitution in the Here document, but you may have to deal with quoting issues. For instance, if you quote the "limit string" (ie. EOF in the above), then you can't do variable substitutions. But without quoting the limit string, variables are substituted. For example, if you have defined $NAME above in your shell script, you could do
ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF
and it would create a file on the destination otherhost with the name of whatever you'd assigned to $NAME. Other rules about shell script quoting also apply, but are too complicated to go into here.
Edit your script locally, then pipe it into ssh, e.g.
cat commands-to-execute-remotely.sh | ssh blah_server
where commands-to-execute-remotely.sh looks like your list above:
ls some_folder
./someaction.sh
pwd;
To match your sample code, you can wrap your commands inside single or double qoutes. For example
ssh blah_server "
ls
pwd
"
I see two ways:
First you make a control socket like this:
ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>
and run your commands
ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>
This way you can write an ssh command without actually reconnecting to the server.
The second would be to dynamically generate the script, scping it and running.
This can also be done as follows.
Put your commands in a script, let's name it commands-inc.sh
#!/bin/bash
ls some_folder
./someaction.sh
pwd
Save the file
Now run it on the remote server.
ssh user#remote 'bash -s' < /path/to/commands-inc.sh
Never failed for me.
Put all the commands on to a script and it can be run like
ssh <remote-user>#<remote-host> "bash -s" <./remote-commands.sh
Not sure if the cleanest for long commands but certainly the easiest:
ssh user#host "cmd1; cmd2; cmd3"
This works well for creating scripts, as you do not have to include other files:
#!/bin/bash
ssh <my_user>#<my_host> "bash -s" << EOF
# here you just type all your commmands, as you can see, i.e.
touch /tmp/test1;
touch /tmp/test2;
touch /tmp/test3;
EOF
# you can use '$(which bash) -s' instead of my "bash -s" as well
# but bash is usually being found in a standard location
# so for easier memorizing it i leave that out
# since i dont fat-finger my $PATH that bad so it cant even find /bin/bash ..
SSH and Run Multiple Commands in Bash.
Separate commands with semicolons within a string, passed to echo, all piped into the ssh command. For example:
echo "df -k;uname -a" | ssh 192.168.79.134
Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda2 18274628 2546476 14799848 15% /
tmpfs 183620 72 183548 1% /dev/shm
/dev/sda1 297485 39074 243051 14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
The posted answers using multiline strings and multiple bash scripts did not work for me.
Long multiline strings are hard to maintain.
Separate bash scripts do not maintain local variables.
Here is a functional way to ssh and run multiple commands while keeping local context.
LOCAL_VARIABLE=test
run_remote() {
echo "$LOCAL_VARIABLE"
ls some_folder;
./someaction.sh 'some params'
./some_other_action 'other params'
}
ssh otherhost "$(set); run_remote"
For anyone stumbling over here like me - I had success with escaping the semicolon and the newline:
First step: the semicolon. This way, we do not break the ssh command:
ssh <host> echo test\;ls
^ backslash!
Listed the remote hosts /home directory (logged in as root), whereas
ssh <host> echo test;ls
^ NO backslash
listed the current working directory.
Next step: breaking up the line:
v another backslash!
ssh <host> echo test\;\
ls
This again listed the remote working directory - improved formatting:
ssh <host>\
echo test\;\
ls
If really nicer than here document or quotes around broken lines - well, not me to decide...
(Using bash, Ubuntu 14.04 LTS.)
The easiest way to configure your system to use single ssh sessions by default with multiplexing.
This can be done by creating a folder for the sockets:
mkdir ~/.ssh/controlmasters
And then adding the following to your .ssh configuration:
Host *
ControlMaster auto
ControlPath ~/.ssh/controlmasters/%r#%h:%p.socket
ControlMaster auto
ControlPersist 10m
Now, you do not need to modify any of your code. This allows multiple calls to ssh and scp without creating multiple sessions, which is useful when there needs to be more interaction between your local and remote machines.
Thanks to #terminus's answer, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ and https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing.
What is the cleanest way to ssh and run multiple commands in Bash?
I recommend using this escaping function. The function takes one argument - a function to escape. Then sshqfunc outputs declare -f of the function and then outputs a string that will call the function with "$#" arguments properly quoted. Then the whole is "%q" quoted and bash -c is added. In case the remote does not have bash, you could change bash to sh.
sshqfunc() { echo "bash -c $(printf "%q" "$(declare -f "$#"); $1 \"\$#\"")"; };
Then define a function with the work you want to do on the remote. The function is defined normally, so it will be properly "clean". You can test such function locally. After defining, properly escaped function is passed to the remote.
work() {
ls
pwd
echo "Some other command"
}
ssh host#something "$(sshqfunc work)"
Passing You can also pass arguments, and they will be passed to your function as positional arguments. The right next argument after the function will be assigned to $0 - usually a placeholder like -- or _ is used to separate arguments from call.
work() {
file=$1
num=$2
ls "$file"
echo "num is $num"
}
ssh host#something "$(sshqfunc work)" -- /this/file 5
But note that arguments should also be properly quoted if there are any magic characters:
ssh host#something "$(sshqfunc work)" -- "$(printf "%q" "$var1" "$var2")"
For simple commands you can use:
ssh <ssh_args> command1 '&&' command2
or
ssh <ssh_args> command1 \&\& command2

Pass along variables via ssh

I'm trying to pass along variables via ssh, and wrote two short testscripts (bash)
This one is to the execute the script on the other side (and it works, at least partially)
I start it with executing: 'mms test alpha one'
#!/bin/bash
sshpass -p (password) ssh hellfire#192.168.0.11 'bash /scripts/mms2 "$#"'
The second script that are executed is:
#!/bin/bash
echo "$#" >/scripts/test1.txt
This script is only for testing if the parameters are are transfered.
So far it creates the text file, but it's empty, so I have no idea if there's wrong with both or only one of the script.
Basically I want to pass a set of variables to the script on the server, these variables can contain spaces.
Anybody have any tips?
I found out by #Gordon Davidsson comment that the $(printf "%q " "$#") could be used to send it as a string, so the remote server didn't interpret the variables as different commands.
My new and working script is:
#!/bin/bash
sshpass -p (password) ssh hellfire#192.168.0.11 "bash /scripts/mms2 $(printf "%q " "$#")"

ssh + here document + interactive mode

Can I run a here document script over ssh on remote machine with interactive mode?
Code example is:
ssh -t xijing#ggzshop.com 'bash -s' <<EOF
sudo ls
......Other big scripts......
EOF
double -t won't work properly as well.
-----------------------------One possible solution:-------------------
After a lot of tries, I come up with following answers:
Script=`cat <<'EOF'
sudo ls
.....Big scripts.....
EOF`
ssh -t user#host ${Script}
which will allow user to type password in.
Solution of Xijing appears to work ok for me. However, I did a couple of cosmetic changes. First, for readability I used "dollar-parentheses" instead of backticks. For another thing I don't offer any explanation: Semicolons were needed to separate multiple commands in Script snippet even though commands are written on separate lines. My test:
Script=$( cat <<'HERE'
hostname;
cat /etc/issue;
sudo id
HERE
)
ssh -t user#host ${Script}
Sudo password will be asked in a normal manner, no need to omit that.
No, I don't think you can run interactive scripts like that.
To achieve what you want, you could create dedicated users for your common admin tasks that can run admin commands with sudo without password. Next, setup ssh key authentication to login as the dedicated users and perform the necessary tasks.
It is not necessary to use semicolons to separate multiple commands in Script if there are quotes around it.
Script="$( cat <<'HERE'
hostname;
cat /etc/issue;
sudo id
HERE
)"
- ssh -t user#host ${Script}
+ ssh -t user#host "${Script}"
# alternative (not recommended)
# set IFS variable to null string to avoid deletion of newlines \n in unquoted variable expansion
export IFS=''
ssh -t user#host ${Script}

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