
SwiftNIO SSH ist eine programmatische Implementierung von SSH mit SwiftNIO.
Dieses Projekt enthält SSH-Unterstützung unter Verwendung von SwiftNIO.
SwiftNIO SSH ist eine programmatische Implementierung von SSH: das heißt, es ist eine Sammlung von APIs, die es Programmierern ermöglichen, SSH-sprechende Endpunkte zu implementieren. Entscheidend ist, dass es eher wie libssh2 als wie OpenSSH ist. SwiftNIO SSH liefert keine produktionsreifen SSH-Clients und -Server, sondern bietet die Bausteine, um solche Clients und Server zu erstellen.
Es gibt eine Reihe von Gründen, eine programmatische SSH-Implementierung bereitzustellen. Einer ist, dass SSH eine einzigartige Beziehung zur Benutzerinteraktivität hat. Technische Benutzer sind es gewohnt, interaktiv mit SSH zu arbeiten, entweder um Befehle auf entfernten Maschinen auszuführen oder um interaktive Shells zu nutzen. Die Fähigkeit, programmatisch auf diese Anfragen zu reagieren, ermöglicht interessante alternative Interaktionsmodi. Als frühere Beispiele können wir Twisted's Manhole nennen, das eine programmatische SSH-Implementierung namens conch verwendet, um einen interaktiven Python-Interpreter in einem laufenden Python-Server bereitzustellen, oder ssh-chat, einen SSH-Server, der anstelle der normalen SSH-Shell-Funktionalität einen Chatroom bereitstellt. Innovative Verwendungen sind auch für die TCP-Weiterleitung denkbar.
Ein weiterer guter Grund, eine programmatische SSH-Lösung bereitzustellen, ist, dass es nicht ungewöhnlich ist, dass Dienste mit anderen Diensten interagieren müssen, indem sie Befehle ausführen. Während Process dies für den lokalen Anwendungsfall löst, müssen die auszuführenden Befehle manchmal entfernt liegen. Obwohl Process einen ssh-Client als Subprozess starten könnte, um diese Ausführung durchzuführen, kann es wesentlich einfacher sein, SSH direkt aufzurufen. Dies ist der Zielanwendungsfall von libssh2. SwiftNIO SSH bietet das Äquivalent der Netzwerk- und Kryptographieschicht von libssh2 und ermöglicht es motivierten Benutzern, SSH-Sitzungen direkt aus Swift-Diensten heraus zu steuern.
Die aktuellsten Versionen von SwiftNIO SSH unterstützen Swift 5.9 und neuer. Die minimale Swift-Version, die von den SwiftNIO SSH-Versionen unterstützt wird, ist unten im Detail aufgeführt:
SwiftNIO SSH unterstützt SSHv2 mit den folgenden Funktionen:
SwiftNIO SSH bietet einen SwiftNIO ChannelHandler, NIOSSHHandler. Dieser Handler implementiert den Großteil des SSH-Protokolls direkt. Benutzer werden nicht erwartet, SSH-Nachrichten direkt zu generieren: stattdessen interagieren sie mit dem NIOSSHHandler über Kind-Channels und Delegaten.
SSH ist ein multiplexiertes Protokoll: Jede SSH-Verbindung wird in mehrere bidirektionale Kommunikationskanäle unterteilt, die entsprechend als "Channels" bezeichnet werden. SwiftNIO SSH spiegelt diese Konstruktion wider, indem es eine Abstraktion von "Kind-Channels" verwendet. Wenn ein Peer einen neuen SSH-Channel erstellt, erstellt SwiftNIO SSH einen neuen NIO Channel, der den gesamten Datenverkehr auf diesem SSH-Channel repräsentiert. Innerhalb dieses Kind-Channel sind alle Ereignisse streng in Bezug zueinander geordnet: Ereignisse in verschiedenen Channels können jedoch von der Implementierung frei verschachtelt werden.
Eine aktive SSH-Verbindung sieht daher wie folgt aus:
┌ ─ NIO Channel ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ ┌────────────────────────────────┐ │
│ │
│ │ │ │
│ │
│ │ │ │
│ NIOSSHHandler │───────────────────────┐
│ │ │ │ │
│ │ │
│ │ │ │ │
│ │ │
│ └────────────────────────────────┘ │ │
│
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
│
│
│
│
▼
┌── SSH Child Channel ─────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ├───┐
│ │ │ │ │ │ │
│ │ │ │ │ │ ├───┐
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ User Handler │ │ User Handler │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ └────────────────────────────────┘ └────────────────────────────────┘ │ │ │
│ │ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
Ein SSH-Channel wird mit einem Channel-Typ aufgerufen. NIOSSH unterstützt drei: session, directTCPIP und forwardedTCPIP. Der häufigste Channel-Typ ist session: session wird verwendet, um den Aufruf eines Programms darzustellen, sei es ein bestimmtes benanntes Programm oder eine Shell. Die anderen beiden Channel-Typen beziehen sich auf die TCP-Portweiterleitung und werden später besprochen.
Ein SSH-Channel arbeitet mit einem einzigen Datentyp: SSHChannelData. Diese Struktur kapselt die Tatsache, dass SSH sowohl normale als auch "erweiterte" Channel-Daten unterstützt. Die normalen Channel-Daten (SSHChannelData.DataType.channel) werden für die überwiegende Mehrheit der Kerndaten verwendet. In session-Channels wird der .channel-Datentyp für Standardeingabe und -ausgabe verwendet: der .stdErr-Datentyp wird für die Standardfehlerausgabe verwendet (natürlich). In TCP-Weiterleitungskanälen wird nur der .channel-Datentyp verwendet und repräsentiert die weitergeleiteten Daten.
Ein session-Channel repräsentiert den Aufruf eines Befehls. Wie genau der Channel arbeitet, wird in einer Reihe von eingehenden Benutzerereignissen mitgeteilt. Die folgenden Ereignisse sind wichtig:
SSHChannelRequestEvent.PseudoTerminalRequest: Fordert die Zuweisung eines Pseudo-Terminals an.SSHChannelRequestEvent.EnvironmentRequest: Fordert eine einzelne Umgebungsvariable für den Befehlsaufruf an. Wird immer vor dem Befehl selbst gesendet.SSHChannelRequestEvent.ShellRequest: Fordert an, dass der Befehl, der ausgeführt werden soll, die Shell des authentifizierten Benutzers ist.SSHChannelRequestEvent.ExecRequest: Fordert den Aufruf eines bestimmten Befehls an.SSHChannelRequestEvent.ExitStatus: Wird verwendet, um zu signalisieren, dass der entfernte Befehl beendet wurde, und teilt den Exit-Code mit.SSHChannelRequestEvent.ExitSignal: Wird verwendet, um anzuzeigen, dass der entfernte Befehl als Reaktion auf ein Signal beendet wurde, und um welches Signal es sich handelt.SSHChannelRequestEvent.SignalRequest: Wird verwendet, um ein Signal an den entfernten Befehl zu senden.SSHChannelRequestEvent.LocalFlowControlRequest: Wird verwendet, um anzuzeigen, ob der Client in der Lage ist, die Ctrl-Q/Ctrl-S-Flusskontrolle selbst durchzuführen.SSHChannelRequestEvent.WindowChangeRequest: Wird verwendet, um eine Änderung der Größe des Terminalfensters auf dem Client an das zugewiesene Pseudo-Terminal zu übermitteln.Diese Ereignisse werden in Portweiterleitungsnachrichten nicht verwendet. SSH-Implementierungen, die .session-Typ-Channels unterstützen, müssen darauf vorbereitet sein, die meisten oder alle dieser Ereignisse auf verschiedene Weise zu behandeln.
Jedes dieser Ereignisse hat auch ein wantReply-Feld. Dies zeigt an, ob die Anfrage eine Antwort benötigt, um Erfolg oder Misserfolg anzuzeigen. Wenn ja, werden die folgenden beiden Ereignisse verwendet:
ChannelSuccessEvent, um Erfolg zu melden.ChannelFailureEvent, um Misserfolg zu melden.Das SSH-Netzwerkprotokoll verwendet in den Kind-Channels durchgängig Halbschließung. NIO Channels haben standardmäßig die Halbschließungsunterstützung deaktiviert, und SwiftNIO SSH respektiert diese Voreinstellung auch in seinen Kind-Channels. Wenn Sie diese Einstellung jedoch auf dem Standardwert belassen, verhalten sich die SSH-Kind-Channels äußerst unerwartet. Daher wird dringend empfohlen, dass alle Kind-Channels die Halbschließungsunterstützung aktivieren:
channel.setOption(ChannelOptions.allowRemoteHalfClosure, true)
Dies verwendet dann die standardmäßige NIO-Halbschließungsunterstützung. Der entfernte Peer, der EOF sendet, wird mit einem eingehenden Benutzerereignis, ChannelEvent.inputClosed, mitgeteilt. Um selbst EOF zu senden, rufen Sie close(mode: .output) auf.
Die Benutzerauthentifizierung ist ein wesentlicher Bestandteil von SSH. Zur Verwaltung verwendet SwiftNIO SSH ein Paar von Delegatprotokollen: NIOSSHClientUserAuthenticationDelegate und NIOSSHServerUserAuthenticationDelegate. Clients und Server sollten Implementierungen dieser Delegatprotokolle bereitstellen, um die Benutzerauthentifizierung zu verwalten.
Das Client-Protokoll ist einfach: SwiftNIO SSH ruft die Methode nextAuthenticationType(availableMethods:nextChallengePromise:) auf dem Delegaten auf. Die availableMethods sind eine Instanz von NIOSSHAvailableUserAuthenticationMethods, die mitteilt, welche Authentifizierungsmethoden der Server als akzeptabel vorgeschlagen hat. Der Delegat kann dann das nextChallengePromise entweder mit einer neuen Authentifizierungsanfrage oder mit nil abschließen, um anzuzeigen, dass dem Client nichts mehr zum Ausprobieren einfällt.
Das Server-Protokoll ist komplexer. Der Delegat muss eine supportedAuthenticationMethods-Eigenschaft bereitstellen, die mitteilt, welche Authentifizierungsmethoden vom Delegaten unterstützt werden. Dann wird jedes Mal, wenn der Client eine Benutzerauthentifizierungsanfrage sendet, die Methode requestReceived(request:responsePromise:) aufgerufen. Diese kann mehrmals parallel aufgerufen werden, da Clients berechtigt sind, Authentifizierungsanfragen parallel zu stellen. Das responsePromise sollte mit dem Ergebnis der Authentifizierung erfolgreich abgeschlossen werden. Es gibt drei Ergebnisse: .success und .failure sind einfach, aber im Prinzip kann der Server mehrere Herausforderungen mit .partialSuccess(remainingMethods:) verlangen.
Direkte Portweiterleitung ist die Portweiterleitung vom Client zum Server. In diesem Modus hört der Client traditionell auf einem lokalen Port und leitet eingehende Verbindungen an den Server weiter. Er bittet den Server, diese Verbindungen als ausgehende Verbindungen zu einem bestimmten Host und Port weiterzuleiten.
Diese Channels können von Clients direkt mit dem Channel-Typ .directTCPIP geöffnet werden.
Remote-Portweiterleitung ist eine weniger häufige Situation, in der der Client den Server bittet, auf einer bestimmten Adresse und einem bestimmten Port zu lauschen und alle eingehenden Verbindungen an den Client weiterzuleiten. Da der Client dieses Verhalten anfordern muss, tut er dies mit globalen Anfragen.
Globale Anfragen werden mit NIOSSHHandler.sendGlobalRequest initiiert und über einen GlobalRequestDelegate empfangen und verarbeitet. Heute werden zwei globale Anfragen unterstützt:
GlobalRequest.TCPForwardingRequest.listen(host:port:): eine Anfrage, dass der Server auf einem bestimmten Host und Port lauscht.GlobalRequest.TCPForwardingRequest.cancel(host:port:): eine Anfrage, das Lauschen auf dem bestimmten Host und Port zu beenden.Server können über einen GlobalRequestDelegate über diese Anfragen benachrichtigt werden und darauf antworten. Die zu implementierende Methode ist tcpForwardingRequest(_:handler:promise:). Diese Delegatenmethode wird jedes Mal aufgerufen, wenn eine globale Anfrage empfangen wird. Die Antwort auf die Anfrage wird in promise übergeben.
Weitergeleitete Channels werden dann mit dem Channel-Typ .forwardedTCPIP vom Server zum Client gesendet.
| SwiftNIO SSH | Minimale Swift-Version |
|---|
0.0.0 ..< 0.3.0 | 5.1 |
0.3.0 ..< 0.4.0 | 5.2 |
0.4.0 ..< 0.5.0 | 5.4 |
0.5.0 ..< 0.6.2 | 5.5.2 |
0.6.2 ..< 0.9.0 | 5.6 |
0.9.0 ..< 0.9.2 | 5.8 |
0.9.2 ..< 0.10.0 | 5.9 |
0.10.0 ... 0.12.0 | 5.10 |
0.12.0 ..< 0.13.0 | 6.0 |
0.13.0 ..< | 6.1 |
SSHChannelRequestEvent.SubsystemRequest: Wird verwendet, um den Aufruf eines bestimmten Subsystems anzufordern. Die Bedeutung ist anwendungsspezifisch.