为什么 ssh-agent 和 ssh-add 不能在一个 bash 脚本中一起工作?

为什么 ssh-agent 和 ssh-add 不能在一个 bash 脚本中一起工作?

我编写了一个脚本,希望它启动第ssh-agent一个脚本以便在后台运行代理,并为当前 shell 实例设置适当的环境变量。但是,在脚本的第二部分,我还想添加我的私有 SSH 密钥以连接到我的服务器。

目前,脚本中的两个命令都无法互相配合。有人能帮助我正确理解我做错了什么吗?

#!/bin/bash

exec ssh-agent bash
sleep 5s
ssh-add /media/MyUSB/.ssh/id_00123 &

此外,利用内置调试器时,bash我可以看到只有脚本的第一部分正在运行(即exec ssh-agent bash)。

答案1

这么小的剧本中有这么多有趣的方面。


理解ssh-agent

让我们从ssh-agent设计目的开始。ssh-agent当你需要一个进程时,它会驻留在那儿,监听某个套接字(这是一种文件用于双向进程间通信)并处理来自连接到套接字的程序(如ssh-add或 )的请求ssh。程序将与代理对话并存储、操作或使用私钥。

任何想要使用代理的程序都需要知道代理监听的套接字的路径。如果程序知道该路径,那么它就可以使用套接字与代理进行通信。

曾经有一个设计决策:任何想要知道身份验证代理套接字路径的程序都应该检查SSH_AUTH_SOCK其自身环境中的变量,变量的值就是路径。这是一个决定(我的意思是可以以其他方式设计,例如,程序可能被设计为每次通过命令行参数接受此路径),但这是一个非常好的决定。

这是一个非常好的决定,因为环境默认是继承的。这意味着您需要SSH_AUTH_SOCK为一个进程(例如 shell)设置环境变量,并且其所有后代都将继承它(除非其中一些故意选择改变其环境或创建具有改变环境的子代)。相比之下:每次您想要运行应该与代理对话的某些东西时,将路径作为命令行参数传递都需要额外的输入;并且您希望将路径存储在某个地方,所以无论如何都可能存储在变量中。所以在这里,变量的名称是标准化的,感兴趣的程序会自动检查它。

另一个选择是将路径存储在固定位置的文本文件中,甚至首先在固定位置创建套接字。但有时您希望某些程序使用一个代理(一个套接字),而其他一些程序使用另一个代理(另一个套接字)。让两个程序在同一位置看到不同的文件很难。让两个程序看到不同的环境变量很容易。

因此,感兴趣的程序应该检查SSH_AUTH_SOCK其环境。我们如何在进程环境中将此变量设置为正确的值?如果没有调试器,有两种方法:

  1. 要么父级知道该值,并且当它产生子级时,它会SSH_AUTH_SOCK在环境中为子级设置正确的值(从父级继承不变的行为SSH_AUTH_SOCK可以解释为“父级什么都不做来设置这个值”);

  2. 或者该过程以其他方式学习价值并修改其自身的环境。

因此ssh-agent支持两种启动方法:

  1. ssh-agent command …
    

    这里ssh-agent创建一个套接字并准备好为将来连接到该套接字的程序提供服务。然后它command …作为其子进程运行,并SSH_AUTH_SOCK在子进程的环境中使用正确的值。子进程(或继承该变量的任何后代)可以轻松找到套接字,但其他进程则不那么容易。当终止时command,也会终止ssh-agent(即使有孙进程)。

  2. ssh-agent   # but don't use it exactly this way
    

    这里ssh-agent分叉到后台,即它创建自己的子副本,并且不等待它退出。子进程与父进程的标准流和终端分离,它不会自行退出。子进程将是留下来的真正代理。父进程将自行退出,但在退出之前,会打印出 shell 代码。shell 代码在由 shell 评估时,会使 shell 修改自己的环境,从而将SSH_AUTH_SOCK正确的值放在那里。但外壳必须评价输出,而不仅仅是运行ssh-agent,所以正确的方法是这样的:

    eval "$(ssh-agent)"
    

    此后,运行的 shelleval在其环境中拥有正确的变量(实际上是变量),从现在开始,像ssh-add从此 shell 运行这样的命令将找到代理,因为它们将继承该变量。退出 shell 不会终止代理,因此在退出 shell 之前的某个时间点,您可能需要调用ssh-agent -k(或者,如果您还想取消设置变量eval "$(ssh-agent -k)":)。没有进程持有正确值的代理SSH_AUTH_SOCK实际上是无用的。


你的脚本有什么问题

现在,终于到了你的脚本。这是你的脚本:

#!/bin/bash

exec ssh-agent bash
sleep 5s
ssh-add /media/MyUSB/.ssh/id_00123 &

脚本执行的第一件事是exec ssh-agent bashexec告诉解释脚本的 shell 用命令替换自身,即ssh-agent bash。 shell 执行此操作并变为ssh-agent启动一个新命令bash(它是上面的方法 1)。 它bash保存了 的正确值SSH_AUTH_SOCK,它是交互式的,它会打印提示并允许您运行命令(包括需要 的命令SSH_AUTH_SOCK)。 如果您原来的交互式 shell 是,bash那么您可能会错过您现在处于单独的 中的事实bash。 您可能会将 的存在解释SSH_AUTH_SOCK为已修改原始 shell 环境的确认ssh-agent。 不,您仍然在脚本的中间。

嗯,不完全在中间。如果你退出这个bash,那么sleep其余的将不会被执行,因为解释脚本的 shell 已经用 替换了自己ssh-agent。从某种意义上说,你exit离脚本结束还差一步。

如果你运行脚本的方法类似于./myscript,那么exit将让你回到原始 shell。如果你运行脚本的方法类似于. ./myscript或者source myscript然后exit将会表现得好像你退出了原始 shell,因为原始 shell 是解释脚本的 shell,并且已经用ssh-agent即将exit从当前 shell 退出的 shell 替换了自身;这可以加强你在原始 shell 中的印象(并且现在你正在退出)。


修复

在问题中你已经明确陈述了你的目标:

[…] 当前 shell 实例的适当环境变量。[…]

要修改当前 shell 的环境,脚本必须使用上面的方法 2。当前 shell 必须是解释 shell,即脚本必须是源。shell 不能exec执行任何操作,因为您不希望 shell 被任何东西替换。示例修复:

#!/usr/bin/false
[ -n "$SSH_AUTH_SOCK" ] || eval "$(ssh-agent)"
ssh-add /media/MyUSB/.ssh/id_00123

还有更多改进:

  • #!/usr/bin/false因为 shebang 确保脚本不会执行任何操作,如果您(无意中)运行它而不是获取它,它将失败。其他策略如下:忘记运行脚本的策略source沒有什麼或者使用指向 或另一个兼容 shell 的 shebang bashsh执行的脚本(未获取源代码)将启动一个新代理,向其添加密钥并退出。所有这些都不会影响当前 shell 的环境,因此代理将白白地呆在那里,几乎无法访问。您需要花一些精力来查找和终止它,或者花一些精力来查找它的套接字并SSH_AUTH_SOCK在 shell 的环境中手动设置;或者您就让它这样吧。false因为 shebang 可以防止这种不方便的情况。

  • [ -n "$SSH_AUTH_SOCK" ]检查是否$SSH_AUTH_SOCK扩展为非空字符串。空字符串表示没有可用的代理,而非空字符串表示可能有代理。ssh-agent仅当字符串为空时,脚本才会启动新的代理。这是针对以下情况的基本预防措施:您(无意中)第二次获取脚本,创建新的身份验证代理,并丢失与前一个代理相关的变量,这些变量将继续无用地运行。

  • 无需sleepssh-agent当代理(即后台的子代理)准备就绪时,我们的脚本就会退出。您可以ssh-add立即退出。

  • ssh-add在这里作为同步命令。异步运行它(使用&,就像您尝试做的那样)可能不会为您节省很多时间。您可以尝试。但您很可能会从启用了作业控制的交互式 shell 中获取脚本,因此&(如果您将它放在那里)会用类似 的消息污染您的终端[1]+ Done …

相关内容