Script to automate two consecutive ssh connections - linux

I know it is possible to write a shell script which passes your hard-coded password to a ssh connection authentication (using expect). However what I need is slightly more complicated.
At my university I have a desktop computer appointed to me. I can connect remotely to this computer by first making a ssh connection with some server, then making another ssh connection from that server to my appointed desktop computer. This goes like:
localuser#localcomputer:~$ ssh -X username#serveraddress
username#serveradress password:
server$ ssh -X username#remotecomputeraddress
username#remotecomputeraddress password:
username#remotecomputer:~>
Is there a way to write a script which could automate the above (i.e. performing two consecutive ssh connections)?
Thanks in advance!
ps: Both the local and the remote computers are running on Linux.

You can do this interactively with:
ssh -t -X username#serveraddress ssh -t -X username#remotecomputeraddress
Note that is not a pipe - the second ssh is the command to run on the connection created by the first ssh. The -t options are necessary to allocate the pseudo-ttys necessary for interaction (password gathering as well as the ultimate goal - an interactive session on the remote system). Wrapping it up with expect left as an exercise for the reader.... ;-)
Bonus points for setting up proper private/public key pairs and ssh-agent so that the passwords aren't necessary (unless, of course, that is disallowed for security reasons).

Yes, you can do this.
Presuming you have your except script in the expect_script:
cat expect_script | ssh -X username#serveraddress sh -s
In this expect_script you must run ssh -X username#remotecomputeraddress.
And of course you can install public keys on the both hosts and use passwordless authentication.

I wrote something to do this with bang paths a while back:
http://stromberg.dnsalias.org/~strombrg/deep-ssh.html
So you'd set up passwordless, passphraseless authentication (or use an agent for the passphrase), like:
http://stromberg.dnsalias.org/~strombrg/ssh-keys.html
And then:
deep-ssh username#serveraddress!username#remotecomputeraddress command
If bash complains about the !, you can just escape it with a backslash.
The old timers will recognize that this is how UUCP paths were specified.

Related

Run a command on local machine while on ssh in bash [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I want to run a command on local system while I have ssh'd to a remote system in bash. Is there a way to do this? This is what I want:
#!/bin/bash
ssh mysystem#ip <<'SSH'
#Do something
#Run a command here on local machine and not on machine I have sshed to
#Do Something
exit
SSH
Edit: I want to echo some message and since echo command output won't show from remote machine, I want to run from local.
WHen you are using SSH, the key sequence <enter>~ is a escape prefix that allows you to pause SSH and send key sequences to the ssh client on the host-side.
The sequence <enter>~<ctrl + z> will pause (stop) the ssh-client job and drop you to a prompt in the calling system. Typing fg (if ou are on a Unix shell) will resume your ssh session afterwards.
You can see other ssh escape sequences avaiable by typing <enter>~?.
The sequence <enter>~. will terminate the connection and is very handy when your session is locked on the remote machine.
(Users with non-US keyboard layouts that use ~ as a dead-key to compose accents and digrams have, obviously, to type ~ twice in all of these sequences)
These sequences are of use from when you are operating the SSH session an d typign commands yourself, not for scripting.
Since you seem to want a way to that in scripts, the straightforward solution is to include an ssh command back to the originating host.
I have an approach which is pretty hacky, but it works.
Overview and security caveats
In brief, you use reverse SSH tunnelling to SSH back to your local machine and run a single command, and you connect back using your SSH keys so that no password is required.
NB This approach involves agent forwarding, which comes with a risk:
anyone with root access on the remote host can discreetly access your local SSH agent through the socket. They can use your keys to impersonate you on other machines on the network.
The risk is lessened in your case because the SSH session is only open for the duration of the command. But I'm not a security expert so can't comment further.
An alternative would be to generate a specific keypair just for this connection and use that, but I'm not sure how scriptable this would be.
The second security caveat is that this approach involves running an SSH server on your local machine. See my notes at the end of this answer for more on that.
Details
First of all, your SSH command needs some extra parameters:
ssh mysystem#ip -A -R 2900:localhost:22
-A forwards your credentials (detailed article on agent forwarding). You'll use them when connecting back to your local machine.
-R 2900:localhost:22 sets up the reverse tunnel. This means that on the remote machine you can run ssh -p2900 yourlocaluser#localhost and it'll SSH back to your local machine. Replace yourlocaluser with the user from your host machine (not the machine you're SSHing into). I picked 2900 as an arbitrary port. It needs to be higher than 1024, I think.
To avoid typing these every time, you can set them in your SSH config (~/.ssh/config) on your local machine. These are the relevant properties:
ForwardAgent yes
RemoteForward 2900 localhost:22
Also, you need to tell your local machine that SSH connections are allowed to connect to it using its own key pair(!) To do this, add the contents of your public key file (e.g. ~/.ssh/id_rsa.pub) to ~/.ssh/authorized_keys.
You can now connect to your remote machine and run a command like this to connect back to your local one:
ssh -t -p2900 yourlocaluser#localhost <command here>
Note, however, that the first time you connect back from the remote machine to your local one using the key, you'll get a warning that the host you're connecting to is unknown. Once you say that you want to continue connecting, it'll save the relevant details to ~/.ssh/known_hosts on the remote machine and not ask again.
You could log in and manually do an SSH to get the details saved. Alternatively, you can update the SSH command that you run on the remote machine, but it comes with an additional security caveat.
Here's the updated command:
ssh -o StrictHostKeyChecking=accept-new -t -p2900 yourlocaluser#localhost <command here>
The security risk is that you're accepting the key without reviewing it and making sure that it's what you're expecting, so you're vulnerable to man-in-the-middle attacks. Again, I'm no security expert, but given that you're connecting using an SSH tunnel rather than a regular SSH connection, I believe that this reduces the risk. If the known hosts file on the remote machine only contains the entry for your local machine, you could update your SSH config to replace the contents of that file with your local machine's key fingerprint from your local machine on login, and then remove -o StrictHostKeyChecking=accept-new from the above.
Note: If you're prompted for your password when trying to SSH back, that suggests that agent forwarding hasn't worked. You probably need to run ssh-add on your local machine or update your local SSH config for the host in question to include AddKeysToAgent yes.
Note about running sshd on your local machine
The above assumes that you're running sshd on your local machine, and thus accepting SSH connections to that machine. That's a security risk in itself. One way of reducing that risk is to specify that SSH is only allowed from localhost, which will work in this case because you're tunnelling back. You can find instructions on how to configure your local SSH server for this here: https://askubuntu.com/questions/179325/accepting-ssh-connections-only-from-localhost
You could also adapt the answer here and use netcat rather than SSH: https://superuser.com/a/1274810/126533
If you can change the script, you can use an expect script for that - expect_example_and_tips
This allows you to start an "ssh process" to which can send commands to the remote machine, while still running on the local machine.
Much easier in python though in my opinion - example:
#!/usr/bin/env python
import pexpect
PROMPT = "\$|\%|\>"
ssh_cmd = "ssh user#192.168.1.1"
try:
ssh = pexpect.spawn(ssh_cmd)
ssh.sendline("echo hello on remote")
ssh.expect(PROMPT)
print "hello on local machine"
ssh.close()
except Exception as e:
print e
sys.exit(2)
If you want to (for argument's sake) run date locally, just don't quote the here document, and any command substitution will be executed locally.
ssh mysystem#ip <<SSH # notice absence of quotes
echo I am logged in from $(uname -n) since $(date)
SSH
Here, the uname and date commands will be executed locally, before the ssh command runs, whereas the echo in the here document will then execute remotely.
(As an aside, there is no need to explicitly exit at the end; the shell will exit when it reaches the end of input. It's hard to imagine a scenario where anything else would make any sense whatsoever.)

ssh without key and collect the output using bash script

I want to create a bash script that will login to all the linux servers in my network using ssh and collect the output of 'uptime' command to a local file. There is no keypair installed between these local server and the remote servers. So I need to give the password (username and password is same for all the remote servers) in the script itself. I know this is not a secure way to do it, but it is just for learning purpose. I see 'expect' command can be used for the ssh login with password but confused how to use it together with the 'uptime' command that provide the server status. So my requirement is
1. I have local server test1 which contains a text file 'server_status.txt'
2. I need a script in test1 that will try to login to all the remote servers (say 192.168.0.1 to 192.168.0.50) using the same username and password. It will execute the command 'uptime' once logged in to the remote servers and store the output to the local file 'server_status.txt'
REVOKE: paste your public key into the server's /path2userthatshouldlogon/.ssh/authorized_keys and run the your commands remotely using ssh user#host commandtoexecute
due to connection wanted to be established without key.
UPDATE: have a look at sshpass if you really want to need passwords, which is NOT RECOMMENDED
Note: Doing this is poor practice. If you are testing around with this then you are learning a bad habit. Don't do this in production on servers you care about.
You'll want to execute the expect call as a $? and be sure to store the $USER and $SERVER variables or just replace them:
uptime=$(expect -c 'spawn ssh $USER#$SERVER send "uptime"; exit;')
echo $uptime

Provide password to ssh command inside bash script, Without the usage of public keys and Expect

I want to use SSH inside a script, but this script is not going to be executed on my machine.
In my implementation there are two limitations.
I can not work outside shell's standards,therefore i can not use expect because i do not know if it will be available on this machine.
I can not expect that this machine will have public keys for the SSH.
What are the possible options-solutions ?
How can i provide ssh with the requested password with an automated and secure way without adding extra dependencies?
Will it be possible to provide the password inside the script?
Thank you all in advance :)
Install sshpass, then launch the command:
sshpass -p "yourpassword" ssh -o StrictHostKeyChecking=no yourusername#hostname
For security reasons you must avoid providing password on a command line otherwise anyone running ps command can see your password. Better to use sshpass utility like this:
#!/bin/bash
export SSHPASS="your-password"
sshpass -e ssh -oBatchMode=no sshUser#remoteHost
You might be interested in How to run the sftp command with a password from Bash script?
First of all: Don't put secrets in clear text unless you know why it is a safe thing to do (i.e. you have assessed what damage can be done by an attacker knowing the secret).
If you are ok with putting secrets in your script, you could ship an ssh key with it and execute in an ssh-agent shell:
#!/usr/bin/env ssh-agent /usr/bin/env bash
KEYFILE=`mktemp`
cat << EOF > ${KEYFILE}
-----BEGIN RSA PRIVATE KEY-----
[.......]
EOF
ssh-add ${KEYFILE}
# do your ssh things here...
# Remove the key file.
rm -f ${KEYFILE}
A benefit of using ssh keys is that you can easily use forced commands to limit what the keyholder can do on the server.
A more secure approach would be to let the script run ssh-keygen -f ~/.ssh/my-script-key to create a private key specific for this purpose, but then you would also need a routine for adding the public key to the server.
AFAIK there is no possibility beside from using keys or expect if you are using the command line version ssh. But there are library bindings for the most programming languages like C, python, php, ... . You could write a program in such a language. This way it would be possible to pass the password automatically. But note this is of course a security problem as the password will be stored in plain text in that program
I completely agree with everybody who says this is almost certainly a terrible idea. It is extremely likely to allow others to attack your computers.
USE AT YOUR OWN RISK AFTER EVALUATING THE SECURITY HAZARDS
Answer
Make a program /path/to/saypass which outputs the password, such as
#!/bin/sh
echo 'secret'
Make it executable with
chmod +x /path/to/saypass
Then this is the main command:
SSH_ASKPASS="/path/to/saypass" DISPLAY=anything setsid ssh username#hostname [farcommand]
This
sets the two environment variables SSH_ASKPASS and DISPLAY
and then runs setsid
which then runs ssh without a controlling terminal
which connects to the far hostname
... runs saypass locally to get the password
... tells it to the far server
... and assuming it's correct
which then runs farcommand (if given), or an interactive shell.
I normally test with date or hostname for the optional farcommand.
There are lots of places for this to go wrong.
Explanation
The trick to this is that standard Linux command line ssh has a couple of environment variables you can use to choose a program which gets executed to supply the password.
ssh(1) manual page says:
SSH_ASKPASS If ssh needs a passphrase, it will read the passphrase from the current terminal if it was run from a terminal. If ssh does not have a terminal associated with it but DISPLAY and SSH_ASKPASS are set, it will execute the program specified by SSH_ASKPASS and open an X11 window to read the passphrase.
So: you need a program (shell script or any other kind) which will output the password. Then you need to convince ssh to use it:
With SSH_ASKPASS set to /path/to/saypass
With DISPLAY set to something silly
With no controlling terminal (this is what setsid does)
Which you put together in the following sh command:
SSH_ASKPASS="/path/to/saypass" DISPLAY=anything setsid ssh username#hostname [command]
ssh will execute
/path/to/saypass "username#hostname's password:"
Fingerprint check
If the fingerprint is needed, where you'd normally see the message
The authenticity of host '*hostname* (*ipaddress*)' can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no)?
Then ssh will run your command like this:
/path/to/saypass "Please type 'yes' or 'no':"
All-in-one script
The following is a single script for creating, using, and removing a saypass within the main script. Everyone will tell you do not put plaintext passwords in files and also never hardcode a password. They tell you this for good reason: it will cause you a lot of trouble. Use at your own risk.
#!/bin/sh
echo "#!/bin/sh\necho 'secret';rm -f /tmp/saypass.$$" > /tmp/saypass.$$
chmod 775 /tmp/saypass.$$
SSH_ASKPASS="/tmp/saypass.$$" DISPLAY=anything setsid ssh "$#"
SCP
This also works for scp, the copy program on top of ssh:
SSH_ASKPASS=/path/to/saypas DISPLAY=anything setsid scp username#hostname:/path/to/farfile .
Caveat
Really don't use this except in dire, dire, circumstances, such as where you have hundreds of computers and you can't install anything like ssh keys, sshpass even expect.
If you do use it, please don't tell anyone I told you how to do it. It really is terrible.
I don't know what the man page means about "open an X11 window", no such thing happens in my testing.
Tested on
OpenSSH_6.6.1p1 Ubuntu-2ubuntu2, OpenSSL 1.0.1f 6 Jan 2014 on Ubuntu 14.04.1 LTS,
OpenSSH_7.2p2 Ubuntu-4ubuntu2.1, OpenSSL 1.0.2g 1 Mar 2016 on Ubuntu 16.04.2 LTS
OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017 on Ubuntu 18.04.5 LTS

write a shell script to ssh to a remote machine and execute commands

I have two questions:
There are multiple remote linux machines, and I need to write a shell script which will execute the same set of commands in each machine. (Including some sudo operations). How can this be done using shell scripting?
When ssh'ing to the remote machine, how to handle when it prompts for RSA fingerprint authentication.
The remote machines are VMs created on the run and I just have their IPs. So, I cant place a script file beforehand in those machines and execute them from my machine.
There are multiple remote linux machines, and I need to write a shell script which will execute the same set of commands in each machine. (Including some sudo operations). How can this be done using shell scripting?
You can do this with ssh, for example:
#!/bin/bash
USERNAME=someUser
HOSTS="host1 host2 host3"
SCRIPT="pwd; ls"
for HOSTNAME in ${HOSTS} ; do
ssh -l ${USERNAME} ${HOSTNAME} "${SCRIPT}"
done
When ssh'ing to the remote machine, how to handle when it prompts for RSA fingerprint authentication.
You can add the StrictHostKeyChecking=no option to ssh:
ssh -o StrictHostKeyChecking=no -l username hostname "pwd; ls"
This will disable the host key check and automatically add the host key to the list of known hosts. If you do not want to have the host added to the known hosts file, add the option -o UserKnownHostsFile=/dev/null.
Note that this disables certain security checks, for example protection against man-in-the-middle attack. It should therefore not be applied in a security sensitive environment.
Install sshpass using, apt-get install sshpass then edit the script and put your linux machines IPs, usernames and password in respective order. After that run that script. Thats it ! This script will install VLC in all systems.
#!/bin/bash
SCRIPT="cd Desktop; pwd; echo -e 'PASSWORD' | sudo -S apt-get install vlc"
HOSTS=("192.168.1.121" "192.168.1.122" "192.168.1.123")
USERNAMES=("username1" "username2" "username3")
PASSWORDS=("password1" "password2" "password3")
for i in ${!HOSTS[*]} ; do
echo ${HOSTS[i]}
SCR=${SCRIPT/PASSWORD/${PASSWORDS[i]}}
sshpass -p ${PASSWORDS[i]} ssh -l ${USERNAMES[i]} ${HOSTS[i]} "${SCR}"
done
This work for me.
Syntax : ssh -i pemfile.pem user_name#ip_address 'command_1 ; command 2; command 3'
#! /bin/bash
echo "########### connecting to server and run commands in sequence ###########"
ssh -i ~/.ssh/ec2_instance.pem ubuntu#ip_address 'touch a.txt; touch b.txt; sudo systemctl status tomcat.service'
There are a number of ways to handle this.
My favorite way is to install http://pamsshagentauth.sourceforge.net/ on the remote systems and also your own public key. (Figure out a way to get these installed on the VM, somehow you got an entire Unix system installed, what's a couple more files?)
With your ssh agent forwarded, you can now log in to every system without a password.
And even better, that pam module will authenticate for sudo with your ssh key pair so you can run with root (or any other user's) rights as needed.
You don't need to worry about the host key interaction. If the input is not a terminal then ssh will just limit your ability to forward agents and authenticate with passwords.
You should also look into packages like Capistrano. Definitely look around that site; it has an introduction to remote scripting.
Individual script lines might look something like this:
ssh remote-system-name command arguments ... # so, for exmaple,
ssh target.mycorp.net sudo puppet apply
The accepted answer sshes to machines sequentially. In case you want to ssh to multiple machines and run some long-running commands like scp concurrently on them, run the ssh command as a background process.
#!/bin/bash
username="user"
servers=("srv-001" "srv-002" "srv-002" "srv-003");
script="pwd;"
for s in "${servers[#]}"; do
echo "sshing ${username}#${s} to run ${script}"
(ssh ${username}#${s} ${script})& # Run in background
done
wait # If removed, you can run some other script here
If you are able to write Perl code, then you should consider using Net::OpenSSH::Parallel.
You would be able to describe the actions that have to be run in every host in a declarative manner and the module will take care of all the scary details. Running commands through sudo is also supported.
For this kind of tasks, I repeatedly use Ansible which allows to duplicate coherently bash scripts in several containets or VM. Ansible (more precisely Red Hat) now has an additional web interface AWX which is the open-source edition of their commercial Tower.
Ansible: https://www.ansible.com/
AWX:https://github.com/ansible/awx
Ansible Tower: commercial product, you will probably fist explore the free open-source AWX, rather than the 15days free-trail of Tower
There is are multiple ways to execute the commands or script in the multiple remote Linux machines.
One simple & easiest way is via pssh (parallel ssh program)
pssh: is a program for executing ssh in parallel on a number of hosts. It provides features such as sending input to all of the processes, passing a password to ssh, saving the output to files, and timing out.
Example & Usage:
Connect to host1 and host2, and print "hello, world" from each:
pssh -i -H "host1 host2" echo "hello, world"
Run commands via a script on multiple servers:
pssh -h hosts.txt -P -I<./commands.sh
Usage & run a command without checking or saving host keys:
pssh -h hostname_ip.txt -x '-q -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes' -i 'uptime; hostname -f'
If the file hosts.txt has a large number of entries, say 100, then the parallelism option may also be set to 100 to ensure that the commands are run concurrently:
pssh -i -h hosts.txt -p 100 -t 0 sleep 10000
Options:
-I: Read input and sends to each ssh process.
-P: Tells pssh to display output as it arrives.
-h: Reads the host's file.
-H : [user#]host[:port] for single-host.
-i: Display standard output and standard error as each host completes
-x args: Passes extra SSH command-line arguments
-o option: Can be used to give options in the format used in the configuration file.(/etc/ssh/ssh_config) (~/.ssh/config)
-p parallelism: Use the given number as the maximum number of concurrent connections
-q Quiet mode: Causes most warning and diagnostic messages to be suppressed.
-t: Make connections time out after the given number of seconds. 0 means pssh will not timeout any connections
When ssh'ing to the remote machine, how to handle when it prompts for
RSA fingerprint authentication.
Disable the StrictHostKeyChecking to handle the RSA authentication prompt.
-o StrictHostKeyChecking=no
Source: man pssh
This worked for me. I made a function. Put this in your shell script:
sshcmd(){
ssh $1#$2 $3
}
sshcmd USER HOST COMMAND
If you have multiple machines that you want to do the same command on you would repeat that line with a semi colon. For example, if you have two machines you would do this:
sshcmd USER HOST COMMAND ; sshcmd USER HOST COMMAND
Replace USER with the user of the computer. Replace HOST with the name of the computer. Replace COMMAND with the command you want to do on the computer.
Hope this helps!
You can follow this approach :
Connect to remote machine using Expect Script. If your machine doesn't support expect you can download the same. Writing Expect script is very easy (google to get help on this)
Put all the action which needs to be performed on remote server in a shell script.
Invoke remote shell script from expect script once login is successful.

linux execute command remotely

how do I execute command/script on a remote linux box?
say I want to do service tomcat start on box b from box a.
I guess ssh is the best secured way for this, for example :
ssh -OPTIONS -p SSH_PORT user#remote_server "remote_command1; remote_command2; remote_script.sh"
where the OPTIONS have to be deployed according to your specific needs (for example, binding to ipv4 only) and your remote command could be starting your tomcat daemon.
Note:
If you do not want to be prompt at every ssh run, please also have a look to ssh-agent, and optionally to keychain if your system allows it. Key is... to understand the ssh keys exchange process. Please take a careful look to ssh_config (i.e. the ssh client config file) and sshd_config (i.e. the ssh server config file). Configuration filenames depend on your system, anyway you'll find them somewhere like /etc/sshd_config. Ideally, pls do not run ssh as root obviously but as a specific user on both sides, servers and client.
Some extra docs over the source project main pages :
ssh and ssh-agent
man ssh
http://www.snailbook.com/index.html
https://help.ubuntu.com/community/SSH/OpenSSH/Configuring
keychain
http://www.gentoo.org/doc/en/keychain-guide.xml
an older tuto in French (by myself :-) but might be useful too :
http://hornetbzz.developpez.com/tutoriels/debian/ssh/keychain/
ssh user#machine 'bash -s' < local_script.sh
or you can just
ssh user#machine "remote command to run"
If you don't want to deal with security and want to make it as exposed (aka "convenient") as possible for short term, and|or don't have ssh/telnet or key generation on all your hosts, you can can hack a one-liner together with netcat. Write a command to your target computer's port over the network and it will run it. Then you can block access to that port to a few "trusted" users or wrap it in a script that only allows certain commands to run. And use a low privilege user.
on the server
mkfifo /tmp/netfifo; nc -lk 4201 0</tmp/netfifo | bash -e &>/tmp/netfifo
This one liner reads whatever string you send into that port and pipes it into bash to be executed. stderr & stdout are dumped back into netfifo and sent back to the connecting host via nc.
on the client
To run a command remotely:
echo "ls" | nc HOST 4201

Resources