29

I have some arbitrary number of servers with the same user/pass combination. I want to write a script (that I call once) so that

ssh-copy-id user@myserver

is called for each server. Since they all have the same user/pass this should be easy but ssh-copy-id wants me to type the password in separately each time which defeats the purpose of my script. There is no option for putting in a password, ie ssh-copy-id -p mypassword user@myserver.

How can I write a script that automatically fills in the password field when ssh-copy-id asks for it?

  • why do you use user/pass identification instead of user/publickey identification? – kagali-san Aug 30 '11 at 18:20
  • 16
    because I'm using this script to set up the user/publickey. – devin Aug 30 '11 at 18:37
27

Take a look at sshpass. Place your password in a text file and do something like this:

$ sshpass -f password.txt ssh-copy-id user@yourserver
  • its not working on Centos7 just run without error and no key on remote server – ImranRazaKhan Oct 5 at 9:09
19

You can use expect to listen for the password prompt and send your password:

#!/usr/bin/expect -f
spawn ssh-copy-id $argv
expect "password:"
send "YOUR_PASSWORD\n"
expect eof

Save the script, make it executable, and call it like: ./login.expect user@myserver

  • Do you need a newer version of bash to use spawn? For reasons I can't control I'm stuck with bash v3.2. – devin Aug 30 '11 at 20:38
  • Bash version shouldn't matter. I tested with expect 5.44.1.15, but I've used similar with older versions of expect. Are you having trouble using the script? – MonkeeSage Aug 30 '11 at 21:20
  • spawn: command not found – devin Aug 31 '11 at 12:02
  • spawn is an expect keyword (see expect(1) manual). Sounds like the script is being interpreted as shell rather than expect. Do you have expect installed? What happens if you run expect directly: expect -f login.expect user@myserver – MonkeeSage Aug 31 '11 at 13:51
  • 1
    @Envek I was just going to add this but it's nice to see that the last comment is a direct question for the thing that I was going to write. Use this line instead: spawn ssh-copy-id -o StrictHostKeyChecking=no $argv – Steven Lu Jan 12 '18 at 7:40
3

quanta's answer is pretty good, but it requires you to put your password in a text file.

From the "sshpass" man page:

If no option is given, sshpass reads the password from the standard input.

So, what you can do is to capture the password once during the script, store it in a variable, echo the password and pipe that to sshpass as an input.

I do this all the time and it works fine. Example: echo "Please insert the password used for ssh login on remote machine:" read -r USERPASS for TARGETIP in $@; do echo "$USERPASS" | sshpass ssh-copy-id -f -i $KEYLOCATION "$USER"@"$TARGETIP" done

2

This is a problem with ssh-copy-id; it also adds a key every time you run it. If you are automating the process, your authorized_keys file quickly gets cluttered with duplicate keys. Here is a Python program that avoids both problems. It runs from the control server and puts the keys from one remote server into another remote server.

import subprocess
def Remote(cmd,IP):
    cmd = '''ssh root@%s '''%(IP)+cmd
    lines = subprocess.check_output(cmd.split())
    return '\n'.join(lines)
source = '123.456.78.90'
target = '239.234.654.123'
getkey = 'cat /root/.ssh/id_rsa.pub'
getauth = 'cat /root/.ssh/authorized_keys'
sourcekey = Remote(getkey, source).replace('\n','').strip()
authkeys = Remote(getauth, target).replace('\n','').strip()
if sourcekey not in authkeys: 
    keycmd=''' echo "%s" >>/root/.ssh/authorized_keys; 
    chmod 600 /root/.ssh/authorized_keys '''%(sourcekey) # A compound shell statement
    print 'Installed key', Remote(keycmd,target)
else: print 'Does not need key'
  • my ssh-copy-id does that already: WARNING: All keys were skipped because they already exist on the remote system. Is this your attempt to steal keys? :) – Mihai Stanescu Aug 30 '17 at 11:37
2

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.

  • Ansible's authorized_key_module seems not work for new machine. I have to ssh-copy-id xxx first, so I am looking for a way just use ansible add ssh-key for new machine, any idea? – Mithril Nov 29 '16 at 3:24
  • @mithril - sounds like a bug, I'd ask on the Ansible forums about it. – slm Nov 29 '16 at 14:22
1

One of the parallel SSH tools (clusterssh, mssh, pssh) may be appropriate for you.

For instance, use cssh to log into all the machines and append the key yourself.

  • 1
    I already have a set of custom tools for doing everything I need, except for copying the key that is. – devin Aug 30 '11 at 18:36
  • Exactly… so use this one tool for doing the one task that's missing. Though if this is going to be an ongoing thing, the script that MonkeeSage posted (adapted to read password from stdin and work on multiple servers) would probably be your best bet. – MikeyB Aug 30 '11 at 22:57
0

Couple of things that might fit the bill:

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

0

I want to stress just how bad of an idea it is to:

  1. Use a hard-coded password in your scripts
  2. Use the same password on ALL of your servers...like...why!?
  3. NOT use SSH public_key+password authentication if you insist on this
  4. Save the password into a text file

Here is an implementation that is a bit more secure...

#!/usr/bin/python3
import os
import getpass
import argparse

parser = argparse.argument_parser()
parser.add_argument('-l','--login', action='store', help='username')
parser.add_argument('-p','--port', action='store', default='22', help='port')
parser.add_argument('-L','--list', action='store', help='file list of IPs')
parser.add_argument('-i','--ip-address', action='store', nargs='+', metavar='host' help='ip or list of ips')

args = parser.parse_args()
if not args.login:
    print("You need a login, broski!")
    return 0

if args.list:
    ips = [i for i in open(args.list, 'r').readlines()]
    passwd = getpass.getpass('Password: ')

    for ip in ips:
        cmd = 'ssh-id-copy {0}@{1} -p {2}'.format(ip,args.port,passwd)            
        os.system('sshpass -p ' + passwd + ' ' + cmd)
        print("Key added: ", ip)   # prints if successful
        # ex: sshpass -p passwd ssh-id-copy login@1.1.1.1

elif args.host:
    ip = args.host
    cmd = 'ssh-id-copy {0}@{1} -p {2}'.format(ip,args.port,passwd)
    os.system('sshpass -p ' + passwd + ' ' + cmd)
    print("Key added: ", ip)   # prints if successful
else:
    print("No IP addresses were given to run script...")
    return 0 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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