自动化ssh-copy-id


29

我有任意数量的具有相同用户/通行证组合的服务器。我想编写一个脚本(我只调用一次),以便

ssh-copy-id user@myserver

为每个服务器调用。由于他们都具有相同的用户名/密码,这应该很容易,但是ssh-copy-id希望我每次都分别键入密码,这违反了脚本的目的。没有输入密码的选项,即ssh-copy-id -p mypassword user@myserver

如何编写一个脚本,该脚本在需要时自动填写密码字段ssh-copy-id


为什么使用用户/密码标识而不是用户/公钥标识?
kagali-san 2011年

16
因为我正在使用此脚本来设置用户/公共密钥。
devin 2011年

Answers:


27

看看sshpass。将密码放在文本文件中,然后执行以下操作:

$ sshpass -f password.txt ssh-copy-id user@yourserver

它在Centos7上不起作用,只是在没有错误且远程服务器上没有密钥的情况下运行
ImranRazaKhan

19

您可以使用Expect监听密码提示并发送密码:

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

保存脚本,使其可执行,然后按以下方式调用它: ./login.expect user@myserver


您需要使用较新版本的bash spawn吗?由于某些原因,我无法控制,因此无法使用bash v3.2。
devin 2011年

Bash版本无关紧要。我用Expect 5.44.1.15进行了测试,但是我已经使用了类似的旧版本的Expect。您在使用脚本时遇到麻烦了吗?
2011年

spawn: command not found
devin

spawn是Expect关键字(请参见Expect(1)手册)。听起来好像脚本被解释为外壳程序,而不是期望值。您期望安装吗?如果您直接期望运行会发生什么:expect -f login.expect user@myserver
MonkeeSage 2011年

1
@Envek我只是要添加此内容,但是很高兴看到最后一条评论是我要写的东西的直接问题。请改用此行:spawn ssh-copy-id -o StrictHostKeyChecking=no $argv
Steven Lu

3

Quanta的回答相当不错,但是它要求您将密码放入文本文件中。

在“ sshpass”手册页中:

如果未提供任何选项,则sshpass从标准输入中读取密码。

因此,您可以做的是在脚本执行期间捕获一次密码,将其存储在变量中,回显密码并将其传递给sshpass作为输入。

我一直都这样做,而且效果很好。例: 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

这是ssh-copy-id的问题;每次您运行它时,它还会添加一个密钥。如果要自动执行该过程,则您的authorized_keys文件很快就会被重复的密钥弄得一团糟。这是一个避免了这两个问题的Python程序。它从控制服务器运行,并将密钥从一个远程服务器放入另一台远程服务器。

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'

我的ssh-copy-id已经做到了:警告:所有键都已跳过,因为它们已存在于远程系统上。这是您尝试窃取钥匙的尝试吗?:)
Mihai Stanescu

2

无需多次输入密码,您可以利用pssh及其-A开关进行一次提示,然后将密码提供给列表中的所有服务器。

注意:ssh-copy-id但是,使用此方法不允许您使用,因此您需要滚动自己的方法来将SSH pub密钥文件附加到远程帐户的~/.ssh/authorized_keys文件中。

这是完成工作的示例:

$ 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

上面的脚本通常是这样构造的:

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

高级pssh细节

  • cat <pubkey> 将公钥文件输出到 pssh
  • pssh使用-I开关通过STDIN提取数据
  • -l <remote user> 是远程服务器的帐户(假设IP文件中各服务器的用户名相同)
  • -A告诉pssh您输入密码,然后将其重新用于连接到的所有服务器
  • -i告诉pssh将任何输出发送到STDOUT而不是将其存储在文件中(默认行为)
  • '...cmds to add pubkey...'-这是正在发生的事情中最棘手的部分,所以我将自己分解(见下文)

在远程服务器上运行的命令

这些是pssh将在每台服务器上运行的命令:

'                                         \
  umask 077;                              \
  mkdir -p ~/.ssh;                        \
  afile=~/.ssh/authorized_keys;           \
  cat - >> $afile;                        \
  sort -u $afile -o $afile                \
'
为了:
  • 将远程用户的umask设置为077,这样我们将要创建的任何目录或文件都会相应地设置其权限,如下所示:

    $ 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
    
  • 创建目录~/.ssh,如果目录已经存在,请忽略警告我们

  • 设置一个变量,$afile其路径为authorized_keys文件
  • cat - >> $afile -接收来自STDIN的输入并附加到authorized_keys文件中
  • sort -u $afile -o $afile -对authorized_keys文件进行唯一排序并保存

注意:最后一点是要处理对同一台服务器多次运行上述操作的情况。这样可以避免多次添加您的发布密钥。

注意单the!

还应特别注意所有这些命令都嵌套在单引号内的事实。这很重要,因为我们要$afile等到它在远程服务器上执行后才能进行评估。

'               \
   ..cmds...    \
'

我已经扩展了上面的内容,因此在这里更容易阅读,但是我通常将它们全部运行在一行上,如下所示:

$ 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'

奖金材料

通过使用,pssh您可以不必构造文件并使用来提供动态内容-h <(...some command...),也可以使用的另一个pssh开关创建IP列表-H "ip1 ip2 ip3"

例如:

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

以上内容可用于从我的~/.ssh/config文件中提取IP列表。当然,您也可以printf用于生成动态内容:

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

例如:

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

您也可以seq用来生成格式化的数字序列!

参考和类似工具 pssh

如果您不想像pssh我上面那样使用,可以使用其他一些选项。


Ansible's authorized_key_module似乎不适用于新机器。我必须先ssh-copy-id xxx,所以我正在寻找一种只在新机器上使用ansible add ssh-key的方法,有什么主意吗?
秘银

@mithril-听起来像个错误,我想在Ansible论坛上问一下。
slm

1

并行SSH工具之一(clusterssh,mssh,pssh)可能适合您。

例如,使用cssh登录所有计算机并自己添加密钥。


1
我已经有了一套自定义工具,可以执行我需要做的所有事情,除了复制密钥外。
devin 2011年

确实是…因此,请使用此工具完成一项遗漏的任务。尽管如果这将是一件持续的事情,MonkeeSage发布的脚本(适应于从stdin读取密码并在多台服务器上工作)可能是最好的选择。
2011年


0

我想强调的是,一个想法有多糟糕:

  1. 在脚本中使用硬编码的密码
  2. 在所有服务器上使用相同的密码...为什么...?
  3. 如果您坚持这样做,请不要使用SSH public_key + password身份验证
  4. 将密码保存到文本文件中

这是一个更安全的实现...

#!/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 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.