SSH, Benutzermodus, TCP / IP und WireGuard

Jeder, der eine Anwendung von einem Anbieter wie Fly.io (im Folgenden einfach Fly) hostet, muss möglicherweise über SSH eine Verbindung zum Server herstellen, auf dem diese Anwendung ausgeführt wird.



Aber Fly ist wie ein schwarzes Schaf unter anderen ähnlichen Plattformen. Unsere Hardware arbeitet in Rechenzentren auf der ganzen Welt. Unsere Server sind über das Anycast-Netzwerk mit dem Internet verbunden und über das WireGuard-Netzwerk miteinander verbunden. Wir nehmen Docker-Container von Benutzern und wandeln sie in Firecracker-Mikrovirtuals um. Und als wir anfingen, haben wir genau das getan, um unseren Kunden die Möglichkeit zu geben, "Edge-Anwendungen" auszuführen. Diese Anwendungen sind normalerweise relativ kleine, in sich geschlossene Codeteile, die sehr empfindlich auf die Netzwerkleistung reagieren. Daher müssen diese Codefragmente auf Servern ausgeführt werden, die sich so nahe wie möglich an den Benutzern befinden. In einer solchen Umgebung ist die Möglichkeit, über SSH eine Verbindung zum Server herzustellen, nicht so wichtig.







Aber jetzt nutzen nicht alle unsere Kunden Fly auf diese Weise. Heutzutage können Sie in der Fly-Umgebung problemlos den gesamten Code ausführen, der sich auf eine Anwendung bezieht. Wir haben das Verfahren zum Starten eines Ensembles von Diensten in einer Clusterumgebung vereinfacht . Solche Dienste können über sichere Kommunikationskanäle miteinander interagieren , Daten dauerhaft speichern und über das WireGuard-Netzwerk mit ihren Betreibern kommunizieren. Wenn ich die Geschichte über unser System im gleichen Sinne fortsetze, muss ich Links zu allen Materialien bereitstellen, die wir in den letzten Monaten geschrieben haben.



Auf jeden Fall hatten wir keine normale SSH-Unterstützung.



Es ist natürlich klar, dass Sie einfach einen Container mit einem SSH-Dienst erstellen können, mit dem Sie über SSH eine Verbindung herstellen können. Die Fly-Plattform unterstützt die Arbeit mit gängigen TCP-Ports (und auch mit UDP-Ports ). Wenn der Client mithilfe der Datei fly.toml



unser Anycast-Netzwerk über seinen seltsamen SSH-Port "informiert", organisiert das System das Routing seiner SSH-Verbindungen. Danach funktioniert alles wie gewünscht.



Aber diejenigen, die Container erstellen, tun dies normalerweise nicht, und wir schlagen nicht vor, dass sie dies tun. Aus diesem Grund haben wir Fly mit SSH-Unterstützung ausgestattet. Was wir getan haben, ist eher ungewöhnlich angeordnet. In diesem Artikel, der aus zwei Teilen besteht, werde ich darüber sprechen.



Teil 1: 6PN und Hallpass



Ich schrieb eine Menge darüber, wie private Netzwerke in Fly angeordnet sind. Kurz gesagt, es stellt sich heraus, dass das, was wir haben, mit einer vereinfachten IPv6-Version des GCP oder AWS "Virtual Private Clouds" verglichen werden kann. Wir nennen dieses System 6PN. Wenn eine Anwendungsinstanz (Firecracker Microvirtual Machine) in Fly gestartet wird, weisen wir dieser Instanz ein spezielles IPv6-Präfix zu. Das Präfix enthält mehrere Bezeichner: den Bezeichner der Anwendung, die Organisation, der die Anwendung gehört, und die Hardwareressourcen, auf denen die Anwendung ausgeführt wird. Wir verwenden ein bisschen eBPF-Code, um solche IPv6-Pakete statisch in unserem internen WireGuard-Netzwerk weiterzuleiten und um sicherzustellen, dass Clients keine Verbindung zu den Systemen von Organisationen herstellen können, an denen sie nicht beteiligt sind.



Sie können WireGuard auch verwenden, um die von uns erstellten privaten IPv6-Netzwerke mit anderen Netzwerken zu verbinden. Unsere API kann WireGuard-Konfigurationen erstellen , die beispielsweise auf EC2-Hosts für das RDS Postgres- Proxy verwendet werden können . Bei Bedarf können Sie auch WireGuard-Clients (unter Windows, Linux oder macOS) verwenden, um den Entwicklungscomputer mit Ihrem eigenen privaten Netzwerk zu verbinden.



Sie wissen wahrscheinlich schon, worauf ich hinaus will.



Wir haben in Go einen sehr kleinen und sehr einfachen SSH-Server namens Hallpass geschrieben. Es kann mit "Hallo Welt!" Verglichen werden, das mit der Go-Bibliothek erstellt wurde x/crypto/ssh



... (Wenn ich dies wieder tat, würde ich wahrscheinlich nur verwenden , das Glider Labs Paket für SSH - Server zu bauen. Mit diesem Paket, unser Server würde buchstäblich eine „Hallo, Welt!“ Initialisierung von allen Instanzen Kracher microvirtual Maschinen durchgeführt und Hallpass ist mit Bindung an ihre 6PN-Adressen gestartet.



Wenn Sie im 6PN-Netzwerk Ihres Unternehmens arbeiten können (z. B. über eine WireGuard-Verbindung), können Sie sich mit Hallpass bei der mikrovirtuellen Instanz anmelden.



Es gibt nur ein interessantes Detail darüber, wie Hallpass funktioniert. Es geht um Authentifizierung. Infrastrukturelemente in unserem Produktionsnetzwerk haben normalerweise keinen direkten Zugriff auf unsere APIs oder die zugrunde liegenden Datenbanken. Und die Instanzen von Firecracker selbst haben diesen Zugriff natürlich auch nicht. Dies führt zu einigen Schwierigkeiten beim Ändern der Kommunikationseinstellungen. Wie können Sie beispielsweise die Frage beantworten, welche Art von Schlüsseln Sie benötigen, um eine Verbindung zu bestimmten Instanzen von mikrovirtuellen Maschinen herzustellen?



Wir haben eine Problemumgehung für dieses Problem gefunden, indem wir auf SSH-Client-Zertifikate zurückgegriffen haben. Anstatt jedes Mal, wenn sich ein Benutzer von einem neuen Host aus anmelden möchte, die Schlüssel übergeben zu müssen, erstellen wir ein Stammzertifikat, um diesen Benutzer zu organisieren. Der öffentliche Schlüssel für dieses Stammzertifikat wird in unserem privaten DNS-System gehostet, und Hallpass kontaktiert den DNS, um dieses Zertifikat bei jedem Anmeldeversuch zu erhalten. Unsere API signiert neue Zertifikate für Benutzer. Diese Zertifikate können verwendet werden, um sich beim System anzumelden.



Möglicherweise haben Sie Fragen zu dieser Lösung. Deshalb werde ich einige weitere Details über ihn verraten.



Lassen Sie uns zunächst über Zertifikate sprechen. Jahrzehnte X.509 Wahnsinn"Möglicherweise hat das Wort" Zertifikat "zu einem unangenehmen Nachgeschmack geführt. Und ich beschuldige Sie nicht dafür. Bei der Organisation von SSH-Verbindungen sollten jedoch Zertifikate verwendet werden, da solche Zertifikate in diesem Fall eine gute Lösung darstellen. SSH-Zertifikate sind jedoch keine X.509-Zertifikate. Es verwendet ein eigenes OpenSSH-Format , und zu diesen Zertifikaten kann im Allgemeinen nichts Besonderes gesagt werden. Sie haben wie alle anderen Zertifikate ein "Ablaufdatum", mit dem Sie kurzlebige Schlüssel erstellen können (und das ist fast immer sogenau das, was Sie brauchen). Natürlich können Sie damit einer ganzen Gruppe von Servern einen öffentlichen Schlüssel zuweisen, wodurch eine beliebige Anzahl privater Schlüssel autorisiert werden kann. Die entsprechenden Server müssen nicht ständig aktualisiert werden.



Als nächstes folgt unsere API- und Zertifikatsignatur. Gut! Wir sind sehr vorsichtig, aber diese Zertifikate sind im Allgemeinen so sicher wie Fly Access Token. Derzeit können Zertifikate nicht besser geschützt werden als Token, da das Token die Bereitstellung neuer Versionen von Anwendungscontainern ermöglicht. Die Arbeit mit Web PKI X.509 CA erfordert viele Formalitäten. Wir verzichten auf sie.



Und schließlich unser DNS. Ich stimme zu, sie sieht aus wie völliger Unsinn. Aber es ist wirklich nicht so schlimm. Auf jedem Host, auf dem mikrovirtuelle Firecracker-Instanzen ausgeführt werden, wird eine lokale Version unseres privaten DNS-Servers ausgeführt (ein kleines Programm, das in Rust geschrieben wurde). Der eBPF-Code stellt sicher, dass Firecracker-Computer nur mit diesem DNS-Server interagieren können, indem er von der 6PN-Adresse ihres Servers aus darauf verweist. (Aus technischer Sicht kann ein Benutzer nur Anfragen an die private DNS-API dieses Servers stellen, und alle Anfragen anderer Benutzer werden rekursiv verarbeitet.) Ein DNS-Server kann (ich weiß, dass es ungewöhnlich aussieht) eine Organisation zuverlässig identifizieren durch Analysieren der Quell-IP-Adressanforderungen. Im Allgemeinen arbeiten wir so.



All dies geschieht in den Tiefen unseres Systems, Benutzer können dies alles nicht sehen. Benutzer sahen nur einen Befehl flyctl ssh issue -a



, der ein neues Zertifikat von unserer API anforderte und es dann an den lokalen SSH-Agenten weitergab. Danach stellte sich heraus, dass SSH-Verbindungen im Allgemeinen betriebsbereit waren. All dies war ordentlich arrangiert. Aber jedes Geschäft kann immer genauer als zuvor abgewickelt werden.



Teil 2: Arbeiten in einem WireGuard-Netzwerk aus dem Benutzermodus mit TCP / IP



Es gibt ein Problem mit dem obigen Schema der Verwendung von SSH, nämlich dass nicht jeder WireGuard installiert hat. Das entsprechende Programm sollte jedoch von jedem installiert werden. WireGuard ist eine großartige Technologie, die bei der Verwaltung von Anwendungen, die auf der Fly-Plattform ausgeführt werden, sehr hilfreich ist. Wie dem auch sei, einige unserer Benutzer haben WireGuard nicht.



Zwar müssen solche Benutzer auch über SSH mit ihren Systemen arbeiten.



Auf den ersten Blick kann die Tatsache, dass jemand WireGuard nicht installiert hat, als unüberwindbares Hindernis erscheinen. Wie funktioniert WireGuard? Auf dem Computer des Benutzers wird eine neue Netzwerkschnittstelle erstellt. Dies ist entweder eine WireGuard-Schnittstelle auf Kernelebene (unter Linux) oder ein Tunnel, an den ein WireGuard-Dienst im Benutzermodus angeschlossen ist (in allen anderen Betriebssystemen). Ohne diese Netzwerkschnittstelle können Sie nicht mit dem WireGuard-Netzwerk arbeiten.



Wenn Sie WireGuard jedoch aus dem richtigen Blickwinkel betrachten, können Sie feststellen, dass dies aus technischer Sicht nicht der Fall ist. Zum Konfigurieren einer neuen Netzwerkschnittstelle sind nämlich Berechtigungen auf Betriebssystemebene erforderlich. Aber um Pakete an zu senden 51820/udp



Es sind keine Berechtigungen erforderlich. Alles, was erforderlich ist, damit das WireGuard-Protokoll funktioniert, kann als nicht privilegierter Prozess gestartet werden, der im Benutzermodus ausgeführt wird. So funktioniert das wireguard-go- Paket .



Auf diese Weise können Sie nur das WireGuard-Handshake-Verfahren ausführen. Gleichzeitig geht es aber nicht um den Informationsaustausch mit den Knoten des WireGuard-Netzwerks, da Sie nicht einfach beliebige Daten aufnehmen und an ein anderes System senden können, das mit diesem Netzwerk verbunden ist. Ein solches System wartet auf Pakete, die normalerweise über TCP / IP-Netzwerke übertragen werden. Standard-Systemtools, die UDP-Sockets unterstützen, helfen beim Aufbau einer TCP-Verbindung mit solchen Sockets nicht weiter.



Wäre es schwierig, einen kleinen Code zu schreiben, der TCP im Benutzermodus aktiviert, der ausschließlich zur Unterstützung der Kommunikation über das WireGuard-Netzwerk dient, wiederum im Benutzermodus? Ein solcher Code würde es Fly-Benutzern ermöglichen, über SSH eine Verbindung zu ihren Systemen herzustellen, ohne die Software installieren zu müssen, die WireGuard antreibt.



Ich war rücksichtslos darin, all dies auf dem Slack-Kanal zu diskutieren, auf dem Jason Donenfeld war. Nachdem ich laut nachgedacht hatte, ging ich nämlich ins Bett. Als ich aufwachte, hatte Jason all dies bereits mit gVisor implementiert und in die WireGuard-Bibliothek aufgenommen.



Das Interessanteste hier ist gVisor. Wir haben bereits darüber geschrieben... Wenn jemand es nicht weiß, ist gVisor im Wesentlichen ein Linux-Betriebssystem für den Benutzerbereich, Linux, das in Golang implementiert ist und als Ersatz runc



für das Ausführen von Containern verwendet wird. Dies ist eigentlich ein völlig verrücktes Projekt. Und wenn Sie es benutzen, können Sie anderen stolz davon erzählen, denn es ist einfach eine großartige Sache. In seinen Tiefen gibt es eine vollständige TCP / IP-Implementierung, die in Go geschrieben ist und Eingangs- und Ausgangsdaten verarbeitet, die als normale Puffer dargestellt werden []byte



.



Dann wurden ein paar Tweets getwittert und ein paar Stunden später bekam ich eine sehr nette E-Mail von Ben Barkert... Ben hatte bereits an verschiedenen Aufgaben im Zusammenhang mit dem gVisor-Netzwerksubsystem gearbeitet. Er war daran interessiert, woran wir arbeiteten. Er wollte wissen, ob wir mit ihm zusammenarbeiten möchten. Uns hat seine Idee gefallen, an diesem Projekt zusammenzuarbeiten. Und jetzt, ohne auf Details einzugehen, haben wir eine zertifikatbasierte SSH-Implementierung, die über die gVisor TCP / IP-Implementierung im Benutzermodus ausgeführt wird. Dies alles interagiert mit dem WireGuard-Netzwerk über ein benutzerdefiniertes Moduspaket wireguard-go



. Und schließlich ist dieses Ding in die eingebaut flyctl



.



Um SSH mit zu verwenden, geben Sie flyctl



einfach einen Befehl wie den folgenden ein:



flyctl ssh shell personal dogmatic-potato-342.internal

      
      





Und jetzt, damit Sie die Unglaubwürdigkeit des Geschehens erkennen können, werde ich Ihnen ein wenig über diesen Befehl erzählen. Also - dogmatic-potato-342.internal



ist ein interner DNS-Name, der nur von einem privaten DNS-Server im 6PN-Netzwerk aufgelöst wird. All dies ist effizient, da das ssh shell



Dienstprogramm im Modus flyctl



den TCP / IP-Stack-gVisor-Benutzermodus verwendet. In gVisor gibt es jedoch keinen Code für eine DNS-Suche. Dies ist nur eine Standard-Go-Bibliothek, die wir getäuscht haben, indem wir unsere spezielle TCP / IP-Schnittstelle hineingeschoben haben.



Flyctl



Dies ist übrigens ein Open-Source-Projekt(Dies sollte der Fall sein, da Clients es auf ihren eigenen Computern verwenden müssen, auf denen sie an der Entwicklung beteiligt sind.) Wenn Sie interessiert sind, können Sie daher einfach den Code lesen. Ben hat einen netten Code in den pkg- Ordner geschrieben . Und der Rest des Codes, schrecklich, schrieb ich. In Go ist die Bereitstellung von IP-Kommunikation im WireGuard-Netzwerk überraschend einfach. Wenn Sie jemals eine einfache TCP / IP-Programmierung durchgeführt haben, ist diese Einfachheit möglicherweise unglaublich. Objekte aus dem gVisor-TCP-Stack stellen eine direkte Verbindung zum Netzwerkcode der Standardbibliothek her.



Schauen Sie sich diesen Code an:



tunDev, gNet, err := netstack.CreateNetTUN(localIPs, []net.IP{dnsIP}, mtu)
if err != nil {
    return nil, err
}

// ...

wgDev := device.NewDevice(tunDev, device.NewLogger(cfg.LogLevel, "(fly-ssh) "))

      
      





CreateNetTUN



Ist ein Teil wireguard-go



. Hier werden die Funktionen von gVisor verwendet. Zunächst verfügen wir über ein synthetisches Tunnelgerät, mit dem normale Pakete gelesen und geschrieben werden können, die den WireGuard-Betrieb ermöglichen. Zweitens haben wir die net.Dialer- Funktion , einen Wrapper für gVisor, der im Go-Code verwendet werden kann und über diesen mit dem entsprechenden WireGuard-Netzwerk interagiert.



Das ist alles? Im Allgemeinen ja. Im Folgenden wird beispielsweise beschrieben, wie wir diese Mechanismen verwenden, um mit DNS zu arbeiten:



resolv: &net.Resolver{
    PreferGo: true,
    Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        return gNet.DialContext(ctx, network, net.JoinHostPort(dnsIP.String(), "53"))
    },
},

      
      





Dies ist normaler Netzwerkcode, der in Go geschrieben wurde. Im Allgemeinen stellte sich heraus, dass es gut war.



Natürlich sollte das jeder tun.



Dank ein paar hundert Codezeilen (abgesehen vom Implementierungscode für den Linux-Benutzermodus, den wir von gVisor erhalten; aber was zu tun ist - gibt es kein Entkommen aus den Abhängigkeiten) können Sie ein neues Netzwerk mit Kryptografie erstellen Authentifizierung zu Ihrer Verfügung. Ein Netzwerk, auf das jederzeit und von fast jedem Programm aus zugegriffen werden kann.



Es ist klar, dass ein solches Netzwerk erheblich langsamer ist als das auf der TCP / IP-Kernimplementierung basierende. Aber ist es oft wirklich wichtig? Und hat es insbesondere oft eine Bedeutung bei der Lösung von periodisch auftretenden Problemen, für deren Lösung normalerweise seltsame, unbekannte TLS-Tunnel gebaut werden? Wenn es auf Geschwindigkeit ankommt, können Sie einfach zur regulären WireGuard-Implementierung wechseln.



Auf jeden Fall hat das, was ich gesagt habe, unser großes Problem gelöst. Schließlich eignet sich dieses System nicht nur zur Organisation der Arbeit von SSH. Wir hosten auch Postgres-Datenbanken. Es ist sehr praktisch, wenn es möglich ist, durch Ausführen eines einfachen Befehls eine Shell buchstäblich von überall zu öffnen psql



, unabhängig davon, ob es zum richtigen Zeitpunkt möglich ist, WireGuard für macOS zu installieren.



Verwenden Sie WireGuard?






All Articles