SSH 转发中的密钥缓存

SSH 转发中的密钥缓存

我想要实现的是连接到服务器 A 并执行一个脚本,该脚本连接到多个其他服务器(串行),并在每台服务器上执行一项任务。虽然我可以使用 执行该任务ssh -A,但该任务需要几天才能完成,我想使用 GNU screen

因此,我使用 登录到服务器ssh -A,打开一个屏幕会话并执行成功连接到第一台服务器的脚本。只要我保持会话screen打开,脚本就可以毫无问题地连接到下一台服务器。如果我关闭会话screen,当服务器 X 上的任务完成并继续到服务器 X+1 时,它会因身份验证失败而失败,当我重新连接到我的屏幕会话时,我发现一切都失败了。

所有服务器都需要相同的密钥才能连接,有没有办法在当前屏幕会话中“缓存”凭据并让屏幕在后台运行?

请注意,我已经阅读了有关的问题sshscreen但我想要的不是重新连接,而是让它运行一周,当我重新连接会话时获得结果。

答案1

总结

请参阅下面的“简易程序”部分。


分析

ssh -A只要连接运行并且代理在本地计算机上运行,​​您所做的操作ssh就有效。您的凭据永远不会离开本地计算机,这就是转发代理的全部意义所在。远程进程通过隧道与本地代理通信并向其传递挑战。隧道和工作代理至关重要;如果其中任何一个终止,远程进程将无法进行身份验证。这就是您断开连接后一切都失败的原因。

所有服务器都需要相同的密钥才能连接,有没有办法在当前屏幕会话中“缓存”凭据并让屏幕在后台运行?

从技术上来说,这是可行的。您可以转发偏僻的将身份验证代理连接到本地计算机并“借用”您的本地密钥。该方法的核心是,即使您断开连接,远程代理仍可用于您的远程脚本。

ssh -A如果您可以从远程服务器返回到本地计算机,那么将会更容易一些。此答案介绍了两个过程:

  • “简单的程序”。当您可以ssh -A从远程服务器返回到本地计算机时,您可以使用它。
  • “正常程序”。无论您的本地计算机上是否有 SSH 服务器,它都应该可以工作。

简单的程序

本节刻意简洁。我建议您阅读整个答案并了解我们在做什么,即使简单的程序对您有用。

此过程不需要:

  • 任何本地身份验证代理,
  • 任何额外的本地终端(一切都在一个本地终端中工作)。

此过程需要:

  • 本地 SSH 服务器,
  • 能够ssh-agent在远程端启动(程序将启动一个),
  • 您的密钥保存在本地文件中。

步骤:

  1. ssh到遥控器。不要使用-A

    # on local
    ssh user@remote
    

    在此步骤中,您可以创建一个隧道(例如-R 2222:localhost:22),如果需要的话,它将允许您ssh返回下一步。

  2. 开始ssh-agent在遥控器上:

    # on remote
    eval "$(ssh-agent)"
    
  3. ssh返回本地。使用-A

    # on remote
    ssh -A localuser@back-to-local
    

    如果通过隧道那么它将像ssh -A -p 2222 localuser@localhost

  4. 将本地密钥加载到远程代理:

    # back on local
    ssh-add
    

    如果您愿意,可以添加非默认键。

  5. 退出“返回本地”shell:

    # back on local
    exit
    
  6. 像在 (本地) 之后的原始方法中一样在远程 shell 中继续ssh -A:运行screen、脚本等;断开连接。

  7. 当您稍后重新连接并发现所有操作均已完成且不再需要代理时,请终止该代理:

    # on remote
    ssh-agent -k 
    

简易程序与正常程序

在简单的过程中(上面),我们使用附加功能ssh -A localuser@back-to-local将远程身份验证代理暂时转发到本地计算机。

正常程序(如下)会创建一个嵌入常规ssh user@remote连接的隧道,本质上是出于同样的目的。还有一些用于“管道”和维护的额外技术命令。


正常程序

此过程不需要:

  • 任何本地身份验证代理,
  • 任何本地 SSH 服务器。

此过程需要:

  • 一个额外的本地终端(shell),
  • 能够ssh-agent在远程端启动(程序将启动一个),
  • 您的密钥放在本地文件中(因此,如果您的原始设置依赖于从其他地方获取密钥的本地身份验证代理,那么这个答案对您不起作用)。

在整个过程中,我假设有一个恒定的本地工作目录和一个恒定的远程工作目录,我将对其进行标记/home/me/(您需要用您的真实远程工作目录替换此字符串)。

  1. 确保在本地机器上ssh-agent-socket不存在。这是一个任意名称,您可以根据需要更改它,但是一旦您选择了名称,就坚持使用它。如果需要,请删除该文件:

    # on local
    rm -f ssh-agent-socket
    
  2. 从本地通过 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。我们稍后会使用它。

  3. 假设上述命令有效,现在您就在远程机器的 shell 中。开始ssh-agent那里:

    # on remote
    eval "$(ssh-agent)"
    
  4. 仍在远程计算机上,创建指向新启动的代理使用的套接字的符号链接。请注意,我们需要事先删除所有旧的符号链接:

    # 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"。由于符号链接的存在,这个答案中的代码更加静态,所以让我们坚持下去。

  5. 打开另一个本地终端(shell)并...

    请注意,此时您无法screen在远程启动、断开连接并在同一个本地终端中工作。我们仍然需要隧道工作,ssh不能中断。最简单的方法是在此步骤中使用单独的本地终端。

    …然后导航 ( cd) 到目录ssh-agent-socket。得益于隧道和远程端的符号链接,套接字现在通向远程身份验证代理。连接到套接字的任何本地工具最终都会与远程代理对话。将正确的变量设置为正确的值,这样ssh-add(我们稍后会用到它)就可以找到套接字:

    # on local
    export SSH_AUTH_SOCK="$PWD/ssh-agent-socket"
    
  6. 将本地密钥添加到远程代理。以下命令从默认位置添加密钥;如果需要,请修改命令。这与将密钥添加到本地代理没有什么不同,因为无论哪种情况,命令都会连接到某个套接字,并且它不关心它通向哪里,只要另一端有某个身份验证代理即可。

    # on local
    ssh-add
    
  7. 验证代理是否持有正确的密钥。此命令应在两台计算机上提供相同的输出,因为它咨询的是同一个代理:

    # on local or on remote
    ssh-add -l
    

    您想要的密钥应该会列出。请注意,当在远程计算机上运行时,该命令仍会显示密钥的本地路径。没关系,代理将路径存储为附加信息,并且它实际上并不知道它们来自哪台计算机。

  8. 清理(​​可选)。删除远程符号链接和本地套接字:

    # on remote
    rm ssh-agent-symlink
    
    # on local
    rm ssh-agent-socket
    

    不再需要额外的本地 shell。您现在可以关闭它。

    如果您知道如何操作,您还可以重新配置仍在运行的隧道ssh并关闭隧道,但这并不重要,我不会在这里解释该方法;该隧道不再需要,但它可能会保留。

  9. 在远程 shell 中继续执行 (local) 之后的操作,就像使用原始方法一样ssh -A。设置几乎相同:您在远程服务器上,并且有一个可以通过$SSH_AUTH_SOCK环境变量访问的身份验证代理,该代理持有正确的密钥。一切都应该像以前一样工作,除了……

    不同之处在于代理在远程系统中运行,不依赖任何 SSH 隧道。因此,如果您在设置screen和完成之后断开连接,您的进程仍将能够与代理通信。

  10. 当您稍后重新连接并发现所有操作均已完成且不再需要代理时,请终止该代理:

    # on remote
    ssh-agent -k
    

    请注意,这只会在权限位于$SSH_AGENT_PID环境中的 shell 中起作用,因此基本上是在screen您创建的会话中,而不是在任何新的 shell 中。

    让代理继续运行是错误的。这不仅是不再需要的进程;它仍然持有你的本地密钥,因此让它运行的时间超过必要时间是不明智的。

    考虑自动化。我的意思是,如果您通常使用 运行远程脚本,./run-my-scripts那么现在您应该运行./run-my-scripts; ssh-agent -k,这样脚本一完成,代理就会得到处理。


安全注意事项

  1. 将本地代理转发给远程代理ssh -A或“借用”本地密钥给远程代理(如我们的情况)比将本地密钥作为文件复制到远程系统更安全。看看man 1 ssh说:

    应谨慎启用代理转发。能够绕过远程主机上的文件权限(对于代理的 UNIX 域套接字)的用户可以通过转发的连接访问本地代理。攻击者无法从代理获取密钥材料,但他们可以对密钥执行操作,使他们能够使用加载到代理中的身份进行身份验证。[…]

    虽然没有明确说明,但这种担忧是有道理的任何身份验证代理,不仅限于 的情况ssh -A。之所以考虑 ,是ssh -A因为之前ssh -A您通常会处理从同一系统中的常规文件加载密钥的代理;因此,如果攻击者(例如具有 root 访问权限的不诚实管理员)可以绕过套接字的文件权限,那么他们可能可以绕过密钥的文件权限并首先获取密钥。访问密钥更有价值,因为人们可以复制密钥文件并在以后使用它。访问代理的价值较低,因为人们只能在代理运行时使用代理进行身份验证。

    在我们的例子中,远程代理持有远程操作系统中不存在的本地密钥,因此情况类似于常规情况,ssh -A即使在我们不使用的“正常程序”中也是如此ssh -A:远程端没有宝贵的文件可供窃取,攻击者可能会尝试获取对代理的不太宝贵的访问权限。

    但是,我们的情况比常规的从本地到远程的情况更不安全ssh -A,因为远程攻击者可能会试图窥探正在运行的代理的内存,密钥就在那里。在ssh -A代理在本地计算机中运行的常规情况下,他们无法利用这种攻击角度。换句话说:我们的程序(“简单”或“正常”)将你的密钥复制到远程系统,但至少复制到记忆远程ssh-agent进程,而不是远程文件系统

  2. ssh-agent-socket如果隧道仍然有效,并且远程代理仍然有效,并且他们在您移除套接字或远程符号链接之前启动连接,则任何具有访问权限的人都可以在本地使用该代理。如果您是唯一的用户,那么它是安全的。如果有其他用户,那么您最好ssh-agent-socket在其他人无权访问的目录中工作(即创建)。ssh-agent-socket根据我们的ssh -L报告,它确实被创建为rw-------600),但我不确定它是否是这样创建的。我无法判断在创建之后是否存在一个时间窗口,当时模式可能没有那么严格,并且它“立即”被修复了。当模式限制较少时,这可能是攻击者的机会之窗。

    您的问题已加标签在 Linux 中,套接字的权限很重要。一般来说,它们可能无关紧要。请参阅人 7 unix

    在 Linux 上,连接到流套接字对象需要该套接字的写入权限;向数据报套接字发送数据报同样需要该套接字的写入权限。POSIX 未对套接字文件权限的影响做出任何声明,并且在某些系统(例如较旧的 BSD)上,套接字权限会被忽略。可移植程序不应依赖此功能来确保安全性。

    无论如何,当本地用户较多时,我们最好创建一个私有目录并mktemp -d在其中工作。mktemp -d创建一个从一开始就无法被其他人访问的目录。在“简单程序”中,应该自动在私有目录中创建本地套接字(sshd它本身会完成工作mktemp -d),因此您不必担心。

    对于可以绕过文件权限的本地攻击者(例如具有 root 访问权限的不诚实管理员),所有这些都无关紧要,但对于防范恶意普通用户而言,这很重要。

  3. 在远程端,您可以免受恶意普通用户的攻击,至少在 Linux 上是如此。即使您将其放在ssh-agent-symlink其他人可以访问的目录中,目标文件的模式也很重要;在我们的例子中:由 创建的套接字ssh-agent。此套接字应该已经得到保护(在我的 Debian 中,它是在私有临时目录中创建的,ssh-agent它本身就完成了 的工作mktemp -d)。似乎有些系统对符号链接的权限很重要如有疑问,请修改程序以便在私有目录中创建远程符号链接;或者之后创建隧道ssh-agent并直接指向远程套接字,这样就不需要符号链接了。

相关内容