在Linux中使用密码自动进行ssh登录的4种有用方法
如何使用密码自动进行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
并禁用其他类型的身份验证方法。