Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I've grown tired of typing my password back and forth to all the hosts you connect to, i want to be able to jump to every single user on all the hosts with ease.

So i've made this script with a quite messy oneliner at the end of it.

The script first reads user input where to connect to.

remote_user, remote_host and remote_port.

echo "Enter the user you want to connect with (sudo needs to be enabled and installed)"
read -e rUser

echo "Enter the host you want to connect to"
read -e rHost


echo "Enter the SSH-port"
read -e rPort

read -s -p "Enter Password: " password 

Checks if ssh-key is generated, if not proceeds with ssh-keygen for the user to generate a strong key.

if [ ! -d "$HOME/.ssh" ] && [ ! -f "$HOME/.ssh/id_rsa" ] && [ ! -f "$HOME/.ssh/id_rsa.pub" ]; then
    echo -e "Private / Public keys not generated"
    echo -e "Generating..."
    ssh-keygen -b 4096
fi

After that we proceed with establishing ssh-connection and creating the authorized_keys file on the remote host.

(Annoying 'bug' here you have to enter the password twice, since it doesnt seem possible to read ssh-password from stdin.)

cat "$HOME/.ssh/id_rsa.pub" | ssh "$rUser@$rHost" -p $rPort "cat >> ~/.ssh/authorized_keys"

Since the script will only be run once per remote host, its acceptable.

We continue with the quite messy oneliner.

We're establishing an SSH connection, to solve the problem with password over SSH we pass the -S parameter to SUDO that lets us read the password from STDIN. Now our subsequent SUDO calls will work. We proceed and create the .ssh directories for all the users on the system if they dont exist.

We create the authorized_keys file for all users and set correct owner, then we append authorized_keys file of the user we connected with.

remoteUsers=($(ssh "$rUser@$rHost" -p $remotePort  
'echo '"$password"' | sudo -S ls /home/ && for localUser 
in $(ls /home | grep -v $USER); do sudo mkdir -p /home/$localUser/.ssh && 
sudo touch /home/$localUser/.ssh/authorized_keys 
&& sudo chown "$localUser:$localUser" /home/$localUser/.ssh/authorized_keys && 
cat /home/$USER/.ssh/authorized_keys | sudo tee 
 --append /home/$localUser/.ssh/authorized_keys > /dev/null; done'))

Not that pretty. But i don't know how to improve this without considering another approach for the sudo over SSH problem.

And finally we write our .ssh/config with all the users in it.

for remoteUser in "${remoteUsers[@]}"
do
   cat <<< "Host ${remoteUser}_${rHost}
            HostName $rHost
            Port $rPort
            User $remoteUser" >> /home/$USER/.ssh/config
   done

Below is the script in its entirety.

#!/bin/bash

echo "Enter the user you want to connect with (sudo needs to be enabled and installed)"
read -e rUser # -e Identifier bash feature

echo "Enter the host you want to connect to"
read -e rHost # -e Identifier bash feature


echo "Enter the SSH-port"
read -e rPort

read -s -p "Enter Password: " password 

# Generate private public ssh-keys 
if [ ! -d "$HOME/.ssh" ] && [ ! -f "$HOME/.ssh/id_rsa" ] && [ ! -f "$HOME/.ssh/id_rsa.pub" ]; then
    echo -e "Private / Public keys not generated"
    echo -e "Generating..."
    ssh-keygen -b 4096
fi

# Send public key to remote host
echo "Sending public key to remote host..."
cat "$HOME/.ssh/id_rsa.pub" | ssh "$rUser@$rHost" -p $rPort "cat >> ~/.ssh/authorized_keys"
remoteUsers=($(ssh "$rUser@$rHost" -p $rPort 'echo '"$password"' | sudo -S ls /home/ && for localUser in $(ls /home | grep -v $USER); do sudo mkdir -p /home/$localUser/.ssh && sudo touch /home/$localUser/.ssh/authorized_keys && sudo chown "$localUser:$localUser" /home/$localUser/.ssh/authorized_keys && cat /home/$USER/.ssh/authorized_keys | sudo tee --append /home/$localUser/.ssh/authorized_keys > /dev/null; done'))

for remoteUser in "${remoteUsers[@]}"
do
    cat <<< "Host ${remoteUser}_${rHost}
            HostName $rHost
            Port $rPort
            User $remoteUser" >> /home/$USER/.ssh/config
done
share|improve this question
up vote 14 down vote accepted

That looks crazy. But some constructive comments:

  • Did you check for ssh-copy-id script, that is usually shipped with openssh? It solves for you the first part of your problem in standard way.

  • The second problem is in my eyes non-existent. Do you really need your authorized_keys in all accounts? Isn't it enough for one user that can do sudo?

  • The third is part is interesting. SSH config file is useful thing and I appreciate that you use it. I see the main plus in the auto-complete of full user_host string in your case (instead of user@host, where ssh would help only with the host part), but if you would use only one user, you would not need that either.

  • You need to count with the case that sudo might not accept password without TTY or from pipe on command-line.

  • Sending password in command like this might be also security issue, since the command might get logged somewhere on the remote host (debug output or in audit).

share|improve this answer
    
I guess i could just use the sudo user and doing sudo su otheruser && cd , but it seems more correct to add my public key to all of the users. And when it's finished i can just simply ssh host_user and all is done, no more need for extra scripts. Otherwise i would have to sudo su otheruser every single time ? – JazzCat Mar 26 at 16:48
    
1) Instead of sudo su user, you can use simply sudo -u user without misusing su. 2) Yes, it clearly depends on how many users you need to connect and how frequently you need to switch them (and if it is a lot, there stinks something). – Jakuje Mar 26 at 16:51
    
Its very frequent. Each host has its own user, and there is hundreds of them, so quite often yes :) Each site that is in development or production has its own $HOME/public_html, when developing or bugfixing, you want to be logged in as that user. (To be able to run screen/tmux or whatever you need to be logged in as that user) – JazzCat Mar 26 at 16:54
    
And i've searched all logs, cannot find any record of the password there, may depend on host configuration though. – JazzCat Mar 26 at 17:34
1  
Yes, got that. About the logs, it might happen when there is LogLevel DEBUG or more on the server sshd and then the full command is dropped into the log. About the audit on RHELs, I was wrong. There is logged only that some command was run, but not the full command line. – Jakuje Mar 26 at 17:55

The conditions to create keys

The condition on the directory seems a bit excessive here:

if [ ! -d "$HOME/.ssh" ] && [ ! -f "$HOME/.ssh/id_rsa" ] && [ ! -f "$HOME/.ssh/id_rsa.pub" ]; then
    echo -e "Private / Public keys not generated"
    echo -e "Generating..."
    ssh-keygen -b 4096
fi

My concern is that if the key files are missing but the directory exists, the script will not create the keys. But it's not unusual that the directory would exist without keys. For example the directory is created when you connect to a remote server for the first time, to store the known_hosts file. So I suggest to drop the condition on the directory:

if [ ! -f "$HOME/.ssh/id_rsa" ] && [ ! -f "$HOME/.ssh/id_rsa.pub" ]; then

Prefer input redirection instead of cat

Instead of using a cat process:

cat "$HOME/.ssh/id_rsa.pub" | ssh "$rUser@$rHost" -p $rPort "cat >> ~/.ssh/authorized_keys"

It's more efficient and elegant to use input redirection:

ssh "$rUser@$rHost" -p $rPort "cat >> ~/.ssh/authorized_keys" < "$HOME/.ssh/id_rsa.pub"

Running long snippets on a remote server

This is pretty much unreadable:

remoteUsers=($(ssh "$rUser@$rHost" -p $remotePort  
'echo '"$password"' | sudo -S ls /home/ && for localUser 
in $(ls /home | grep -v $USER); do sudo mkdir -p /home/$localUser/.ssh && 
sudo touch /home/$localUser/.ssh/authorized_keys 
&& sudo chown "$localUser:$localUser" /home/$localUser/.ssh/authorized_keys && 
cat /home/$USER/.ssh/authorized_keys | sudo tee 
 --append /home/$localUser/.ssh/authorized_keys > /dev/null; done'))

Here's a more readable way to write it:

remoteUsers=($(ssh "$rUser@$rHost" -p $remotePort  << EOF
echo "$password" | sudo -S ls /home/ && \
for localUser in \$(ls /home | grep -v \$USER); do
    sudo mkdir -p /home/$localUser/.ssh && \
    sudo touch /home/$localUser/.ssh/authorized_keys && \
    sudo chown "$localUser:$localUser" /home/$localUser/.ssh/authorized_keys && \
    cat /home/\$USER/.ssh/authorized_keys | sudo tee --append /home/$localUser/.ssh/authorized_keys > /dev/null
done
EOF
))

Just be careful: notice that the $ that should be executed on the remote and not locally, such as $(ls ...) and $USER must be escaped.

Security

The security concerns raised by @Jakuje are very valid. It would be better to not execute sudo on the remote like this. If ssh-copy-id is not an option, another alternative is to run the script for each remote user one by one, so that there's no more need for sudo.

share|improve this answer
    
Didn't know you could escape like that, had some issues with that , thats why i had to cut break the single quotes '"$password"'. Nice, thanks! – JazzCat Mar 26 at 16:45
    
Although i get this error now: Pseudo-terminal will not be allocated because stdin is not a terminal. Maybe your solution breaks the sudo -S ? – JazzCat Mar 26 at 17:29
    
@JazzCat you can use the -T flag of ssh to disable pseudo-tty allocation, that will make the warning (not an error) go away – janos Mar 26 at 17:34
    
Solution isn't working with or without the -T flag, no .ssh directory created or authorized_keys file. – JazzCat Mar 26 at 17:55

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.