我想要实现的是连接到服务器 A 并执行一个脚本,该脚本连接到多个其他服务器(串行),并在每台服务器上执行一项任务。虽然我可以使用 执行该任务ssh -A
,但该任务需要几天才能完成,我想使用 GNU screen
。
因此,我使用 登录到服务器ssh -A
,打开一个屏幕会话并执行成功连接到第一台服务器的脚本。只要我保持会话screen
打开,脚本就可以毫无问题地连接到下一台服务器。如果我关闭会话screen
,当服务器 X 上的任务完成并继续到服务器 X+1 时,它会因身份验证失败而失败,当我重新连接到我的屏幕会话时,我发现一切都失败了。
所有服务器都需要相同的密钥才能连接,有没有办法在当前屏幕会话中“缓存”凭据并让屏幕在后台运行?
请注意,我已经阅读了有关的问题ssh
,screen
但我想要的不是重新连接,而是让它运行一周,当我重新连接会话时获得结果。
答案1
总结
请参阅下面的“简易程序”部分。
分析
ssh -A
只要连接运行并且代理在本地计算机上运行,您所做的操作ssh
就有效。您的凭据永远不会离开本地计算机,这就是转发代理的全部意义所在。远程进程通过隧道与本地代理通信并向其传递挑战。隧道和工作代理至关重要;如果其中任何一个终止,远程进程将无法进行身份验证。这就是您断开连接后一切都失败的原因。
所有服务器都需要相同的密钥才能连接,有没有办法在当前屏幕会话中“缓存”凭据并让屏幕在后台运行?
从技术上来说,这是可行的。您可以转发偏僻的将身份验证代理连接到本地计算机并“借用”您的本地密钥。该方法的核心是,即使您断开连接,远程代理仍可用于您的远程脚本。
ssh -A
如果您可以从远程服务器返回到本地计算机,那么将会更容易一些。此答案介绍了两个过程:
- “简单的程序”。当您可以
ssh -A
从远程服务器返回到本地计算机时,您可以使用它。 - “正常程序”。无论您的本地计算机上是否有 SSH 服务器,它都应该可以工作。
简单的程序
本节刻意简洁。我建议您阅读整个答案并了解我们在做什么,即使简单的程序对您有用。
此过程不需要:
- 任何本地身份验证代理,
- 任何额外的本地终端(一切都在一个本地终端中工作)。
此过程需要:
- 本地 SSH 服务器,
- 能够
ssh-agent
在远程端启动(程序将启动一个), - 您的密钥保存在本地文件中。
步骤:
ssh
到遥控器。不要使用-A
:# on local ssh user@remote
在此步骤中,您可以创建一个隧道(例如
-R 2222:localhost:22
),如果需要的话,它将允许您ssh
返回下一步。开始
ssh-agent
在遥控器上:# on remote eval "$(ssh-agent)"
ssh
返回本地。使用-A
:# on remote ssh -A localuser@back-to-local
如果通过隧道那么它将像
ssh -A -p 2222 localuser@localhost
。将本地密钥加载到远程代理:
# back on local ssh-add
如果您愿意,可以添加非默认键。
退出“返回本地”shell:
# back on local exit
像在 (本地) 之后的原始方法中一样在远程 shell 中继续
ssh -A
:运行screen
、脚本等;断开连接。当您稍后重新连接并发现所有操作均已完成且不再需要代理时,请终止该代理:
# on remote ssh-agent -k
简易程序与正常程序
在简单的过程中(上面),我们使用附加功能ssh -A localuser@back-to-local
将远程身份验证代理暂时转发到本地计算机。
正常程序(如下)会创建一个嵌入常规ssh user@remote
连接的隧道,本质上是出于同样的目的。还有一些用于“管道”和维护的额外技术命令。
正常程序
此过程不需要:
- 任何本地身份验证代理,
- 任何本地 SSH 服务器。
此过程需要:
- 一个额外的本地终端(shell),
- 能够
ssh-agent
在远程端启动(程序将启动一个), - 您的密钥放在本地文件中(因此,如果您的原始设置依赖于从其他地方获取密钥的本地身份验证代理,那么这个答案对您不起作用)。
在整个过程中,我假设有一个恒定的本地工作目录和一个恒定的远程工作目录,我将对其进行标记/home/me/
(您需要用您的真实远程工作目录替换此字符串)。
确保在本地机器上
ssh-agent-socket
不存在。这是一个任意名称,您可以根据需要更改它,但是一旦您选择了名称,就坚持使用它。如果需要,请删除该文件:# on local rm -f ssh-agent-socket
从本地通过 SSH 连接到远程。不要使用
ssh -A
。用于ssh -L
从本地套接字到远程符号链接 建立隧道ssh-agent-symlink
。这是另一个任意名称,这次是远程端的文件。如果此文件尚不存在,也没关系。这是命令:# on local ssh -L ./ssh-agent-socket:/home/me/ssh-agent-symlink user@remote
该命令应该创建一个名为的本地套接字
ssh-agent-socket
。我们稍后会使用它。假设上述命令有效,现在您就在远程机器的 shell 中。开始
ssh-agent
那里:# on remote eval "$(ssh-agent)"
仍在远程计算机上,创建指向新启动的代理使用的套接字的符号链接。请注意,我们需要事先删除所有旧的符号链接:
# on remote cd /home/me/ rm -f ssh-agent-symlink ln -s "$SSH_AUTH_SOCK" ssh-agent-symlink
我们之所以需要这个符号链接,只是因为我选择事先创建隧道(
ssh -L
),当时我们不知道套接字的位置。可以在运行后创建隧道ssh-agent
并将其直接指向正确的远程套接字。如果您选择这样做,那么您将不需要符号链接,但在创建隧道时,您可能需要从远程“传输”(手动输入或复制粘贴)路径echo "$SSH_AUTH_SOCK"
。由于符号链接的存在,这个答案中的代码更加静态,所以让我们坚持下去。打开另一个本地终端(shell)并...
请注意,此时您无法
screen
在远程启动、断开连接并在同一个本地终端中工作。我们仍然需要隧道工作,ssh
不能中断。最简单的方法是在此步骤中使用单独的本地终端。…然后导航 (
cd
) 到目录ssh-agent-socket
。得益于隧道和远程端的符号链接,套接字现在通向远程身份验证代理。连接到套接字的任何本地工具最终都会与远程代理对话。将正确的变量设置为正确的值,这样ssh-add
(我们稍后会用到它)就可以找到套接字:# on local export SSH_AUTH_SOCK="$PWD/ssh-agent-socket"
将本地密钥添加到远程代理。以下命令从默认位置添加密钥;如果需要,请修改命令。这与将密钥添加到本地代理没有什么不同,因为无论哪种情况,命令都会连接到某个套接字,并且它不关心它通向哪里,只要另一端有某个身份验证代理即可。
# on local ssh-add
验证代理是否持有正确的密钥。此命令应在两台计算机上提供相同的输出,因为它咨询的是同一个代理:
# on local or on remote ssh-add -l
您想要的密钥应该会列出。请注意,当在远程计算机上运行时,该命令仍会显示密钥的本地路径。没关系,代理将路径存储为附加信息,并且它实际上并不知道它们来自哪台计算机。
清理(可选)。删除远程符号链接和本地套接字:
# on remote rm ssh-agent-symlink
# on local rm ssh-agent-socket
不再需要额外的本地 shell。您现在可以关闭它。
如果您知道如何操作,您还可以重新配置仍在运行的隧道
ssh
并关闭隧道,但这并不重要,我不会在这里解释该方法;该隧道不再需要,但它可能会保留。在远程 shell 中继续执行 (local) 之后的操作,就像使用原始方法一样
ssh -A
。设置几乎相同:您在远程服务器上,并且有一个可以通过$SSH_AUTH_SOCK
环境变量访问的身份验证代理,该代理持有正确的密钥。一切都应该像以前一样工作,除了……不同之处在于代理在远程系统中运行,不依赖任何 SSH 隧道。因此,如果您在设置
screen
和完成之后断开连接,您的进程仍将能够与代理通信。当您稍后重新连接并发现所有操作均已完成且不再需要代理时,请终止该代理:
# on remote ssh-agent -k
请注意,这只会在权限位于
$SSH_AGENT_PID
环境中的 shell 中起作用,因此基本上是在screen
您创建的会话中,而不是在任何新的 shell 中。让代理继续运行是错误的。这不仅是不再需要的进程;它仍然持有你的本地密钥,因此让它运行的时间超过必要时间是不明智的。
考虑自动化。我的意思是,如果您通常使用 运行远程脚本,
./run-my-scripts
那么现在您应该运行./run-my-scripts; ssh-agent -k
,这样脚本一完成,代理就会得到处理。
安全注意事项
将本地代理转发给远程代理
ssh -A
或“借用”本地密钥给远程代理(如我们的情况)比将本地密钥作为文件复制到远程系统更安全。看看man 1 ssh
说:应谨慎启用代理转发。能够绕过远程主机上的文件权限(对于代理的 UNIX 域套接字)的用户可以通过转发的连接访问本地代理。攻击者无法从代理获取密钥材料,但他们可以对密钥执行操作,使他们能够使用加载到代理中的身份进行身份验证。[…]
虽然没有明确说明,但这种担忧是有道理的任何身份验证代理,不仅限于 的情况
ssh -A
。之所以考虑 ,是ssh -A
因为之前ssh -A
您通常会处理从同一系统中的常规文件加载密钥的代理;因此,如果攻击者(例如具有 root 访问权限的不诚实管理员)可以绕过套接字的文件权限,那么他们可能可以绕过密钥的文件权限并首先获取密钥。访问密钥更有价值,因为人们可以复制密钥文件并在以后使用它。访问代理的价值较低,因为人们只能在代理运行时使用代理进行身份验证。在我们的例子中,远程代理持有远程操作系统中不存在的本地密钥,因此情况类似于常规情况,
ssh -A
即使在我们不使用的“正常程序”中也是如此ssh -A
:远程端没有宝贵的文件可供窃取,攻击者可能会尝试获取对代理的不太宝贵的访问权限。但是,我们的情况比常规的从本地到远程的情况更不安全
ssh -A
,因为远程攻击者可能会试图窥探正在运行的代理的内存,密钥就在那里。在ssh -A
代理在本地计算机中运行的常规情况下,他们无法利用这种攻击角度。换句话说:我们的程序(“简单”或“正常”)做将你的密钥复制到远程系统,但至少复制到记忆远程ssh-agent
进程,而不是远程文件系统。ssh-agent-socket
如果隧道仍然有效,并且远程代理仍然有效,并且他们在您移除套接字或远程符号链接之前启动连接,则任何具有访问权限的人都可以在本地使用该代理。如果您是唯一的用户,那么它是安全的。如果有其他用户,那么您最好ssh-agent-socket
在其他人无权访问的目录中工作(即创建)。ssh-agent-socket
根据我们的ssh -L
报告,它确实被创建为rw-------
(600
),但我不确定它是否是这样创建的。我无法判断在创建之后是否存在一个时间窗口,当时模式可能没有那么严格,并且它“立即”被修复了。当模式限制较少时,这可能是攻击者的机会之窗。您的问题已加标签Linux的在 Linux 中,套接字的权限很重要。一般来说,它们可能无关紧要。请参阅人 7 unix:
在 Linux 上,连接到流套接字对象需要该套接字的写入权限;向数据报套接字发送数据报同样需要该套接字的写入权限。POSIX 未对套接字文件权限的影响做出任何声明,并且在某些系统(例如较旧的 BSD)上,套接字权限会被忽略。可移植程序不应依赖此功能来确保安全性。
无论如何,当本地用户较多时,我们最好创建一个私有目录并
mktemp -d
在其中工作。mktemp -d
创建一个从一开始就无法被其他人访问的目录。在“简单程序”中,应该自动在私有目录中创建本地套接字(sshd
它本身会完成工作mktemp -d
),因此您不必担心。对于可以绕过文件权限的本地攻击者(例如具有 root 访问权限的不诚实管理员),所有这些都无关紧要,但对于防范恶意普通用户而言,这很重要。
在远程端,您可以免受恶意普通用户的攻击,至少在 Linux 上是如此。即使您将其放在
ssh-agent-symlink
其他人可以访问的目录中,目标文件的模式也很重要;在我们的例子中:由 创建的套接字ssh-agent
。此套接字应该已经得到保护(在我的 Debian 中,它是在私有临时目录中创建的,ssh-agent
它本身就完成了 的工作mktemp -d
)。似乎有些系统对符号链接的权限很重要如有疑问,请修改程序以便在私有目录中创建远程符号链接;或者之后创建隧道ssh-agent
并直接指向远程套接字,这样就不需要符号链接了。