Contents

用于登录SSH服务器的Shell脚本

1. 概述

作为系统管理员,我们经常远程使用不同类型的服务器。不用说,使用本地机器上的脚本访问远程服务器成为必要。值得注意的是,有许多基于 Linux 的工具可用于从远程服务器获取数据。因此,这些被广泛用于管理数百个远程服务器以便于编排。

本教程将阐明在远程 SSH 机器上访问和执行命令的最简单方法。

现在,让我们深入了解它的本质。

2. 使用expect工具

通常,我们使用expect脚本来自动化用户交互式 CLI 提示。它通过期待 CLI 提示模式来工作,该模式无需任何用户交互即可自动发送响应。

2.1. 编写脚本

我们使用四个主要的expect命令来自动化这种交互:

  • 首先,**spawn **调用一个新的进程或会话
  • 第二、**expect **以期望的模式等待产生的进程输出
  • 第三,**发送 **写入衍生进程的stdin
  • 最后,它们将控件**交互 **回当前进程,以便将stdin发送到当前进程,随后 返回stdoutstderr

让我们看一个简单的例子:

$ cat remote_login.exp
#!/usr/bin/expect
set timeout 60
spawn ssh -p [lindex $argv 3] [lindex $argv 1]@[lindex $argv 0]
expect "*?assword" {
        send "[lindex $argv 2]\r"
        }
expect ":~$ " {
        send "hostname\r\r"
        }
expect ":~$ " {
        send "df -h | grep sd\r\r"
        }
expect ":~$ " {
        send "tail -2 /var/log/dpkg.log\r\r"
        }
expect ":~$ " {
        send "exit\r"
        }
interact

上面的代码片段显示了登录到 SSH 服务器的简单*期望脚本。*该脚本以shebang ( #! )开头,后跟绝对解释器路径。它有助于在基于 Linux 的环境中识别可执行文件的解释器

在第一行的支持下,程序加载器执行指定的解释器程序并将脚本作为第一个参数传递。建议设置超时,因为某些服务器提示加载缓慢。在这里,我们将其设置为一分钟,而无限等待时间加载远程系统。十秒是expect中的默认超时。

接下来,我们使用spawn调用一个新的 SSH 会话。lindex从位置参数列表中获取单个元素。该脚本需要远程 SSH 服务器上的四个位置参数。

  • IP 地址 [ 位置 0 ]
  • 用户名 [ 职位 1 ]
  • 密码 [ 位置 2 ]
  • SSH 端口 [ 位置 3 ]

在使用所有四个有效位置参数执行脚本后,远程服务器会询问密码。“密码”提示中的第一个字母“P”在某些系统中可能是大写字母,而在其他系统中可能是小写字母。为了消除这种歧义,我们只匹配模式的一部分,“密码”。然后,send命令将密码定向到终端stdin。此外,根据终端模式,我们不断发送命令以供执行。

在这种情况下,主机名获取远程 SSH 机器的名称。df命令提供远程服务器的磁盘使用情况。我们使用grep 命令过滤掉“ sda ”卷。最后,我们在远程服务器的日志文件中执行tail命令,并使用**exit命令关闭会话。如前所述,interact将控制权返回给当前父进程,以便将stdin映射回父进程。

2.2. 执行脚本

现在,让我们通过提供四个位置参数来执行脚本——IP 地址(10.149.20.11)、用户名(tools)、密码(abc@123)和 SSH 端口(4455):

$ ./remote_login.exp 10.149.20.11 tools abc@123 4455
spawn ssh -p 4455 tools@10.149.20.11
tools@10.149.20.11's password:
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64)
...
... output truncated ...
...
Last login: Thu Oct 21 15:55:11 2021 from 180.98.67.141
$ hostname
REMOTE-SERVER
$
$ df -h | grep sd
/dev/sda1        98G   15G   79G  16% /
$
$ tail -2 /var/log/dpkg.log
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1
$
$ exit
logout
Connection to 10.149.20.11 closed.
$

正如我们所见,该脚本在远程 SSH 服务器上执行了所有命令( hostnamedftail )。最后,使用exit命令终止本地和远程服务器之间的会话。

2.3. 使用autoexpect

我们可以使用autoexpect工具自动创建脚本,而不是使用expect脚本手动编写程序。这是因为它监视我们与另一个程序或 CLI 提示的交互,并自动创建一个完全复制我们交互的expect脚本。即使是经验丰富的老手,它也能达到最佳效果,因为它可以自动化各种无意识的交互部分。

为了便于讨论,让我们使用autoexpect工具自动创建 expect脚本。现在,我们将在远程 SSH 服务器上键入autoexpect并执行所需的命令:

$ autoexpect ssh tools@10.149.20.11 -p4455 'hostname; df -h | grep sd; tail -2 /var/log/dpkg.log'
autoexpect started, file is script.exp
tools@10.149.20.11's password:
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1
autoexpect done, file is script.exp
$

输出的第一行表明工具开始记录交互,并且script.exp存储最终的expect脚本。 随后,让我们打开文件查看expect形式的交易记录:

$ cat script.exp
#!/usr/bin/expect -f
...
... output truncated ...
...
set force_conservative 0  ;# set to 1 to force conservative mode even if
                          ;# script wasn't run conservatively originally
if {$force_conservative} {
        set send_slow {1 .1}
        proc send {ignore arg} {
                sleep .1
                exp_send -s -- $arg
        }
}
...
... output truncated ...
...
set timeout -1
spawn ssh tools@10.149.20.11 -p4455 {hostname; df -h | grep sd; tail -2 /var/log/dpkg.log}
match_max 100000
expect -exact "tools@10.149.20.11's password: "
send -- "abc@123\r"
expect eof

最后,在不提供任何输入参数的情况下,我们可以执行脚本来执行上述任务:

$ ./script.exp
spawn ssh tools@10.149.20.11 -p4455 hostname; df -h | grep sd; tail -2 /var/log/dpkg.log
tools@10.149.20.11's password:
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1
$

3. 使用sshpass

在上一节中,我们看到了如何自动化服务器之间的端到端事务。现在,我们将了解如何创建使用sshpass启用的非交互式 SSH 身份验证会话。

sshpass工具会根据登录信息自动提供 SSH 的密码,并将我们带到最终的服务器终端。该工具还有助于用户ssh交互和 shell 脚本。简单来说,sshpass可以支持但不能替代 SSH。sshpass的选项*-p提供内联密码,然后同时传递到远程服务器上的ssh*密码提示:

$ sshpass -p 'abc@123' ssh tools@10.149.20.11 -p4455 'hostname; df -h | grep sd; tail -2 /var/log/dpkg.log'; 
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64) 
...
... output truncated ...
...
Last login: Thu Oct 19 12:15:31 2021 from 180.98.67.141
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1

3.1. 使用环境变量

在上述情况下,我们在命令行中提供密码,这是不安全的,不推荐使用。为了避免这种情况,我们可以使用带有ssh标志*-eSSHPASS环境变量,或者使用带有ssh标志-f*的文件。

现在,让我们探索一下环境变量传递密码的用法:

$ export SSHPASS="abc@123"
$ echo $SSHPASS
abc@123

sshpass命令的选项*-e指示工具使用默认名称SSHPASS*作为密码引用会话环境变量:

$ sshpass -e ssh tools@10.149.20.11 -p4455 'hostname; df -h | grep sd; tail -2 /var/log/dpkg.log';
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64)
...
... output truncated ...
...
Last login: Sat Oct 23 12:14:42 2021 from 180.98.67.141
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1
$

3.2. 使用加密形式的文件

环境变量仅适用于当前会话。如果我们打开一个新会话或从当前会话注销,所有值都将丢失。不过,我们可以选择将密码引用重定向到静态文件:

$ echo 'abc@123' > .sshpasswd
$ cat .sshpasswd
abc@123

现在,让我们将密码保存在*$HOME目录中的.sshpasswd文件中。借助sshpass中的-f*选项,我们在命令中提供密码文件路径:

$ sshpass -f .sshpasswd ssh tools@10.149.20.11 'hostname; df -h | grep sd; tail -2 /var/log/dpkg.log';
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64)
...
... output truncated ...
...
Last login: Sat Oct 23 08:43:16 2021 from 180.98.67.141
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1 
$

普通静态文件是一本打开的书,任何有权访问该本地计算机的用户都可以阅读。加密密码文件以限制无条件使用是明智的。

首先,让我们使用OpenPGP 加密和签名工具对*.sshpasswd文件进行加密。gpg的选项-c将使用密码短语使用对称密码加密密码。默认情况下,它使用 AES-128,但我们可以使用–cipher-algo*选项更改它:

$ echo 'abc@123' > .sshpasswd
$
$ gpg -c .sshpasswd
gpg: keybox '/home/tools/.gnupg/pubring.kbx' created
$
$ ls -la | grep sshpasswd
-rw-rw-r-- 1 tools tools   10 Oct 23 08:59 .sshpasswd
-rw-rw-r-- 1 tools tools   90 Oct 23 08:59 .sshpasswd.gpg

创建*.sshpasswd.gpg文件后,我们可以从机器中删除.sshpasswd*文件:

$ rm -f .sshpasswd
$ ls -la | grep sshpasswd
-rw-rw-r-- 1 tools tools   90 Oct 23 08:59 .sshpasswd.gpg

现在,当我们尝试打开*.sshpasswd.gpg*时,它将采用加密形式:

$ cat .sshpasswd.gpg
-}▒▒▒Iſ▒Y▒▒:▒=8E0▒▒▒▒▒ 
▒▒:i(▒▒+▒mM▒},w.b=zi▒!▒▒ݴ▒t▒▒n▒▒*▒▒▒_YB9▒F▒7

但是,我们可以使用gpg命令解密文件中的文本:

$ gpg -d -q .sshpasswd.gpg
abc@123

现在,让我们整合gpgsshpassssh

$ gpg -d -q .sshpasswd.gpg > inlinepass; sshpass -f inlinepass ssh tools@10.149.20.11 'hostname; df -h | grep sd; tail -2 /var/log/dpkg.log';
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64)
...
... output truncated ...
...
Last login: Sat Oct 23 08:58:39 2021 from 180.98.67.141
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1

首先,密码被解密并复制到inlinepass变量中。此外,sshpass 使用此变量将密码转发到ssh会话登录提示以成功登录。最后,会话认证发生,系统执行命令。

4. 无密码SSH

在本节中,我们将简要介绍在服务器之间创建无密码ssh,它进一步简化了我们登录远程 SSH 服务器的工作。

4.1. 公钥和私钥生成

首先,让我们通过为该机器中的特定用户创建公共和私有 RSA 密钥对来设置机器之间的无密码ssh 。使用ssh-keygen命令,我们可以轻松地在id_rsa文件中制作私钥,在id_rsa.pub中制作公钥。这些文件位于*$HOME路径中的.ssh*目录下:

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/localuser/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/localuser/.ssh/id_rsa.
Your public key has been saved in /home/localuser/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:d9AGZC4DdUPcgOwoX6duD65wxjFk2X7HU1gXWLjYxFg localuser@LOCAL-MACHINE
The key's randomart image is:
+---[RSA 2048]----+
|      .o.=Oo+E=oo|
...
... output truncated ...
...
+----[SHA256]-----+

4.2. 密钥交换和执行

现在,让我们将本地机器公钥上传到远程机器。在ssh-copy-id命令的帮助下,它可以随 OpenSSH 客户端软件包一起提供。执行此命令后,本地机器的公钥会自动保存在远程服务器的*.ssh/authorized_keys文件下:*

$ ssh-copy-id tools@10.149.20.11 -p4455
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/tools/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
tools@10.149.20.11's password:
Number of key(s) added: 1
Now try logging into the machine, with:   "ssh -p '4455' 'tools@10.149.20.11'"
and check to make sure that only the key(s) you wanted were added.

一旦底层密钥交换到位,我们就可以在远程服务器上执行命令或脚本:

$ ssh -p '4455' 'tools@10.149.20.11’
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64)
...
... output truncated ...
...
tools@10.149.20.11:~$ exit
logout
Connection to 10.149.20.11 closed.
$

在无密码ssh选项的帮助下,每当我们尝试使用 SSH 协议访问远程服务器时,它将不再要求输入密码:

$ ssh tools@10.149.20.11 'hostname; df -h | grep sd; tail -2 /var/log/dpkg.log';
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-87-generic x86_64)
...
... output truncated ...
...
REMOTE-SERVER
/dev/sda1        98G   15G   79G  16% /
2021-10-19 05:17:21 status half-configured man-db:amd64 2.8.3-2ubuntu0.1
2021-10-19 05:17:24 status installed man-db:amd64 2.8.3-2ubuntu0.1
$