在Linux中使用密码自动进行ssh登录的4种有用方法

时间:2020-02-23 14:40:31  来源:igfitidea点击:

如何使用密码自动进行SSH登录?
如何将密码传递给bash脚本中使用的scp命令?
如何使用密码而不是私钥执行SSH登录?
如何将密码传递给bash脚本中使用的scp命令?

在本教程中,将介绍不同的方法,我们可以使用这些方法通过shell脚本或者从终端通过ssh和scp到目标节点使用密码。

方法1:使用expect用密码代替密钥来执行ssh

我们可以利用期望以自动方式提供密码,而无需用户在shell脚本中进行提示。
当涉及到需要用户输入的自动化方面,Expect是一个非常方便的工具。
尽管这样做的缺点是任何人都可以打开脚本并检查密码,因为密码将采用纯文本格式。

因此,如果我们不关心安全性,可以使用带有密码的ssh。

提示:

我将在本教程中使用dnf/yum作为包管理器的所有示例输出中使用RHEL分发。
如果我们使用的是Ubuntu/Debian,则可以使用apt安装/更新rpm

如果服务器上尚未安装,请安装"期望"。

~]# dnf install expect

方案1:使用单独的Expect脚本

以下是一个示例Expect脚本,该脚本可用于执行SSH和执行命令,该脚本内部提供了密码:

1 #!/usr/bin/expect
  2
  3 set USER [lindex $argv 0]
  4 set HOST [lindex $argv 1]
  5 set PWD [lindex $argv 2]
  6 log_file /var/log/ssh_tmp.log
  7
  8 set timeout  30
  9 log_user  1
 10 set send_slow {1 .01}
 11
 12 send_log  "Connecting to $HOST using $USER user\n"
 13 eval spawn ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o Connecttimeout =30 "$USER\@$HOST"
 14 expect  {
 15         timeout       { send_user  "timeout  while connecting to $HOST\n"; exit }
 16         "*No route to host*" { send_user  "$HOST not reachable\n"; exit }
 17         "*assword: " { send -s $PWD\r }
 18         }
 19 expect  {
 20         timeout  { send_user  "timeout  waiting for prompt\n"; exit }
 21         "*]#"   { send_user  "Login successful to $HOST\n" }
 22         }
 23 send "hostname\r"
 24 expect  {
 25         "*]#"   { send "exit\r" }
 26         }
 27 send_user  "Disconnected\n"
 28 close
  • 第3行收集用户名作为第一个输入参数

  • 第4行收集主机名作为第二个输入参数

  • 第5行收集密码作为第三个输入参数

  • 第6行,我们定义了日志文件来存储脚本的输出

  • 第8行为脚本定义了超时

  • "第9行"我们正在控制台上打印输出。
    如果我们想抑制它,则使用log_user 0

  • 在"第10行"中,我们定义了发送输出的速度。
    这对于控制输入缓冲区的速度较慢的系统可能会有所帮助,或者我们可以选择忽略它。

  • 第12行,我们使用send_user将消息记录到STDOUT以及日志文件中。
    默认情况下,send_user不会在末尾添加新行,因此我们添加了" \ n"

  • "第13行"我们使用" spawn"向目标主机启动SSH。
    我们有意禁用StrictHostChecking以避免在屏幕上出现其他提示。

  • "第14行"开始"期望"过程。
    在此框中,我们添加了三个可能的条件

  • 第15行:在第一种情况下,我们希望收到一条"超时"消息,在该消息中,我们将记录并打印事件并退出

  • "第16行"在第二种情况下,我们希望没有指向主机的路由提示,这意味着目标服务器不可达。
    在这种情况下,我们还将记录并打印消息并退出

  • "第17行"在第三种情况下,我们希望得到提示,其中P可以是小写或者大写字母,因此我们使用* assword支持密码和password。
    如果找到此提示,则我们发送用户密码。
    我们使用\ r,它被称为回车,它在执行后将光标移动到行的开头

  • "第19行"我们开始新的"期望"状态

  • "第20行"我们再次期望可能的"超时",在此我们将记录并打印一条消息并退出

  • 我们希望在"第21行"出现提示,在该提示中我们将记录并打印成功消息

  • "第23行"我们发送"主机名"命令

  • "第24-26行"我们在新的Expect块中发送退出命令

  • "第27行"我们打印并记录一个偶数以供注销

  • "第28行",我们关闭了预期的会议

让我们执行脚本并验证输出:

~]# expect ssh_with_pwd.exp root 192.168.43.154 redhat

我们可以在/var/log/ssh_tmp.log中找到相同的输出,该输出在脚本中已定义为我们的日志文件。

方案2:在bash脚本中使用Expect

现在,这是一个单独的Expect脚本,但是我们可以选择将其集成到如下所示的Shell脚本中。
我已经将输入参数移到了bash脚本中,而不是expect,但这完全取决于我们,迄今为止没有这种限制。

方案3:使用Expect使用密码执行scp

我们也可以使用Expect脚本将文件传输到另一台服务器,而不会出现密码提示。
这是一个使用单独的Expect脚本将/tmp/src_file传输到目标主机的示例:

#!/usr/bin/expect
set USER [lindex $argv 0]
set HOST [lindex $argv 1]
set PWD [lindex $argv 2]
log_file /var/log/ssh_tmp.log
set timeout 30
log_user 1
set send_slow {1 .01}
send_log "Connecting to $HOST using $USER user\n"
eval spawn scp  -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=30 /tmp/src_file "$USER\@$HOST:/tmp/"
expect  {
        timeout      { send_user "timeout while connecting to $HOST\n"; exit }
        "*No route to host*" { send_user "$HOST not reachable\n"; exit }
        "*assword: " { send -s $PWD\r }
        }
interact

其中由于我们只打算复制文件,因此只有一个期望块,最后我们使用"交互"返回到终端

方法2:使用sshpass通过SSH提供密码

sshpass是一种实用程序,旨在使用称为"键盘交互"密码验证的模式运行ssh,但处于非交互模式。
该rpm是EPEL存储库的一部分,不需要执行SSH的密钥。

确保在服务器上安装了EPEL存储库:

~]# rpm -q epel-release
epel-release-8-8.el8.noarch

如果尚未安装,则可以使用以下命令进行安装:

~]# dnf -y install epel-release

我们可以在存储库中搜索此软件包

~]# dnf search sshpass
Last metadata expiration check: 0:00:27 ago on Thu 17 Sep 2017 01:25:51 PM IST.
===================================== Name Exactly Matched: sshpass =====================================
sshpass.x86_64 : Non-interactive SSH authentication utility

让我们快速安装此rpm进行演示:

~]# dnf install sshpass -y

方案1:使用sshpass提供明文密码

在此示例中,我们为sshpass提供了纯文本密码,该密码非常不安全,因为任何有权访问服务器和历史记录的用户都可以看到该密码。

语法:

sshpass -p <PASSWORD> ssh USER@HOST [COMMANDS]

如果我们希望检查远程服务器的"主机名",那么命令将是:

~]# sshpass -p redhat ssh [email protected] hostname
rhel-8.example.com

方案2:使用sshpass执行scp

通过结合使用scp和sshpass,也可以在没有密码提示的情况下传输文件。
在这个例子中,我们使用scp和sshpass将文件/tmp/src_file复制到我们的远程服务器。

~]# sshpass -p 'redhat' scp /tmp/src_file [email protected]:/tmp/

这里的" redhat"是我的密码。

方案3:在sshpass中使用文件描述符

这是使用" sshpass"的"最推荐"方法,其中我们提供密码作为文件描述符而不是纯文本。
这部分代码摘录来自stackoverflow

# Create a pipe
PIPE=$(mktemp -u)
mkfifo -m 600 $PIPE
# Attach it to file descriptior 3
exec 3<>$PIPE
# Delete the directory entry
rm $PIPE
# Write your password in the pipe
echo 'my_secret_password' >&3
# Connect with sshpass -d
sshpass -d3 ssh user@host
# Close the pipe when done
exec 3>&

我们可以将其放在脚本中,并使用它来安全地登录到服务器,而不会出现密码提示。

还有许多其他方法可以使用sshpass,例如使用包含密码的文本文件,定义变量SSHPASS并使用它代替纯文本密码。
我们可以从sshpass的手册页中获取所有这些信息。

方法3:使用专用公共密码短语代替密码

如果要自动执行SSH进程,这绝对是最推荐使用ssh的方法。
我已经写了一篇非常详尽的文章,介绍基于SSH公钥的身份验证的工作原理。

SSH有6种不同类型的身份验证方法,但本教程我们现在将重点介绍基于公钥的身份验证。

提示:

由于我已经解释了公共私钥组合的工作原理,因此其中我将对这些命令进行简短的介绍。

方案1:创建密码少的密码来执行SSH

在此示例中,我们创建了一个无密码的密钥对,以便SSH不会提示我们输入任何密码。
使用ssh-keygen生成带有-P""的密钥对,以提供一个空密码

~]# ssh-keygen -t rsa -P ""
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:m9TcEC+9H53ObDbnC/Tp5OnNd9ztwv+x3LskMMf1wZI [email protected]
The key's randomart image is:
+---[RSA 3072]----+
|          .      |
|           +  o  |
|          o oE + |
|         o +..o.+|
|        S ooo+..o|
|       . o  =.=..|
|        o    +.#+|
|              &o#|
|              .#/|
+----[SHA256]-----+

现在,将公共密钥复制到我们希望通过SSH连接到的远程节点。
最好的复制公钥的方法是使用ssh-copy-id,因此我们不必担心权限,路径等问题。

~]# ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.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
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

因此,现在我们无需任何密码就可以SSH到目标服务器192.168.43.10.

~]# ssh [email protected] hostname
rhel-8.example.com

如果遇到任何问题,请确保在目标节点的"/etc/ssh/sshd_config"中将" PubkeyAuthentication"设置为" yes",在这种情况下为" 192.168.43.10"。

方案2:使用密码自动执行SSH

在前面的场景中,我们为密钥对设置了空密码,但是如果我们设置了密码,该怎么办。
在这种情况下,脚本仍会提示我们输入密码。
我们无法避免这种情况,但是如果我们有多个主机,那么我们可以确保仅收到一次密码提示,并且SSH将继续连接所有主机。
现在,只有在所有这些主机都配置了相同的私有公钥对的情况下,才有可能。

我们生成一个基于密码短语的密钥对,其中我们的密码短语将为redhat

[root@server ~]# ssh-keygen -t rsa -P "redhat"
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Z0jNJwJQwGpXHsQGJXuDhBHcVKLUK0m7pPoq/7I3voo [email protected]
The key's randomart image is:
+---[RSA 3072]----+
| .+OOX*          |
| .+o+==. o       |
| ..+o=o.o + .    |
|  B o..o o o     |
| + +    S o      |
|. .      o       |
|.                |
|o.. o            |
|E+=B+o           |
+----[SHA256]-----+

接下来,我们使用ssh-copy-id将这个公钥复制到远程服务器的~/.ssh/authorized_keys文件中。

[root@server ~]# ssh-copy-id [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.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
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with:   "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

现在,让我们验证登录并确保我们收到密码提示

[root@server .ssh]# ssh [email protected]
Enter passphrase for key '/root/.ssh/id_rsa':
Last failed login: Thu Sep 17 13:52:02 IST 2017 from server on ssh:notty
Last login: Thu Sep 17 13:38:35 2017 from server
[root@rhel-8 ~]#
[root@rhel-8 ~]# logout
Connection to 192.168.43.10 closed.

因此,我们的连接正在按预期工作。
现在通过脚本自动执行此操作,我们将创建一个ssh代理并将其绑定到我们在连接到远程服务器时使用的私钥

[root@server ~]# eval `ssh-agent` ssh-add /root/.ssh/id_rsa
Agent pid 60251
Enter passphrase for /root/.ssh/id_rsa:
Identity added: /root/.ssh/id_rsa ([email protected])

创建了一个新的ssh代理PID60251. 因此,只要此PID在运行,我们现在就可以SSH到远程服务器而无需添加任何密码。
我们可以验证相同:

[root@server ~]# ssh [email protected]
Last login: Thu Sep 17 15:01:00 2017 from server
[root@rhel-8 ~]# logout
Connection to 192.168.43.10 closed.

完成后,我们可以终止ssh-agent进程

[root@server ~]# kill -9 60251

如果我们希望将其合并到脚本中,那么我其中编写了一个shell脚本,该脚本在使用或者创建新的ssh代理之前执行多次检查。

#!/bin/bash
declare _SSH_AGENT_BINARY=/usr/bin/ssh-agent
declare _SSH_AGENT_LIFETIMESEC=3600
declare _SSH_ENV="$HOME/.ssh/environment"
declare SYSTEM_AGENT=""
function create_env {
  # Create $HOME/.ssh if not present
  if [ ! -e `dirname $_SSH_ENV` ]
  then
    mkdir -p `dirname $_SSH_ENV` > /dev/null 2>&1
    chmod 0700 `dirname $_SSH_ENV` > /dev/null 2>&1
    touch $_SSH_ENV > /dev/null 2>&1
  fi
}
function _launch_ssh_agent ()
{
  # launch ssh agent and comment the echo statement
  $_SSH_AGENT_BINARY -t $_SSH_AGENT_LIFETIMESEC | sed 's/^echo/#echo/' > $_SSH_ENV
  [[ $? -ne 0 ]] && echo "failed to launch ssh-agent" &&  return 1
  chmod 0600 $_SSH_ENV > /dev/null 2>&1
  source $_SSH_ENV > /dev/null 2>&1
  return 0
}
function _use_ssh_agent ()
{
  # Check for existing agent (if running) do not launch another agent
  if [ ! -z "$SSH_AGENT_PID" ]; then
    ps -p $SSH_AGENT_PID -opid= | grep -q $SSH_AGENT_PID
    if [[ $? -eq 0 ]];then
       echo "An ssh agent with $SSH_AGENT_PID PID is already running, not creating a new agent"
       SYSTEM_AGENT=YES
       return 0
    fi
  fi
  # If environment file contains PID then check if the PID is running
  # accordingly launch new ssh agent
  # if file is not present and generate new agent PID
  if [ -f $_SSH_ENV ]; then
    source $_SSH_ENV > /dev/null 2>&1
    ps -p $SSH_AGENT_PID -opid= | grep -q $SSH_AGENT_PID
    [ $? -ne 0 ] && _launch_ssh_agent
  else
    _launch_ssh_agent
    [ $? -ne 0 ] && return 1
  fi
  return 0
}
function _ssh_add
{
  _use_ssh_agent
  [ $? -ne 0 ] && return 1
  ssh-add  > /dev/null 2>&1
  [[ $? -ne 0 ]] && exit "failed to add  to ssh-agent" && return 1
  return 0
}
# Create the environment (if not present)
create_env
# Provide the path of the private key
_ssh_add /root/.ssh/id_rsa
echo "Doing ssh"
ssh [email protected] hostname

# Kill the ssh agent
if [[ $SYSTEM_AGENT == YES ]];then
   echo "used system generated agent, can't kill this one.."
else
   [[ -f $_SSH_ENV ]] && source $_SSH_ENV
   [ ! -z ${SSH_AGENT_PID} ] && kill -9 ${SSH_AGENT_PID} 2>/dev/null
fi

其中

  • 我检查~/.ssh目录,如果找不到,则使用create_env函数创建相同目录

  • 如果我们/Linux服务器上的某人已经启动了ssh-agent,则PID将存储在$SSH_AGENT_PID文件中,因此我们检查此PID是否正在运行。
    如果发现此PID处于运行状态,则我们不会创建新的代理并使用现有的代理

  • 如果来自$SSH_AGENT_PID的PID不在运行状态,那么我们启动一个新的代理并将其添加到~/.ssh/environment文件中

  • 接下来,我们添加之前创建的ssh-agent或者已经存在的ssh-agent

  • 现在我们可以执行SSH活动,即我们可以使用相同的公钥连接到任意数量的主机,并且不会出现多个密码提示

  • SSH活动完成后,我们将终止ssh代理。
    但是,如果ssh代理不是由我们的脚本创建的,那么我们将其保持原样。

方法4:使用PSSH和PSCP

我们还有一个非常方便的用python语言编写的工具,即PSSH和PSCP,我们只需在终端上输入一次密码即可连接到多个主机。

我们还可以使用私有公钥对设置少密码登录,然后使用公钥无需任何密码即可连接到远程服务器。

PSSH是我们在上一节中已经安装的EPEL存储库的一部分,因此我们将仅安装PSSH。

~]# dnf -y install pssh

接下来,我们将生成一个密码少私有私钥对

~]# ssh-keygen -t rsa -P ""
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
/root/.ssh/id_rsa already exists.
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:EyUcATZszzFr1b6+f5fZc8vAbvRsfpCcbIlsL5oC2Yo [email protected]
The key's randomart image is:
+---[RSA 3072]----+
|     .+o+o..     |
|     .o.+o. .    |
|     . o.= .     |
|        =.  .    |
|       +S  . = + |
|      o ..  *.O  |
|     . o   o.=o.+|
|    E . .  .+.+B*|
|         .o.o=+=*|
+----[SHA256]-----+

现在,我们必须创建一个ssh代理并将其绑定到PSSH将使用的私钥:

~]# eval `ssh-agent` ssh-add /root/.ssh/id_rsa
Agent pid 67300
Identity added: /root/.ssh/id_rsa ([email protected])

现在,我们可以使用PSSH连接到远程服务器,而不必担心密码:

~]# pssh -i -H "192.168.43.10" -l root -x "-o StrictHostKeyChecking=no -o GSSAPIAuthentication=no -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes" hostname
[1] 16:34:13 [SUCCESS] 192.168.43.10
rhel-8.example.com

其中我使用了多个SSH选项仅使用PubKeyAuthentication并禁用其他类型的身份验证方法。