# SSH-Hostprüfung für Agenten mit Zugriff auf die Produktion

Ein autonomer Coding-Agent darf niemals selbst entscheiden, dass ein neuer SSH-Server vertrauenswürdig ist. Er kann ein Repository prüfen, eine Bereitstellung entwerfen und Zugriff anfordern. Wenn die Identität des entfernten Systems jedoch nicht mit einem Eintrag übereinstimmt, den eine Person oder ein vertrauenswürdiger Bereitstellungsprozess erstellt hat, muss er anhalten.

SSH stellt zwei getrennte Sicherheitsfragen: «Habe ich den vorgesehenen Server erreicht?» und «Was darf dieses Konto auf diesem Server tun?» Teams werfen beides oft zusammen, weil für beide öffentliche Schlüssel verwendet werden. Dieser Fehler macht aus einer fehlgeleiteten Verbindung zunächst den Verlust von Zugangsdaten und aus einem zu weitreichenden Konto anschließend einen Produktionsvorfall.

Das praktische Konzept ist klar: Die Hostidentität wird vor der Verbindung des Agenten festgelegt, jede Agentenaufgabe erhält ein eng begrenztes entferntes Konto, und eine Person prüft Befehle, deren Wirkung sich nicht sicher ableiten lässt. Jede Ebene begrenzt einen anderen Fehler. Keine kann die anderen ersetzen.

## Die Hostidentität muss vor der Benutzerauthentifizierung geprüft werden

Die Hostprüfung beantwortet die Frage, ob der SSH-Client den vorgesehenen Server erreicht hat. Die Benutzerauthentifizierung beantwortet, ob dieser Server die vom Client angebotenen Zugangsdaten für das Konto akzeptiert. Die Reihenfolge ist wichtig, weil SSH den Host-Schlüssel des Servers aushandelt und prüft, bevor der Client Material zur Benutzerauthentifizierung sendet.

Ein Host-Schlüssel gehört zum Server, nicht zum Administrator und nicht zum Agenten. Der Fingerabdruck ist eine kurze Darstellung dieses öffentlichen Host-Schlüssels und wird meist im SHA256-Format angezeigt. Wenn `deploy.example.internal` normalerweise einen Fingerabdruck präsentiert und plötzlich einen anderen, gibt es einen Hinweis darauf, dass sich etwas geändert hat. Das kann ein legitimer Neuaufbau sein. Möglich sind aber auch ein DNS-Fehler, eine wiederverwendete Adresse, ein Fehler am Bastion-Host oder ein aktiver Abfangversuch.

Stell dir einen Coding-Agenten vor, der angewiesen wurde, eine Migration auf `db-prod.internal` auszuführen. Seine SSH-Konfiguration löst diesen Namen in eine Adresse auf. Ein Angreifer, der DNS, eine Proxy-Route oder einen veralteten Inventareintrag beeinflussen kann, kann die Verbindung auf einen von ihm kontrollierten Server lenken. Akzeptiert der Client den unbekannten Host-Schlüssel, kann dieser Server eine Benutzerauthentifizierung anfordern. Ein privater SSH-Schlüssel auf der Clientseite muss den Client möglicherweise nicht verlassen. Der Agentenzugriff umfasst jedoch oft Passwörter, Zertifikatsignaturen, Weiterleitungen oder Befehle, die nach der Anmeldung nützliche Informationen offenlegen. Noch wichtiger ist, dass der Agent seinen vorgesehenen Befehl nun auf der falschen Maschine ausführen kann.

Ein festgelegter Host-Fingerabdruck lässt die Verbindung fehlschlagen, bevor der Agent die Maschine als sein Ziel behandeln kann. Deshalb ist ein unbekannter oder geänderter Host eine Autorisierungsgrenze und keine harmlose Warnung, die man unterdrücken sollte.

Die Hostprüfung bestätigt nicht, dass eine Maschine fehlerfrei, richtig konfiguriert oder sicher zu ändern ist. Sie bestätigt nur die Kontinuität der kryptografischen Identität. Diese Grenze ist hilfreich. Ein Host-Fingerabdruck soll nicht entscheiden, ob `rm -rf`, eine Schemakmigration oder eine Firewalländerung sinnvoll ist.

## Trust on First Use passt schlecht zu unbeaufsichtigten Prozessen

Trust on First Use ist für einen Entwickler, der sich manuell mit einer kurzlebigen persönlichen Maschine verbindet, vertretbar. Für einen autonomen Prozess ist es eine schlechte Standardeinstellung, denn gerade bei der ersten Verbindung muss jemand entscheiden, ob Hostname, Route und Fingerabdruck zusammengehören.

OpenSSH dokumentiert diese Wahl im Handbuch `ssh_config` unter `StrictHostKeyChecking`. Bei `yes` fügt der Client unbekannte Host-Schlüssel niemals automatisch hinzu und weist einen geänderten Schlüssel zurück. Bei `accept-new` speichert er einen unbekannten Schlüssel automatisch, weist einen geänderten Schlüssel aber weiterhin zurück. Bei `no` oder `off` akzeptiert er mehr Fälle, die eigentlich die Aufmerksamkeit eines Operators verdienen.

`accept-new` wird oft als vernünftiger Kompromiss angepriesen. Die Einstellung reduziert tatsächlich den Aufwand, wenn Hosts häufig erstellt werden. Sie gibt einem Netzwerkpfad aber auch das Recht, den ersten Identitätseintrag anzulegen. Für einen Agenten, der Infrastruktur ändern kann, ist das die falsche Stelle für diese Entscheidung.

Verwende `StrictHostKeyChecking=yes` für von Agenten betriebene Produktions- und Staging-Endpunkte. Wenn eine Verbindung wegen eines unbekannten Hosts fehlschlägt, leite die Anfrage des Agenten an eine Person weiter, die den gemeldeten Fingerabdruck mit einer Quelle außerhalb dieser SSH-Verbindung vergleichen kann. Eine Cloud-Konsole, ein signierter Inventareintrag, eine physische Konsole oder ein bestehender Managementkanal können diesen Vergleich ermöglichen.

Löse die Unterbrechung nicht, indem du `StrictHostKeyChecking=no` in eine globale Konfigurationsdatei einträgst. Diese Zeile bleibt häufig länger bestehen als der vorübergehende Vorfall, der sie gerechtfertigt hat, und gilt dann stillschweigend für Hosts, deren Schutz niemand lockern wollte.

Eine eng begrenzte Ausnahme sind kurzlebige Testsysteme, deren Hostidentitäten von einem Bereitstellungssystem stammen, das vor Beginn des Tests eine authentifizierte Hostliste veröffentlichen kann. Auch hier sollte der Agent die Hostidentität nicht aus seinem ersten Netzwerkkontakt lernen. Die Vertrauensquelle wurde verlagert, nicht abgeschafft.

## Ein Fingerabdruck ist nur nützlich, wenn seine Quelle unabhängig ist

Ein Fingerabdruck, der vom Endpunkt kopiert wurde, den du gerade prüfen willst, beweist fast nichts. `ssh-keyscan` ist praktisch, um öffentliche Host-Schlüssel zu sammeln. Genau diese Bequemlichkeit führt zu einer bekannten Falle: Ein Operator führt den Befehl gegen einen Hostnamen aus, kopiert das Ergebnis nach `known_hosts` und erklärt den Host für geprüft. Wenn DNS oder Routing bereits auf einen Angreifer zeigen, wurde der Schlüssel des Angreifers festgelegt.

Verwende `ssh-keyscan` zum Sammeln, nachdem du einen unabhängigen Fingerabdruck erhalten hast, nicht als Vertrauensquelle. Ein Administrator kann beispielsweise einen Host-Schlüssel-Fingerabdruck aus einer Provider-Konsole oder einem signierten Build-Datensatz abrufen und ihn anschließend mit dem gesammelten Schlüssel vergleichen.

```sh
ssh-keyscan -t ed25519 app-prod.internal > /tmp/app-prod.hostkey
ssh-keygen -lf /tmp/app-prod.hostkey -E sha256
```

Der zweite Befehl gibt ein Ergebnis in dieser Form aus:

```text
256 SHA256:exampleFingerprintMaterial app-prod.internal (ED25519)
```

Vergleiche den Wert `SHA256:` Zeichen für Zeichen mit dem unabhängig ermittelten Wert. Prüfe außerdem den Algorithmus. Wenn im Datensatz ED25519 steht und das gesammelte Ergebnis RSA ist, musst du anhalten und den Fall untersuchen. Behandle die Ergebnisse nicht als austauschbar.

Lege anschließend den öffentlichen Schlüssel fest, nicht nur eine Notiz mit seinem Fingerabdruck. Eine eigene Datei hält die Agentenziele von der persönlichen Sammlung alter Maschinen eines Entwicklers getrennt:

```text
app-prod.internal ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicHostKeyMaterial
[192.0.2.44]:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicHostKeyMaterial
```

Nimm sowohl den kanonischen Hostnamen als auch jede Adressform auf, die der Agent verwenden darf. Andernfalls kann ein Workflow, der an einem Tag eine Adresse und am nächsten einen Namen verwendet, auf eine neue Vertrauensentscheidung zurückfallen. Halte die Datei unter kontrolliertem Konfigurationsmanagement und prüfe Änderungen. Der Review sollte den alten und den neuen Fingerabdruck nennen.

DNS-SSHFP-Einträge können helfen, wenn DNSSEC durchgängig korrekt eingesetzt wird. Sie retten keine Agentenkonfiguration, die gewöhnliche, unsignierte DNS-Antworten als Beweis akzeptiert. Behandle SSHFP als zusätzlichen verifizierten Veröffentlichungsweg und nicht als dekorativen Eintrag, der blinde Erstnutzung sicher macht.

## Route und Hosteintrag in der SSH-Konfiguration festlegen

Ein Agent braucht eine SSH-Konfiguration, die Unklarheiten beseitigt, statt die Gewohnheiten einer Workstation zu übernehmen. Lege Zielname, erwartete Host-Schlüsseldatei, Konto und Verbindungsverhalten in einem expliziten Eintrag fest.

```sshconfig
Host app-production
    HostName app-prod.internal
    User agent_release
    Port 22
    UserKnownHostsFile ~/.ssh/agent_known_hosts
    GlobalKnownHostsFile /dev/null
    StrictHostKeyChecking yes
    UpdateHostKeys no
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    ForwardAgent no
    PermitLocalCommand no
```

Dieses Fragment verhindert mehrere typische Fehler. `UserKnownHostsFile` vermeidet eine unerwartete Abhängigkeit von den Hosteinträgen, die eine Person gesammelt hat. `GlobalKnownHostsFile /dev/null` verhindert, dass eine nicht verwaltete systemweite Datei das Vertrauen stillschweigend erweitert. `UpdateHostKeys no` verhindert, dass automatische Host-Schlüsselaktualisierungen während der Agentenarbeit die festgelegte Schlüsselmenge verändern. `ForwardAgent no` verhindert, dass der entfernte Rechner einen weitergeleiteten Authentifizierungsagenten verwendet, um einen anderen Ort zu erreichen.

Das OpenSSH-Handbuch `ssh_config` erklärt, dass `UpdateHostKeys` die Rotation von Host-Schlüsseln unterstützt, wenn ein Host den Besitz eines vertrauenswürdigen Schlüssels nachweist. Dieses Verhalten kann für interaktive Flotten mit sorgfältigem Hostmanagement sinnvoll sein. Bei einem autonomen Agenten erschwert die automatische Änderung jedoch die Untersuchung von Vorfällen. Eine Person sollte eine Änderung der Produktionsidentität genehmigen und die kontrollierte Hostdatei bewusst aktualisieren.

Verwende im Auftrag des Agenten ein stabiles Alias wie `app-production` und reserviere rohe Adressen für Notfallverfahren. Das Alias macht das genehmigte Ziel in den Logs sichtbar und verhindert, dass Befehle zwischen Namen, temporären Adressen und kopierten Shell-Schnipseln abweichen.

Prüfe die tatsächlich wirksame Konfiguration, bevor du dem Agenten irgendeinen Zugang zu Anmeldedaten gewährst:

```sh
ssh -G app-production | grep -E '^(hostname|user|stricthostkeychecking|userknownhostsfile|forwardagent|updatehostkeys) '
```

Erwarte Werte in etwa dieser Form:

```text
hostname app-prod.internal
user agent_release
stricthostkeychecking yes
userknownhostsfile /Users/operator/.ssh/agent_known_hosts
forwardagent no
updatehostkeys no
```

Damit lassen sich Vorrangfehler durch `Include`-Dateien, Benutzervorgaben und Konfigurationsmanagement erkennen. Sorgfältige Hosteinträge wurden schon durch eine spätere Wildcard-Regel außer Kraft gesetzt, die das Konto änderte, Weiterleitung aktivierte oder eine andere Known-Hosts-Datei auswählte. Die Konfiguration, die tatsächlich ausgeführt wird, musst du prüfen.

## Der Kontoumfang begrenzt den Schaden nach einer korrekten Verbindung

Auch ein verifizierter Host kann einen falschen Befehl erhalten. Gib dem Agenten deshalb ein Konto mit einer kleinen, bewusst festgelegten Aufgabe. Überlass einem autonomen Coding-Agenten nicht dasselbe SSH-Login, das ein Operator für jeden Notfall verwendet.

Der Kontoumfang hat vier Teile: welchen Host das Konto erreicht, welche Dateien und Dienste es beeinflussen kann, welche Wege zur Rechteausweitung es besitzt und wie lange es nutzbar bleibt. Ein eigenes Konto macht diese Antworten prüfbar. Ein gemeinsames `deploy`-Login macht aus jedem Automatisierungslauf ein Zuordnungsproblem und endet meist mit weitreichenden Berechtigungen, weil jeder neue Workflow eine weitere Ausnahme benötigt.

Ein Release-Agent auf einem Anwendungsserver muss möglicherweise ein Release-Verzeichnis lesen, ein neues Artefakt schreiben, einen Bereitstellungs-Wrapper aufrufen und einen Dienst neu starten. Er braucht weder eine Shell mit uneingeschränktem `sudo` noch Zugriff auf jedes Home-Verzeichnis oder die Möglichkeit, die SSH-Konfiguration zu ändern.

Wenn die Aufgabe eng begrenzt ist, kannst du den autorisierten öffentlichen SSH-Schlüssel mit einem erzwungenen Befehl beschränken. In `authorized_keys` kann ein serverseitiger Eintrag die Zugangsdaten an einen Wrapper binden:

```text
command="/usr/local/sbin/agent-release-wrapper",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleAgentPublicKey agent-release
```

Der Wrapper sollte eine kleine Menge von Argumenten verarbeiten, Shell-Metazeichen zurückweisen, die angeforderte Operation protokollieren und feste Binärdateien mit festen Pfaden aufrufen. Schreibe keinen Wrapper, der eine beliebige Zeichenfolge in `SSH_ORIGINAL_COMMAND` akzeptiert und an `sh -c` übergibt. Damit wird der uneingeschränkte Zugriff auf eine entfernte Shell lediglich hinter einem Funktionsnamen versteckt.

Erzwungene Befehle passen nicht zu jeder Wartungsaufgabe. Wenn ein Agent für eine Untersuchung tatsächlich eine Shell benötigt, verwende ein separates Untersuchungskonto mit Leserechten und ohne Möglichkeit zur Rechteausweitung. Schaffe für Änderungen einen eigenen, ausdrücklich geprüften Weg. Diagnose und Änderung in einem weitreichenden Konto zu vermischen, gibt dem Agenten zu viel Spielraum, eine unvollständige Schlussfolgerung in eine nicht rückgängig zu machende Aktion zu verwandeln.

Kurzlebige SSH-Zertifikate können den Bereinigungsaufwand verringern, wenn du bereits eine Zertifizierungsstelle mit klaren Ausgabekontrollen betreibst. Sie machen die Hostprüfung nicht überflüssig. Ein Zertifikat sagt etwas über das Clientkonto aus, der Host-Fingerabdruck sagt, welcher Server es erhalten hat.

## Die Befehlsprüfung muss die Wirkung zeigen, nicht eine vage Absicht

Eine Befehlsgenehmigung ist nur nützlich, wenn die prüfende Person genug Kontext sieht, um die Wirkung einzuschätzen. «Release bereitstellen» ist eine Absicht. `sudo systemctl restart payments-api` auf `app-production` als `agent_release` ist eine Aktion, die ein Reviewer beurteilen kann.

Prüfe das genaue Zielalias, das entfernte Konto, den wörtlichen Befehl, die Argumente und das Arbeitsverzeichnis. Prüfe außerdem, ob der Befehl eine Shell aufruft, Variablen expandiert, ein entferntes Skript liest, Inhalte herunterlädt oder `sudo` verwendet. Diese Details entscheiden, ob eine harmlos wirkende Anfrage weit über ihre Beschreibung hinausreichen kann.

Ein sinnvoller Genehmigungseintrag für eine entfernte Aktion sieht so aus:

```text
Target: app-production (app-prod.internal)
Account: agent_release
Command: /usr/local/sbin/agent-release-wrapper activate 2025.04.17-rc2
Reason: activate the approved release after smoke tests
```

Vergleiche ihn mit dieser Anfrage:

```text
ssh app-production "curl $URL | sudo sh"
```

Der zweite Befehl verbindet das Abrufen entfernter Inhalte, die Ausführung einer Shell, erhöhte Berechtigungen und einen Wert, der anders expandieren kann, als der Reviewer erwartet. Keine Hostprüfung macht das sicher. Weise den Befehl zurück und verlange ein Artefakt, dessen Digest zuvor geprüft wurde, ein festes Bereitstellungsprogramm und Argumente, die ein genehmigtes Release benennen.

Auch die Befehlsprüfung hat einen eigenen Fehler: Genehmigungsmüdigkeit. Wenn ein Agent für jedes harmlose `cat`, `git status` und jeden Gesundheitscheck eine Bestätigung verlangt, lernen Menschen, ohne Lesen zu genehmigen. Lege schreibgeschützte Untersuchungen hinter ein eingeschränktes Konto oder einen eng definierten Befehls-Wrapper. Interaktive Genehmigungen sollten Aktionen vorbehalten bleiben, die schreiben, neu starten, rotieren, Berechtigungen ändern oder eine Vertrauensgrenze überschreiten.

Sallyport kann SSH-Anmeldedaten in seinem verschlüsselten Tresor aufbewahren und für jede Nutzung eines ausgewählten Zugangsschlüssels eine Genehmigung verlangen. Der SSH-Kanal wird dabei über `sp-ssh` ausgeführt, ohne die Zugangsdaten dem Agenten offenzulegen. Du bist trotzdem dafür verantwortlich, den Host festzulegen und zu entscheiden, ob der sichtbare Befehl genehmigt werden sollte.

## Der gefährliche Fehlerpfad besteht meist aus einer Kette gewöhnlicher Abkürzungen

Die meisten SSH-Vorfälle in der Automatisierung beginnen nicht mit einem exotischen kryptografischen Angriff. Sie beginnen mit einer Abkürzung, die bei der Einrichtung harmlos klang.

Stell dir einen Release-Agenten mit `StrictHostKeyChecking=accept-new`, einem gemeinsamen Bereitstellungskonto und einem Genehmigungsdialog vor, der nur «deploy ausführen» sagt. Ein DNS-Eintrag zeigt kurz auf eine Ersatzmaschine, bevor das Inventar aktualisiert wurde. Der Agent sieht einen unbekannten Host, speichert dessen Schlüssel, meldet sich an der Ersatzmaschine an, weil er das gemeinsame Konto akzeptiert, und führt den Bereitstellungs-Wrapper aus. Der Wrapper hat weitreichende Schreibrechte, weil dasselbe Konto auch für Notfallreparaturen verwendet wird.

Für diese Abfolge ist kein Angreifer nötig. Ein gewöhnlicher Fehler bei der Benennung kann Artefakte in der falschen Umgebung bereitstellen, Bereitstellungsausgaben an eine unbeabsichtigte Maschine offenlegen oder einen unvorbereiteten Host ändern. Kommt ein Angreifer hinzu, der die Namensauflösung oder das Routing beeinflussen kann, bieten dieselben Abkürzungen eine weitaus gefährlichere Angriffsfläche.

Jede Kontrolle unterbricht einen anderen Punkt dieser Kette:

1. Ein vorab festgelegter H solchen?
