Bitte Beachten: Dieser Blog wird hier nicht weiter geführt! Kommentare und neue Blogposts gibt es unter http://feitel.indeedgeek.de. Dort bitte auch neue Kommentare zu diesen Beiträge erstellen.

Samstag, 15. März 2008

ssh-agent Startscript

Nachdem ich zufällig auf einige interessante Möglichkeiten von SSH gestoßen bin entschloss ich mich vor meinen Semesterferien das Buch "SSH, The Secure Shell" auszuleihen. Ich muss sagen wenn dieses Buch ist schon fast die SSH Spezifikation. Dort steht wirklich alles drinnen. Zum Lesen völlig ungeeignet allerdings für Ideen und zum Nachblättern gar nicht mal so schlecht.

So bin ich auch auf Authentifikation über SSH-Keys gestoßen. Normalerweise gibt man sein Passwort ein, wenn man sich auf einem Server anmeldet. SSH-Keys funktionieren allerdings eher wie GPG Verschlüsselung. Man generiert einen Public Key, den man auf allen Servern, bei denen man sich anmelden möchte, hinterlegt. Außerdem noch einen Privat Key mit dem sich jeder, bei allen Server auf denen der Public Key hinterlegt ist, anmelden kann. Das funktioniert einfach, in dem der Server mit dem Public-Key eine Challenge verschlüsselt und diese dem Client zusendet. Dieser entschlüsselt die Nachricht wieder und sendet es zurück. Stimmen die Nachrichten beim Server überein wird der Nutzer rein gelassen. Allerdings kann dann wie schon gesagt jeder der den Private Key kennt auch auf den Server zugreifen. Um dies etwas zu erschweren kann man ein Passwort definieren das für den Privaten Key benötigt wird. Man sieht schon, im Grunde nichts anderes als GPG. In Wirklichkeit kann man sogar (mit den nötigen Einstellungen) seinen GPG Key dafür nutzen.

Nun hat man zwar SSH-Key, allerdings darf man nun wieder jedes mal sein Passwort eingeben. Man hat also nichts gewonnen. Dafür gibt es nun (ebenfalls aus der GPG-Welt bekannt) einen Agent, der die Key einmal lädt, dabei das Passwort abfragt und anschließend auf Anfragen reagiert. So muss man nicht jedes mal neu sein Passwort eingeben. Sehr praktische Geschichte! Leider ist die Syntax etwas umständlich und auch muss jeder Key einzeln geladen werden. Deshalb habe ich mir etwas den Kopf zerbrochen wie man dies automatisieren kann. Dabei ist das folgende Script entstanden. Es schaut bei jedem Aufruf (in der Regel sobald sich eine neue Shell öffnet) ob der SSH-Agent läuft. Außerdem lädt es die Keys Automatisch so das man nur noch die Passwörter eingeben muss. Außerdem ermöglicht es über die beiden Funktionen start-ssh-agent und load-ssh-indentities dies auch Manuell durchzuführen.

Um zu verstehen wie das Script funktioniert muss man sich erst einmal anschauen wie der ssh-agent bekannt gibt es es ihn gibt. Ruft man ihn einfach mit dem ssh-agent Kommando auf (was der falsche Weg ist), erscheinen einige Variablen. Diese Variablen sind wichtig damit der ssh-Client auf den Agent zugreifen kann. Der Agent selbst läuft vom Terminal losgelöst. Er wird also nicht beendet wenn die Session beendet wird sondern läuft im Hintergrund weiter. Einer der richtigen Wege (es gibt mehrere Möglichkeiten) wäre eval `ssh-agent`. Hier wird zuerst der ssh-agent aufgerufen und die Ausgaben ausgeführt. So werden die Variablen im aktuellen Terminal bekannt gegeben. Möchte man nun auch in einer anderen Session auf den Agent zugreifen muss man dort auch einfach nur die Variablen bekannt machen. Da der Agent wie gesagt unabhängig von der aktuellen Session läuft interessiert es nicht aus welcher Shell er gestartet wurde.

$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-Yvpvk13029/agent.13029; export SSH_AUTH_SOCK;
SSH_AGENT_PID=13045; export SSH_AGENT_PID;
echo Agent pid 13045;

Mein kleines Script macht im Grunde nichts anderes als diese Variablen in einer Datei zu speichern und bei jedem anmelden diese einzulesen. Außerdem lädt es alle Identitäten die in in dem Verzeichnis ~/.ssh/keys/ liegen. Wichtig ist, dass dort nur Private-Keys liegen dürfen; keine Public-Keys!

# Lädt alle Indentitäten im Verzeichnis ~/.ssh/keys/
function load-ssh-indentities() {
  # Prüft erst ob ein tty verfügbar ist
  if tty -s ; then
    for FILE in ~/.ssh/keys/* ; do
      ssh-add $FILE
    done
  fi
}

# Startet den SSH-Agent mit einer Indentitäten-Lifetime von einer Stunde
# und schreibt die Variablen in die ~/.ssh_agent_vars Datei
function start-ssh-agent() {
  eval `ssh-agent -s -t 3600`
  echo -e "export SSH_AGENT_PID=$SSH_AGENT_PID\nexport SSH_AUTH_SOCK=$SSH_AUTH_SOCK" > ~/.ssh_agent_vars \
  && echo "SSH-Agent gestartet"
}

# Fragt erst nach ob Indentitäten geladern werden sollen.
function _ask_load_ssh_indentities() {
  echo "Indentitäten laden? (y/N)"
  read ANSWER
  ( [ "$ANSWER" = "y" ] || [ "$ANSWER" = "Y" ] ) && load-ssh-Indentitäten
}

# Die Variable SSH_AUTH_SOCK wird für den SSH-Agent benötigt und sollte gesetzt sein wenn er läuft.
if [ "$SSH_AUTH_SOCK" = "" ]; then
  # Es wird geprüft ob die Datei ~/.ssh_agent_vars vorhanden ist.
  if [ -f ~/.ssh_agent_vars ]; then
    # Wenn ja wird diese Includiert und geprüft ob die Variablen darin noch gültig sind.
    source ~/.ssh_agent_vars

    # Prüft ob es ein ssh-agent Prozess mit der PID gibt und das Socket existiert.
    if ( ps -p $SSH_AGENT_PID  | grep -q ssh-agent ) && [ -e $SSH_AUTH_SOCK ]; then
      # SSH-Agent ist gestartet Vorhandene Indentitäten werden angezeigt.
      echo "SSH-Agent ist bereits gestartet Geladene Indentitäten:"
      ssh-add -l
    else
      # Die Variablen sind nicht mehr gültig. Datei wird gelöscht und SSH-Agent wird neu gestartet.
      echo "~/.ssh_agent_vars Datei existiert aber es ist kein Agent gestartet. Starte Agent."
      rm ~/.ssh_agent_vars
      start-ssh-agent && _ask_load_ssh_indentities
    fi
  else
    # Datei ist nicht vorhanden. Es wird geprüft ob SSH-Agent läuft.
    if ps u -C ssh-agent | grep -q ssh-agent ; then 
      echo "SSH-Agent läuft aber es existiert keine ~/.ssh_agent_vars Datei!"
      echo "SSH-Agent muss gestoppt werden und mit 'start-ssh-agent' neu gestartet"
      # Weiteres Vorgehen dem User überlassen
    else
      # Wenn nicht wird er gestartet.
      start-ssh-agent && _ask_load_ssh_indentities
    fi
  fi
else
  # Variable ist schon gesetzt. Es wird geprüft ob SSH-Agent auch wirklich läuft
  if ps u -C ssh-agent | grep -q ssh-agent ; then 
    echo "SSH-Agent ist bereits gestartet. Geladen Indentitäten:"
    ssh-add -l
  else
    # Tritt nur bei Subshells auf, wenn die Variable in die Subshell
    # übergeben wurde obwohl der Agent schon beendet wurde.
    echo "SSH_AUTH_SOCK Variable ist gesetzt aber SSH-Agent läuft nicht!"
    # Weiteres Vorgehen dem User überlassen
  fi
fi

Das Script muss am besten in die Datei ~/.bashrc eingefügt werden. Dabei ist wichtig, dass dies nicht in einer Subshell ausgeführt werden darf. Deshalb direkt in die bashrc kopieren oder mittels source einbinden. Da es sonst Namespace-Probleme mit Variablen und Funktionen gibt darf es nicht über exec eingebunden werden (deshalb auch kein #!/bin/bash am Anfang).

Für Kritik und Verbesserungsvorschläge bin ich immer offen. Getestet habe ich das Script bis jetzt mit Bash 3.2.17 und zsh 4.3.4. Einziger Kritikpunkt den ich schon jetzt von meiner Seite habe: Das Script merkt natürlich nicht wenn der User nicht mehr angemeldet ist. Da der Agent unabhängig von der Session ist läuft er einfach weiter auch wenn der User schon längst über alle Berge ist. Das ist in so weit ein Problem da man theoretisch einen Speicherdump des ssh-agent Prozesses machen kann und dann alle Keys vorfindet. Um dies zu verhindern habe ich die 60 Minuten Sperre eingebaut. So scheißt der Agent nach 60 Minuten alle Keys raus und man muss sie neu laden. Leider ist das etwas unschön. Ich habe allerdings (noch) keine bessere Idee.

Keine Kommentare: