ssh-agent と ssh-add が 1 つの bash スクリプト内で連携して動作しないのはなぜですか?

ssh-agent と ssh-add が 1 つの bash スクリプト内で連携して動作しないのはなぜですか?

エージェントをバックグラウンドで実行するために最初のスクリプトを開始しssh-agent、現在のシェル インスタンスに適切な環境変数を設定するスクリプトを 1 つ作成しました。ただし、スクリプトの 2 番目の部分では、サーバーに接続するために秘密 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_SOCK1 つのプロセス (シェルなど) に環境変数を設定すると、その子孫すべてがそれを継承することになります (子孫の一部が意図的に環境を変更したり、環境を変更した子プロセスを作成したりしない限り)。比較すると、エージェントと通信する必要がある何かを実行するたびに、コマンド ライン引数としてパスを渡すには、余分な入力が必要です。また、パスをどこかに保存する必要がありますが、おそらくいずれにしても変数に保存するでしょう。つまり、変数の名前は標準化されており、関係するプログラムが自動的にチェックします。

別のオプションとしては、パスをテキスト ファイル内の固定の場所に保存するか、そもそも固定の場所にソケットを作成することです。ただし、一部のプログラムでは 1 つのエージェント (1 つのソケット) を使用し、他の一部のプログラムでは別のエージェント (別のソケット) を使用する必要がある場合があります。2 つのプログラムに同じ場所にある異なるファイルを認識させるのは困難です。2 つのプログラムに異なる環境変数を認識させるのは簡単です。

したがって、関心のあるプログラムはSSH_AUTH_SOCK環境を確認する必要があります。プロセスの環境でこの変数を適切な値に設定するにはどうすればよいでしょうか? デバッガーがない場合、次の 2 つの方法があります。

  1. 親が値を知っていて、子を生成するときに、SSH_AUTH_SOCK子の環境に適切な値を設定します (親から変更せずに継承する行為は、SSH_AUTH_SOCK「親が何もせずにこれを設定する」と解釈される場合があります)。

  2. または、プロセスは他の方法で値を学習し、独自の環境を変更します。

したがって、ssh-agent次の 2 つの起動方法をサポートします。

  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バックグラウンドにフォークします。つまり、それ自体の子コピーを作成し、終了を待機しません。子は親の標準ストリームとターミナルから切り離され、それ自体は終了しません。子は、そのまま残る実際のエージェントになります。親はそれ自体で終了しますが、その前にシェル コードが出力されます。シェル コードは、シェルによって評価されると、シェルに自身の環境を変更させ、SSH_AUTH_SOCK適切な値がそこに置かれます。しかし、シェルは評価する実行だけではなく出力もssh-agent正しい方法は次のようになります:

    eval "$(ssh-agent)"
    

    この後、実行されるシェルはeval環境に適切な変数 (実際は variables) を持ち、今後はssh-addこのシェルから run などのコマンドを実行すると、変数が継承されるためエージェントが見つかります。シェルを終了してもエージェントは終了しないため、シェルを終了する前のある時点で を呼び出す必要があります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は、スクリプトを解釈するシェルに、 であるコマンドで自身を置き換えるように指示しますssh-agent bash。 シェルはそれを実行し、 になり、ssh-agent新しい を開始しますbash(上記の方法 1 です)。 これはbashの正しい値を保持しSSH_AUTH_SOCK、対話型であり、プロンプトを出力して コマンド ( を必要とするコマンドを含むSSH_AUTH_SOCK) を実行できます。 元の対話型シェルが だった場合bash、別の になっているという事実に気付かない可能性がありますbash。 の存在を、が元のシェルの環境を変更したSSH_AUTH_SOCKことの確認と解釈する可能性がありますssh-agent。 いいえ、まだスクリプトの途中です。

まあ、ちょうど真ん中というわけではありません。これを終了した場合bashsleepスクリプトを解釈するシェルが に置き換えられているため、残りは実行されません。ある意味では、スクリプトの終わりのssh-agent1 つ手前です。exit

スクリプトを実行するメソッドが のような場合./myscript、 はexit元のシェルに戻ります。メソッドが のような場合、. ./myscriptまたはsource myscriptは、exit元のシェルを終了したかのように動作します。元のシェルはスクリプトを解釈するシェルであり、現在のシェルからssh-agent終了しようとしているに置き換えられたためexitです。これにより、元のシェルにいた (そして今は終了している) という印象が強まります。


修正方法

質問の中で、あなたは自分の目標を明示的に述べています:

[…] 現在のシェルインスタンスに適切な環境変数を設定します。 […]

現在のシェルの環境を変更するには、スクリプトは上記の方法 2 を使用する必要があります。現在のシェルは解釈シェルである必要があります。つまり、スクリプトはソース化されている必要があります。シェルがexec何かに置き換えられないようにするため、シェルは何も実行しないでください。修正例:

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

改善された点は他にもあります:

  • #!/usr/bin/falseシェバンは、スクリプトをソースする代わりに(誤って)実行した場合にスクリプトが何も実行されずに失敗することを保証します。他の戦略は次のとおりです。スクリプトの実行を忘れた場合の戦略source何もせずにbashまたは、または別の互換性のあるシェルを指すシェバンを使用するとsh、実行されたスクリプト (ソース化されていない) は新しいエージェントを開始し、それにキーを追加して終了します。これはすべて、現在のシェルの環境に影響を与えずに行われるため、エージェントはほとんどアクセスできない状態で無駄に残ります。エージェントを見つけて強制終了するか、ソケットを見つけてシェルの環境に手動で設定するか、またはそのままにしておく必要があります。シェバンがこの不便なシナリオを防ぐため、SSH_AUTH_SOCKそのままにしておくことになります。false

  • [ -n "$SSH_AUTH_SOCK" ]$SSH_AUTH_SOCK空でない文字列に展開されるかどうかを確認します。空の文字列はエージェントが利用できないことを示し、空でない文字列はエージェントが存在する可能性があることを示します。ssh-agent文字列が空の場合にのみ、スクリプトは新規に開始されます。これは、(誤って) スクリプトを 2 回目にソースし、新しい認証エージェントを作成し、以前のエージェントに関連付けられた変数が失われ、無駄に実行され続けるというシナリオに対する基本的な予防策です。

  • エージェント (つまり、バックグラウンドの子) の準備ができたらスクリプトを終了するので、 .は必要ありませんsleep。すぐに実行できます。ssh-agentssh-add

  • ssh-addは、ここでは同期コマンドとして使用されています。これを非同期的に実行しても ( を使用&)、おそらく多くの時間は節約できません。試してみることはできます。ただし、ジョブ制御が有効になっている対話型シェルからスクリプトをソースする可能性が高く、そのため&(そこに配置した場合) ターミナルに のようなメッセージが表示されます[1]+ Done …

関連情報