linux 下通过sshpass工具做ssh远程操作避免交互式输入密码,比pexpect更方便

linux如果不做节点间的ssh互信,则通过ssh连接节点时必须输入密码,虽然可以通过pexpect等实现,但需要自己编写脚本。本文介绍sshpass工具更加方便,且提供了多种密码安全保护方案。

工具获得

下载

https://sourceforge.net/projects/sshpass/

解压后需要自行编译获得可执行文件。

解压

[root@rh6-1 ~]# tar xvf sshpass-1.08.tar.gz
sshpass-1.08/
sshpass-1.08/ChangeLog
sshpass-1.08/missing
sshpass-1.08/configure
sshpass-1.08/README
sshpass-1.08/NEWS
sshpass-1.08/depcomp
sshpass-1.08/compile
sshpass-1.08/main.c
sshpass-1.08/INSTALL
sshpass-1.08/aclocal.m4
sshpass-1.08/Makefile.in
sshpass-1.08/configure.ac
sshpass-1.08/AUTHORS
sshpass-1.08/COPYING
sshpass-1.08/sshpass.1
sshpass-1.08/config.h.in
sshpass-1.08/Makefile.am
sshpass-1.08/install-sh
[root@rh6-1 ~]# cd sshpass-1.08

配置 configure

[root@rh6-1 sshpass-1.08]# ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking whether gcc understands -c and -o together... yes
checking dependency style of gcc... gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking minix/config.h usability... no
checking minix/config.h presence... no
checking for minix/config.h... no
checking whether it is safe to define __EXTENSIONS__... yes
checking for gcc... (cached) gcc
checking whether we are using the GNU C compiler... (cached) yes
checking whether gcc accepts -g... (cached) yes
checking for gcc option to accept ISO C89... (cached) none needed
checking whether gcc understands -c and -o together... (cached) yes
checking dependency style of gcc... (cached) gcc3
checking for ANSI C header files... (cached) yes
checking for sys/wait.h that is POSIX.1 compatible... yes
checking fcntl.h usability... yes
checking fcntl.h presence... yes
checking for fcntl.h... yes
checking for stdlib.h... (cached) yes
checking for string.h... (cached) yes
checking sys/ioctl.h usability... yes
checking sys/ioctl.h presence... yes
checking for sys/ioctl.h... yes
checking for unistd.h... (cached) yes
checking termios.h usability... yes
checking termios.h presence... yes
checking for termios.h... yes
checking for an ANSI C-conforming const... yes
checking for pid_t... yes
checking for ssize_t... yes
checking vfork.h usability... no
checking vfork.h presence... no
checking for vfork.h... no
checking for fork... yes
checking for vfork... yes
checking for working fork... yes
checking for working vfork... (cached) yes
checking whether gcc needs -traditional... no
checking for stdlib.h... (cached) yes
checking for GNU libc compatible malloc... yes
checking sys/select.h usability... yes
checking sys/select.h presence... yes
checking for sys/select.h... yes
checking sys/socket.h usability... yes
checking sys/socket.h presence... yes
checking for sys/socket.h... yes
checking types of arguments for select... int,fd_set *,struct timeval *
checking return type of signal handlers... void
checking for select... yes
checking for posix_openpt... yes
checking for strdup... yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands

编译make

[root@rh6-1 sshpass-1.08]# make
make  all-am
make[1]: Entering directory `/root/sshpass-1.08'
gcc -DHAVE_CONFIG_H -I.     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
gcc  -g -O2   -o sshpass main.o
make[1]: Leaving directory `/root/sshpass-1.08'

安装

sshpass文件放到了/usr/local/bin/下面,如果操作系统版本相同,可以复制出来给其它环境使用。

[root@rh6-1 sshpass-1.08]# make install
make[1]: Entering directory `/root/sshpass-1.08'
 /bin/mkdir -p '/usr/local/bin'
  /usr/bin/install -c sshpass '/usr/local/bin'
 /bin/mkdir -p '/usr/local/share/man/man1'
 /usr/bin/install -c -m 644 sshpass.1 '/usr/local/share/man/man1'
make[1]: Leaving directory `/root/sshpass-1.08'
[root@rh6-1 sshpass-1.08]# whereis sshpass
sshpass: /usr/local/bin/sshpass

sshpass参数

其中 -f 时

[root@rh6-1 sshpass-1.08]# sshpass -h
Usage: sshpass [-f|-d|-p|-e] [-hV] command parameters
   -f filename   Take password to use from file
   -d number     Use number as file descriptor for getting password
   -p password   Provide password as argument (security unwise)
   -e            Password is passed as env-var "SSHPASS"
   With no parameters - password will be taken from stdin

   -P prompt     Which string should sshpass search for to detect a password prompt
   -v            Be verbose about what you're doing
   -h            Show help (this screen)
   -V            Print version information
At most one of -f, -d, -p or -e should be used
[root@rh6-1 sshpass-1.08]#

中文

-f filename   从文件中获取密码
-d number   使用文件描述符数字获取密码
-p password  提供明文密码作为参数(安全性不高)
-e 从环境变量“SSHPASS”读取密码
没有参数 - 密码将从标准输入中获取

-P 设定密码提示的字符串
-v 版本
-h 帮助
-V打印版本信息

执行ssh远程命令

执行单个命令

[root@gbase_rh7_001 ~]# sshpass -p 111111 ssh root@10.0.2.115 date
Wed Feb 16 14:33:43 CST 2022
[root@gbase_rh7_001 ~]#

执行多个命令

与ssh用法一致,不多解释。

[root@gbase_rh7_001 ~]# sshpass -p 111111 ssh root@10.0.2.115 date;ip addr
Wed Feb 16 14:34:49 CST 2022
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:da:df:28 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.101/24 brd 10.0.2.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::1276:3b1e:84f8:5415/64 scope link
       valid_lft forever preferred_lft forever
3: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN qlen 1000
    link/ether 52:54:00:4a:d6:8a brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
       valid_lft forever preferred_lft forever
4: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 state DOWN qlen 1000
    link/ether 52:54:00:4a:d6:8a brd ff:ff:ff:ff:ff:ff

执行scp复制文件

与执行ssh基本一样,sshpass负责输入密码等。

sshpass -p 111111 scp ./SetSysEnv.py root@10.0.2.115:/root/

密码安全保护

明文密码

通过 -p的命令行参数直接提供密码,输入明文密码。

该方案测试环境使用可以,但正式环境是不符合安全要求的。

密码文件

通过-f参数执行一个包含密码的文件,该文件的第一行是密码。

该方案可以提供代码扫描级的安全,但对系统文件级全盘扫描时,任然是明文密码。

[root@gbase_rh7_001 ~]# cat p.txt
111111
[root@gbase_rh7_001 ~]# sshpass -f p.txt ssh root@10.0.2.115 date
Wed Feb 16 14:40:58 CST 2022
[root@gbase_rh7_001 ~]#

环境变量

通过-e参数从环境变量SSHPASS读取密码。

该方案方便了多个sshpass的使用,修改密码较方便。但做系统级全盘文件扫描时,仍然是明文密码。

[root@gbase_rh7_001 ~]# export SSHPASS=111111
[root@gbase_rh7_001 ~]# sshpass -e ssh root@10.0.2.115 date
Wed Feb 16 14:45:35 CST 2022
[root@gbase_rh7_001 ~]#

文件描述符(FD file descriptor)

比如FD 0是stdin 如下是一个例子

[root@gbase_rh7_001 ~]# sshpass -d 0 ssh root@10.0.2.115 date
111111
Wed Feb 16 15:38:26 CST 2022
[root@gbase_rh7_001 ~]#

将文件发通过exec 命令送到fd 指定数字,然后读取的例子。 密码可以在某个时期解码并发送到fd里,后面使用时直接从里面读取。

[root@gbase_rh7_001 ~]# exec 4<p.txt
[root@gbase_rh7_001 ~]# sshpass -d 4 ssh root@10.0.2.115 date
Wed Feb 16 15:42:49 CST 2022
[root@gbase_rh7_001 ~]#

密码加密通过管道传输

将密码加密后保存,然后动态解密,通过管道传输。 这样密码本身不是明文,加密后不会被认为时明文密码,而动态机密只是在运行时,所以代码扫描也可以通过。

如下是用gpg加密的例子

加密

[root@gbase_rh7_001 ~]# gpg --cipher-algo AES256 -c p.txt

测试解密

[root@gbase_rh7_001 ~]# gpg -d -q p.txt.gpg
111111

管道传输密码

实际测试中,gpg -d解密时,第一次会弹出输入密钥的窗口。

[root@gbase_rh7_001 ~]# gpg -d -q p.txt.gpg | sshpass ssh root@10.0.2.115 date
Wed Feb 16 14:54:11 CST 2022

Base64等加密

加密

[root@gbase_rh7_001 ~]# echo -n 111111 | base64
MTExMTEx
[root@gbase_rh7_001 ~]# 

管道传输密码

[root@gbase_rh7_001 ~]# echo -n MTExMTEx | base64 -d|sshpass ssh root@10.0.2.115 date
Wed Feb 16 14:49:59 CST 2022