Warum funktionieren ssh-agent und ssh-add nicht zusammen in einem Bash-Skript?

Warum funktionieren ssh-agent und ssh-add nicht zusammen in einem Bash-Skript?

Ich habe ein Skript geschrieben, das ich zunächst starten möchte, ssh-agentum den Agenten im Hintergrund auszuführen und die entsprechenden Umgebungsvariablen für die aktuelle Shell-Instanz festzulegen. Im zweiten Teil des Skripts möchte ich jedoch auch meinen privaten SSH-Schlüssel hinzufügen, um eine Verbindung zu meinem Server herzustellen.

Derzeit funktioniert keiner der Befehle im Skript mit den anderen. Kann mir jemand helfen, herauszufinden, was ich falsch mache?

#!/bin/bash

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

Darüber hinaus kann ich beim Verwenden des integrierten Debuggers bashsehen, dass nur der erste Abschnitt des Skripts funktioniert (d. h. exec ssh-agent bash).

Antwort1

So viele interessante Aspekte in einem so kleinen Skript.


Verständnisssh-agent

Beginnen wir mit dem, was ssh-agentes tun soll. Sie führen es aus, ssh-agentwenn Sie einen Prozess wollen, der dort sitzt und auf einem Socket lauscht (das ist eine ArtDateifür bidirektionale Interprozesskommunikation) und bedient Anfragen von Programmen wie ssh-addoder ssh, die sich mit dem Socket verbinden. Programme kommunizieren mit dem Agenten und speichern, bearbeiten oder verwenden private Schlüssel.

Jedes Programm, das einen Agenten verwenden möchte, muss den Pfad zum Socket kennen, auf dem der Agent lauscht. Wenn ein Programm den Pfad kennt, kann es den Socket verwenden, um mit dem Agenten zu kommunizieren.

Es wurde einmal eine Designentscheidung getroffen: Jedes Programm, das den Pfad zum Socket eines Authentifizierungsagenten wissen möchte, sollte die SSH_AUTH_SOCKVariable in seiner eigenen Umgebung überprüfen, der Wert der Variablen ist der Pfad. Es war eine Entscheidung (ich meine, die Dinge könnten auch anders gestaltet werden, z. B. könnten Programme so gestaltet werden, dass sie diesen Pfad jedes Mal über Befehlszeilenargumente akzeptieren), aber es war eine sehr gute Entscheidung.

Das war eine sehr gute Entscheidung, da die Umgebung standardmäßig übernommen wird. Das bedeutet, dass Sie die SSH_AUTH_SOCKUmgebungsvariable für einen Prozess (z. B. eine Shell) festlegen müssen und alle seine Nachkommen sie übernehmen (es sei denn, einige von ihnen entscheiden sich bewusst dafür, ihre Umgebung zu ändern oder ein Kind mit geänderter Umgebung zu erstellen). Zum Vergleich: Wenn Sie den Pfad jedes Mal als Befehlszeilenargument übergeben, wenn Sie etwas ausführen möchten, das mit dem Agenten kommunizieren soll, ist zusätzliche Tipparbeit erforderlich. Außerdem möchten Sie den Pfad irgendwo speichern, also wahrscheinlich sowieso in einer Variablen. Also los geht‘s: Der Name der Variablen ist standardisiert und interessierte Programme überprüfen ihn automatisch.

Eine andere Möglichkeit war, den Pfad in einer Textdatei an einem festen Ort zu speichern oder sogar von vornherein einen Socket an einem festen Ort zu erstellen. Aber manchmal möchten Sie, dass einige Programme einen Agenten (einen Socket) und andere Programme einen anderen Agenten (einen anderen Socket) verwenden. Es ist schwierig, zwei Programmen zu ermöglichen, unterschiedliche Dateien am gleichen Ort anzuzeigen. Es ist einfach, zwei Programmen zu ermöglichen, unterschiedliche Umgebungsvariablen anzuzeigen.

Interessierte Programme sollten also SSH_AUTH_SOCKin ihrer Umgebung nachschauen. Wie können wir oder sonst wie diese Variable in der Umgebung eines Prozesses auf den richtigen Wert setzen? Ohne Debugger gibt es zwei Möglichkeiten:

  1. Entweder kennt das übergeordnete Element den Wert und richtet beim Erzeugen eines untergeordneten Elements SSH_AUTH_SOCKden richtigen Wert in der Umgebung für das untergeordnete Element ein (eine unveränderte Übernahme SSH_AUTH_SOCKvom übergeordneten Element kann so interpretiert werden, dass „das übergeordnete Element dies einrichtet, indem es nichts tut“);

  2. oder der Prozess lernt den Wert auf andere Weise und ändert seine eigene Umgebung.

Daher ssh-agentwerden zwei Startmethoden unterstützt:

  1. ssh-agent command …
    

    Hier ssh-agentwird ein Socket erstellt und darauf vorbereitet, zukünftige Programme zu bedienen, die sich mit dem Socket verbinden. Dann wird es command …als sein Kind ausgeführt, mit SSH_AUTH_SOCKdem richtigen Wert in der Umgebung für das Kind. Das Kind (oder jeder Nachkomme, der die Variable erbt) kann den Socket problemlos finden, andere Prozesse jedoch nicht so leicht. Wenn beendet wird command, wird dies auch getan ssh-agent(selbst wenn es Enkelkinder gibt).

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

    Hier ssh-agentwird in den Hintergrund verzweigt, d. h. es erstellt eine untergeordnete Kopie von sich selbst und wartet nicht, bis diese beendet wird. Das untergeordnete Element löst sich von den Standard-Streams des übergeordneten Elements und vom Terminal und wird nicht von selbst beendet. Das untergeordnete Element ist der eigentliche Agent, der bleibt. Das übergeordnete Element wird von selbst beendet, aber bevor dies geschieht, wird Shell-Code gedruckt. Wenn der Shell-Code von einer Shell ausgewertet wird, veranlasst diese die Shell, ihre eigene Umgebung zu ändern, sodass SSH_AUTH_SOCKdort der richtige Wert eingefügt wird.Aber die Schale mussauswertendie Ausgabe, nicht nur ausführenssh-agent, also ist die richtige Vorgehensweise:

    eval "$(ssh-agent)"
    

    Danach evalhat die ausgeführte Shell die richtige Variable (eigentlich: Variablen) in ihrer Umgebung und von nun an werden Befehle wie „ ssh-addAusführen“ von dieser Shell den Agenten finden, da sie die Variable erben. Das Beenden der Shell beendet den Agenten nicht, daher möchten Sie möglicherweise irgendwann vor dem Beenden der Shell Folgendes aufrufen ssh-agent -k(oder, wenn Sie auch die Variablen zurücksetzen möchten: eval "$(ssh-agent -k)"). Ein Agent, für den es keinen Prozess mit dem richtigen Wert gibt, SSH_AUTH_SOCKist praktisch nutzlos.


Was ist falsch an deinem Skript

Und nun – endlich – zu Ihrem Skript. Dies ist Ihr Skript:

#!/bin/bash

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

Das erste, was das Skript tut, ist exec ssh-agent bash. execweist die Shell, die das Skript interpretiert, an, sich selbst durch den Befehl zu ersetzen, der lautet ssh-agent bash. Die Shell tut dies und wird , ssh-agentwodurch ein neues gestartet wird bash(es ist die Methode 1 von oben). Dies bashenthält den richtigen Wert von SSH_AUTH_SOCK, es ist interaktiv, es druckt eine Eingabeaufforderung und ermöglicht Ihnen, Befehle auszuführen (einschließlich Befehle, die benötigen SSH_AUTH_SOCK). Wenn Ihre ursprüngliche interaktive Shell war, bashübersehen Sie möglicherweise die Tatsache, dass Sie sich jetzt in einem separaten befinden bash. Sie können die Existenz von SSH_AUTH_SOCKals Bestätigung interpretieren, dass ssh-agentdie Umgebung Ihrer ursprünglichen Shell geändert wurde. Nein, Sie befinden sich noch mitten in Ihrem Skript.

Nun, nicht genau in der Mitte. Wenn Sie dies beenden bash, sleepwerden und der Rest nicht ausgeführt, da die Shell, die das Skript interpretiert, sich selbst durch ersetzt hat ssh-agent. In gewisser Weise sind Sie eins exitvor dem Ende des Skripts.

Wenn Ihre Methode zum Ausführen des Skripts wie folgt war ./myscript, exitwerden Sie zurück in die ursprüngliche Shell gebracht. Wenn Ihre Methode wie folgt war. ./myscriptodersource myscriptdann exitverhält es sich so, als ob Sie die ursprüngliche Shell beendet hätten, weil die ursprüngliche Shell die Shell war, die das Skript interpretiert hat, und sich selbst durch die Shell ersetzt hat, ssh-agentdie gerade beendet wird, wenn Sie exitdie aktuelle Shell verlassen. Dadurch kann der Eindruck verstärkt werden, dass Sie sich in der ursprünglichen Shell befunden haben (und diese nun beenden).


Die Reparatur

In der Frage hast Du Dein Ziel explizit benannt:

[…] entsprechende Umgebungsvariablen für die aktuelle Shell-Instanz. […]

Um die Umgebung der aktuellen Shell zu ändern, muss das Skript die Methode 2 von oben verwenden. Die aktuelle Shell muss die interpretierende Shell sein, d. h. das Skript muss als Quelle verwendet werden. Die Shell darf nichts ändern exec, da Sie nicht möchten, dass die Shell durch irgendetwas ersetzt wird. Beispiel für eine Lösung:

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

Es gibt noch mehr Verbesserungen:

  • #!/usr/bin/falseda der Shebang sicherstellt, dass das Skript nichts tut und fehlschlägt, wenn Sie es (versehentlich) ausführen, anstatt es zu beziehen. Weitere Strategien finden Sie hier:Strategie für das Vergessen, ein Skript auszuführen mitsource.Ohne viel Aufhebensoder mit einem Shebang, der auf bashoder sheine andere kompatible Shell zeigt, würde das ausgeführte Skript (ohne Quelle) einen neuen Agenten starten, ihm den Schlüssel hinzufügen und beendet werden. All dies ohne Auswirkungen auf die Umgebung Ihrer aktuellen Shell, sodass der Agent dort vergeblich und fast unzugänglich herumsitzen würde. Sie müssten einige Mühe aufwenden, um ihn zu finden und zu beenden, oder einige Mühe aufwenden, um seinen Socket zu finden und ihn manuell SSH_AUTH_SOCKin der Umgebung Ihrer Shell einzustellen; oder Sie würden ihn einfach so lassen, falseda der Shebang dieses unbequeme Szenario verhindert.

  • [ -n "$SSH_AUTH_SOCK" ]prüft, ob $SSH_AUTH_SOCKes sich zu einer nicht leeren Zeichenfolge erweitert. Eine leere Zeichenfolge gibt an, dass kein Agent verfügbar ist, während eine nicht leere Zeichenfolge angibt, dass wahrscheinlich ein Agent vorhanden ist. Das Skript startet ssh-agentnur dann neu, wenn die Zeichenfolge leer ist. Dies ist eine grundlegende Vorsichtsmaßnahme für den Fall, dass Sie das Skript (versehentlich) zum zweiten Mal aufrufen, einen neuen Authentifizierungsagenten erstellen und Variablen verlieren, die mit dem vorherigen Agenten verknüpft sind und der nutzlos weiterläuft.

  • Das ist nicht nötig sleep. ssh-agentUnser Skript wird beendet, wenn der Agent (also sein untergeordnetes Element im Hintergrund) bereit ist. Sie können es ssh-addsofort tun.

  • ssh-addist hier als synchroner Befehl vorhanden. Wenn Sie es asynchron ausführen (mit &, wie Sie es versucht haben), sparen Sie wahrscheinlich nicht viel Zeit. Sie können es versuchen. Aber Sie werden das Skript höchstwahrscheinlich aus einer interaktiven Shell mit aktivierter Jobsteuerung beziehen und daher &(wenn Sie es dort einfügen) Ihr Terminal mit einer Meldung wie verunreinigen [1]+ Done ….

verwandte Informationen