25

I was recently given username/password access to a list of servers and want to propagate my SSH public key to these servers, so that I can login more easily.

So that it's clear:

  • There is not any pre-existing public key on the remote servers that I can utilize to automate this
  • This constitutes the very first time I'm logging into these servers, and I'd like to not have to constantly type my credentials in to access them
  • Nor do I want to type in my password over and over using ssh-copy-id in a for loop.
  • 1
    It's the same username and password for all the servers? – roaima May 22 '15 at 7:18
  • @roaima - yup! That detail surprised me as well, but that's how this particular data center setup is and that's how they do it. – slm May 22 '15 at 10:52
  • @ott-- - double check the Q. I explicitly state that I don't want to do a for loop through ssh-copy-id, pumping my password over and over. – slm May 22 '15 at 18:53
  • See also script to automate scp in network – Gilles May 22 '15 at 21:19
  • 2
    This is a perfect use case for config management. Look at puppet,chef,ansible or salt. – spuder May 23 '15 at 18:18
30

Rather than type your password multiple times you can make use of pssh and its -A switch to prompt for it once, and then feed the password to all the servers in a list.

NOTE: Using this method doesn't allow you to use ssh-copy-id, however, so you'll need to roll your own method for appending your SSH pub key file to your remote account's ~/.ssh/authorized_keys file.

Example

Here's an example that does the job:

$ cat ~/.ssh/my_id_rsa.pub                    \
    | pssh -h ips.txt -l remoteuser -A -I -i  \
    '                                         \
      umask 077;                              \
      mkdir -p ~/.ssh;                        \
      afile=~/.ssh/authorized_keys;           \
      cat - >> $afile;                        \
      sort -u $afile -o $afile                \
    '
Warning: do not enter your password if anyone else has superuser
privileges or access to your account.
Password:
[1] 23:03:58 [SUCCESS] 10.252.1.1
[2] 23:03:58 [SUCCESS] 10.252.1.2
[3] 23:03:58 [SUCCESS] 10.252.1.3
[4] 23:03:58 [SUCCESS] 10.252.1.10
[5] 23:03:58 [SUCCESS] 10.252.1.5
[6] 23:03:58 [SUCCESS] 10.252.1.6
[7] 23:03:58 [SUCCESS] 10.252.1.9
[8] 23:03:59 [SUCCESS] 10.252.1.8
[9] 23:03:59 [SUCCESS] 10.252.1.7

The above script is generally structured like so:

$ cat <pubkey> | pssh -h <ip file> -l <remote user> -A -I -i '...cmds to add pubkey...'

High level pssh details

  • cat <pubkey> outputs the public key file to pssh
  • pssh uses the -I switch to ingest data via STDIN
  • -l <remote user> is the remote server's account (we're assuming you have the same username across the servers in the IP file)
  • -A tells pssh to ask for your password and then reuse it for all the servers that it connects to
  • -i tells pssh to send any output to STDOUT rather than store it in files (its default behavior)
  • '...cmds to add pubkey...' - this is the trickiest part of what's going on, so I'll break this down by itself (see below)

Commands being run on remote servers

These are the commands that pssh will run on each server:

'                                         \
  umask 077;                              \
  mkdir -p ~/.ssh;                        \
  afile=~/.ssh/authorized_keys;           \
  cat - >> $afile;                        \
  sort -u $afile -o $afile                \
'
In order:
  • set the remote user's umask to 077, this is so that any directories or files we're going to create, will have their permissions set accordingly like so:

    $ ls -ld ~/.ssh ~/.ssh/authorized_keys
    drwx------ 2 remoteuser remoteuser 4096 May 21 22:58 /home/remoteuser/.ssh
    -rw------- 1 remoteuser remoteuser  771 May 21 23:03 /home/remoteuser/.ssh/authorized_keys
    
  • create the directory ~/.ssh and ignore warning us if it's already there

  • set a variable, $afile, with the path to authorized_keys file
  • cat - >> $afile - take input from STDIN and append to authorized_keys file
  • sort -u $afile -o $afile - uniquely sorts authorized_keys file and saves it

NOTE: That last bit is to handle the case where you run the above multiple times against the same servers. This will eliminate your pubkey from getting appended multiple times.

Notice the single ticks!

Also pay special attention to the fact that all these commands are nested inside of single quotes. That's important, since we don't want $afile to get evaluated until after it's executing on the remote server.

'               \
   ..cmds...    \
'

I've expanded the above so it's easier to read here, but I generally run it all on a single line like so:

$ cat ~/.ssh/my_id_rsa.pub | pssh -h ips.txt -l remoteuser -A -I -i 'umask 077; mkdir -p ~/.ssh; afile=~/.ssh/authorized_keys; cat - >> $afile; sort -u $afile -o $afile'

Bonus material

By using pssh you can forgo having to construct files and either provide dynamic content using -h <(...some command...) or you can create a list of IPs using another of pssh's switches, -H "ip1 ip2 ip3".

For example:

$ cat .... | pssh -h <(grep -A1 dp15 ~/.ssh/config | grep -vE -- '#|--') ...

The above could be used to extract a list of IPs from my ~/.ssh/config file. You can of course also use printf to generate dynamic content too:

$ cat .... | pssh -h <(printf "%s\n" srv0{0..9}) ....

For example:

$ printf "%s\n" srv0{0..9}
srv00
srv01
srv02
srv03
srv04
srv05
srv06
srv07
srv08
srv09

You can also use seq to generate formatted numbers sequences too!

References & similar tools to pssh

If you don't want to use pssh as I've done so above there are some other options available.

  • 2
    Three minor additions: (1) pssh is a Python script, and can be installed with pip install pssh. (2) You can also generate ssh keys on all servers simultaneously by running ssh-keygen through pssh. (3) After generating the keys you can distribute the keys "all-to-all" by copying all public keys in a loop to the local machine, assembling them in a common authorized_keys, and copying that to each machine. ssh_agent / ssh_add can help with the passwords. – lcd047 May 22 '15 at 11:12
  • @lcd047 - thanks, I"ll incorporate those into the A later today! – slm May 22 '15 at 13:04
  • 1
    I think this script qualifies for the useless use of cat award (of old): to start a pipeline with the contents of a file, you can just redirect the input from that file. – Marc van Leeuwen May 22 '15 at 14:05
  • 1
    @MarcvanLeeuwen - I would tend to agree, but I wanted it to be easier for anyone that may come across this via future searches to clearly understand how the pubkey is getting passed to pssh. – slm May 22 '15 at 16:41
  • 1
    @MarcvanLeeuwen: It's no longer useless if you do it like this: cat ~/.ssh/*.pub | .... It may or may not be what you want in this situation though. – lcd047 May 22 '15 at 17:43
7

Alternative using xargs, sshpass and ssh-copy-id:

Assuming your credentials living in credentials.txt in format user:password@server:

$ cat credentials.txt
root:insecure@192.168.0.1
foo:insecure@192.168.0.2
bar:realsecure@192.168.0.3

You could do:

tr ':@' '\n' < credentials.txt \
| xargs -L3 sh -c 'sshpass -p $1 ssh-copy-id $0@$2'

Note: Remember to remove credentials.txt after usage!

  • 2
    And if it's the same username and password for all servers, you can just hardcode it directly and only read a list of IP-Adresses :-) – Falco May 22 '15 at 9:57
6

ClusterSSH gives you a window on each machine and with a common window to control all windows.

If we are talking 10 machines this will work. If we are talking 100 machines, there will be to many windows.

The beauty of ClusterSSH is that if one machine is not 100% like the rest, you can just click the window, and send keystrokes only to that machine before you go back to sending keystrokes to all machines.

6

Using Ansible is fairly simple. Just replace <USER> with the real login name

$ cd /path/to/public/key

$ cat<<END > hosts
  host1.example.com
  10.10.10.10
  END

$ ansible -i hosts all --ask-pass -u <USER> -m authorized_key \
      -a "user=<USER> key='$(cat id_rsa.pub)'"        
0

Couple of things that might potentially fit the bill:

As mentioned in other answers, sshpass is likely the easiest solution.

-1

You have two options here:

  • You can create a file with all the servers' IP addresses, then do the below

    while read -r ip;do
      ssh-copy-id -i .ssh/id_rsa.pub $ip
    done < servers.txt
    

Assuming servers.txt is the file with IPs/hostnames.

  • You can put all your IPs/hostnames in a loop, and run ssh-copy-id like below:

    for i in hostname1 hostname2
      do ssh-copy-id -i .ssh/id_rsa.pub $i
    done
    
  • Which completely goes against the OPs requirements: "Nor do I want to type in my password over and over using ssh-copy-id in a for loop." – OldTimer Jun 6 '15 at 19:56
  • I don't think there is another way, unless OP is willing to physically copy to all the servers. – Tolga Ozses Jun 7 '15 at 9:46

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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