為什麼 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為確認ssh-agent已修改原始 shell 的環境。不,你還在寫劇本。

嗯,不完全是在中間。如果退出此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沒有捨邦或使用指向 的 shebangbashsh另一個相容的 shell,執行的腳本(未取得來源)將啟動一個新代理,向其新增金鑰並退出。所有這一切都不會影響您目前 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 ….

相關內容