ssh密钥批量分发

#################以下所有的命令脚本都是在centos7系统上实现,centos6略有不同

交互式登录系统

提起ssh我们就能想到的是远程连接,平时我们都是通过密码来登录linux主机,其实我们还是可以通过密钥来登录linux的,那么它的实现步骤是怎样的呢?

第一步:客户端通过ssh-keygen生成一对密钥,会自动存放在家目录的.ssh/authorized_keys文件下

第二步:客户端通过ssh-copy-id把公钥上传到服务器,会自动保存在./ssh/

这样以后客户端再连接服务器时,就不用使用密码了。

上面的步骤有一个疑惑:客户端和服务器的用户要对应起来吗?不需要对应起来,比如客户端当前账户是zhanghe,而服务器上的账户是root,我们也可以把zhanghe的公钥通过ssh-copy-id放置到服务器上的root家目录下,这样客户端的zhanghe用户再登录服务器端的root就不用再输入密码了。

非交互式登录系统:(expect代替我们发送公钥与远程主机)

如果想通过密钥登录的话,我们就是先生成密钥,然后再把公钥发送给远程服务器,生成密钥时我们使用ssh-keygen命令,此命令可以交互,也可以不交互,但是通过ssh-copy-id时就必需得交互了,这时我们就得用到expect了。

[root@linuxprobe ~]# vim 1.sh

#!/bin/bash

SERVER_ADDR=192.168.80.7

SERVER_PAWD=cba-123

expect () {

/usr/bin/expect <<-EOF

spawn ssh-copy-id $SERVER_ADDR

expect {

"yes/no" { send "yes\r"; exp_continue }

"password:" { send "$SERVER_PAWD\r" }

}

expect eof

EOF

}

test -f ~/.ssh/id_rsa

if [ $? -ne 0 ];then

ssh-keygen -P "" -f ~/.ssh/id_rsa &>/dev/null

expect

echo "keygen alreadly send!"

else

expect

echo "keygen alreadley send2"

fi

解释:

在脚本的开头我们定义了两个变量,一个变量是服务器的地址,另一个变量的服务器的密码

之后我们我们定义一个函数,这个函数其实就是调用了expect来帮助我们完成交互。

最后,我们通过一个id判断当前家目录有没有密钥文件,如果没有话,我们就创建,并且调用expect函数代替我们交互;如果有的话,就不用创建了,直接代替我们交互就可以了。

注意1:

ssh-keygen里面,-P“”的意思是空密码,-f是指定文件名,ssh-keygen不使用这两个选项时是交互的,使用了这两个选项就不用再交互了,也不用expect的帮忙,有的同学可能注意到了后面的文件是.ssh/id_rsa,为什么是这个路径呢?id_rsa是私钥,生成私钥会一块生成公钥的,所以我们在这里指定私钥就可以了。

注意2:

ssh-copy-id也是可以使用[email protected]的,上文当中没有写,默认就是当前用户了。

再加大一点难度,怎样批量给多台服务器推送公钥呢?在上面我们给一台服务器推送脚本会了,给多台服务器推送脚本也不难在上面的脚本里面再加一个for循环就可以了,下面我们就来实现一下。假设我们已经知道和服务器的IP列表和各个密码,然后再给这台服务器推送公钥,IP列表和密码在此:

[root@linuxprobe tmp]# cat serverhost.txt

192.168.80.7 cba-123                #都是用空格隔开的

192.168.80.8 abc-123

[root@linuxprobe tmp]# vim send_key.sh

#!/bin/bash

expect () {

/usr/bin/expect <<-EOF

spawn ssh-copy-id root@$ADDR

expect {

"yes/no" { send "yes\r"; exp_continue }

"password:" { send $PASSWD\r }

}

expect eof

EOF

}

SERVERHOST=/tmp/serverhost.txt

for ADDR in `cut -d" " -f1 $SERVERHOST`;do

PASSWD=$(grep $ADDR $SERVERHOST | cut -d" " -f2)

test -f ~/.ssh/id_rsa

if [ $? -ne 0 ];then

ssh-keygen -P "" -f ~/.ssh/id_rsa &>/dev/null

expect

echo "key already send!"

else

expect

echo "key already send!!"

fi

done

非交互式登录系统:(expect代替我们输入yes和密码)

上一步节我们是用交互式登录的系统,可是我们在脚本当中就不能再使用交互式了,我们需要用非交互式的方式来登录远程主机,那么怎样实现呢?原理又是什么呢?

原理与交互式登录远程主机是一样的,只不过在脚本当中我们通过expect程序代替了我们,使得我们不需要再参与交互。

我们先来说一下expect程序的使用。

yum -y install expect     #expect程序位于/usr/bin/

注意:

第一行的解释器不再是/bin/bash,而不是/usr/bin/expect

spawn是“产生”的意思,就是产生一个会话,会话的就是通过root @192.168.80.7发起的。

expect {

} 这是固定的格式,注意expect整个脚本不能有空格,只能使用tab来缩进,不然会出问题!

"yes/no"是关键词,这个非常的重要,你还记得我们通过交互式连接远程主机是会提示你是否要继续连接,这个yes/no就是从那个结尾复制过来的,这样expect程序就可以定位到在什么地方帮助我们输入原本我们要输入的内容,那么要输入什么内容呢?当然是要输入yes,所以我们在“yes/no”后面加了一个中括号,括号里面内容就是发送yes\r(\r是回车的意思),注意,中括号两端都是有空格的,那么那个exp_continue是什么意思呢?就是为了防止万一没有让我们输入"yes/no"时我们来要继续连接。好了,那么下面的password也是关键词,后面就是发送的密码。最后一点是那个interact的意思是当连接成功之后就停留在服务器端先不要断开的意思。

注意:即使这样登录了之后,然后你退出来再次登录的时候,如果不调用此脚本片断还是要手动与之交互的,此脚本片断只不过是代替你完成了交互,是一次性的,用过一次之后下次再用时,还要再次调用!而密钥认证就不是这样了,密钥认证登录过一次之后,以后登录再也不需要交互了。

好了,我们来思考一下,我们不用交互的目的是为了什么?它的应用场景是什么?

假如单位有100台服务器,需要每隔一段时间修改密码,如果手工连接然后修改密码,费时费力,我们能不能通过脚本实现,通过脚本实现100台服务器也就花费几分钟的时间。

我们怎样才能实现呢?

实现的过程要分为两个大步骤,第一个大步骤是把客户端的公钥推送到各个服务器,这就要求客户端:

第一个大步骤

  • 得知道服务器的IP地址列表(可手工输入并列出到一个文件,也可以用ping脚本进行探测)

  • 知道服务器当前的root密码(各个服务器的root密码肯定是不一样的,一般是与IP一同放置在一个文件里面)

  • 客户端需要有公钥和expect工具(这个是必需的了,这个实现起来也非常的简单)

即使是第一个大步骤也要分为多个小步骤,我们先来做第一个小步骤当中的一个可能,我们想要得知服务器的IP地址,我们先用PING探测的方式,这个ping探测并不是一个很好的方式,因为服务器是可以禁止ping的,使用arping比ping要好多了,我们先用ping实现一次,然后再用arping再实现一次。

思路:

首先通过交互的方式让用户输入将要探测的网段赋值给一个变量NET

然后使用for循环给ADDR变量赋值{1..255}

然后ping这个地址,将返回值定向到/dev/null

通过if结合$?取ping的返回值,如果$?=0的话说明是通的,就echo一个信息发送到屏幕一份,同时也发送一个到/tmp/serverhost.txt文件里面。

第一版:

[root@linuxprobe tmp]# cat ping.sh

#!/bin/bash

SERVERHOST=/tmp/serverhost.txt

>$SERVERHOST

read -p "please input currt network: " NET

for ADDR in {2..254};do

IPADDR=$NET.$ADDR

ping -c1 -W1 $IPADDR &>/dev/null

if [ $? -eq 0 ];then

echo "$IPADDR" | tee -a $SERVERHOST

fi

done

第一版有一个很大的缺点就是:慢!太慢了,这200台主机探测完成得5分钟左右,这可怎么解决呢?我们知道当脚本在执行的时候其实是又打开了一个shell,那么我们可不可以在打开的另一个shell的基础上把ping探测放置到其后台执行呢?放置后后台执行有什么好处呢?这当然是可以的,好处就是前台的shell可以专注完成循环,前台的shell把ping任务直接交给后台执行,后台运行的速度显示是要大于shell很多的,为什么后台比前台快呢?因为后台可以同时启动多个进程并发完成ping探测,而不用像前台shell那样,线性的一个一个的完成ping探测,我们怎样才能实现把ping放置到后台执行呢?这个也非常的简单,我们如果想把一个命令送到后台执行的话,就在其后面加上&符号,在脚本里也是一样,不过为了把一个片断看做是一个整体,我们通常要使用中大括号辅助,把要放置到后台的命令用大括号括起来,然后在最后加上&符号,就像下文:

第二版:(我们仅仅增加了第7行和第12行,速度非常之快,几乎是瞬间就完成了200台主机的探测)

第二版当中速度问题虽然解决了,但是我们不知道这个脚本到底有没有真正的执行完毕,我们想要一个返回值,告诉我们这个脚本确实是执行完了,这个很好解决,我们只要在脚本的最后添加一个echo "finish"不就可以了吗?这样看上去是可以的,但是往往在执行的时间,for循环还没有完成(因为被放入后台上嘛!),echo “finish”就开始执行了,没有完成就打印“完成”,这样可不行,我们可以在echo “finish”的前面加上“wait”关键字,wait在脚本当中的意思就是当前面的脚本内容统统执行完毕之后才能通过,这样的话只有for循环全部循环完成才会打印finish,这正是我们想要看到的结果。

第三版:(仅仅是在第二版的基础上加了最后两行)

执行结果是这样的:

在上面这个ping探测当中我们是使用交互的方式输入了网段,这仅仅是一个方式,我们还可以不用交互直接在脚本当中截取当前的网段或者直接定义一个网段都是可以的,由于实现起来比较简单,这里不在演示!

上面的ping探测还有一个非常的缺陷,就是有的主机可以通过防火墙禁ping,一旦禁ping,我们的脚本就探测不到对方了,我们可以把ping替换成arping,对于arp广播任何主机是没有办法拒绝的!当一个广播请求某一个主机回应时,对方主机不回应就是不在线,一旦在线必定会回应,好了接下来,我们就使用arping替换ping试一试:

第四版:(仅把ping替换成了arping)

第四版是一个比较完善的版本了,并且是生成了一个文件,文件里面保存的是当前在线的主机IP地址。

#spawn产生,产生一个会话连接通过root连接192.168.80.9

expect { #这是固定格式

"yes/no"   { sed "yes\r"; exp_continue  }

}

那么上面这个步骤通过脚本怎样实现呢?

第一步:首先我们先要明确我们我们需要连接服务器的IP地址,放置在一个文件当中,比如叫做server.addr文件

第二步:

客户端主机向

交互式公钥分发

非交互式公钥分发


猜你喜欢

转载自blog.51cto.com/13778749/2160031