Heim >Web-Frontend >js-Tutorial >So implementieren Sie die Mehrbenutzer-Webterminalanzeige mit node.js

So implementieren Sie die Mehrbenutzer-Webterminalanzeige mit node.js

亚连
亚连Original
2018-06-23 16:11:471371Durchsuche

In diesem Artikel werden hauptsächlich die Unterstützung von node.js für die Implementierung von Mehrbenutzer-Webterminals und die Lösung zur Gewährleistung der Sicherheit von Webterminals vorgestellt.

Terminal (Befehlszeile) bietet als gemeinsame Funktion lokaler IDEs eine sehr leistungsstarke Unterstützung für Git-Operationen und Dateioperationen von Projekten. Da bei WebIDE kein Web-Pseudo-Terminal vorhanden ist, kann die Bereitstellung einer gekapselten Befehlszeilenschnittstelle die Entwickler überhaupt nicht zufriedenstellen. Um ein besseres Benutzererlebnis zu bieten, wurde daher auf die Entwicklung von Web-Pseudo-Terminals gesetzt Agenda.

Forschung

Terminal ist, soweit wir wissen, einem Befehlszeilentool ähnlich kann eine Shell ausführen. Jedes Mal, wenn Sie eine Reihe von Befehlen in die Befehlszeile eingeben und die Eingabetaste drücken, gibt der Terminalprozess einen untergeordneten Prozess aus, um den eingegebenen Befehl auszuführen. Der Terminalprozess wartet darauf, dass der untergeordnete Prozess über den Systemaufruf wait4() beendet wird, und gibt ihn aus es durch die offengelegten stdout-Ausführungsinformationen.

Wenn Sie eine Terminalfunktion ähnlich der Lokalisierung auf der Webseite implementieren, müssen Sie möglicherweise mehr tun: Netzwerkverzögerungs- und Zuverlässigkeitsgarantie, Shell-Benutzererfahrung so nah wie möglich an der Lokalisierung, Breite und Höhe der Web-Terminal-Benutzeroberfläche usw Anpassung der Ausgabeinformationen, Sicherheitszugriffskontrolle und Berechtigungsverwaltung usw. Vor der konkreten Implementierung des Web-Terminals muss bewertet werden, welche dieser Funktionen am wichtigsten sind. Es ist sehr klar: die funktionale Implementierung der Shell, die Benutzererfahrung und die Sicherheit (das Web-Terminal ist eine Funktion, die auf dem Online-Server bereitgestellt wird). , daher muss die Sicherheit gewährleistet sein. Nur unter der Voraussetzung, diese beiden Funktionen sicherzustellen, kann das Web-Pseudo-Terminal offiziell gestartet werden.

Im Folgenden wird zunächst die technische Umsetzung dieser beiden Funktionen betrachtet (serverseitige Technologie nutzt NodeJS):

Das Node-Native-Modul stellt das Repl-Modul bereit, mit dem interaktive Eingaben und implementiert werden können führt die Ausgabe aus und bietet außerdem eine Tab-Vervollständigungsfunktion, einen benutzerdefinierten Ausgabestil und andere Funktionen. Es kann jedoch nur knotenbezogene Befehle ausführen, sodass der Zweck der Ausführung der System-Shell nicht erfüllt werden kann die zugrunde liegende libuv-Funktion verwendet die zugrunde liegenden Systemaufrufe fork und execvp, um Shell-Befehle auszuführen. Es bietet jedoch keine anderen Funktionen des Pseudo-Terminals, wie z. B. automatische Tab-Vervollständigung, Pfeiltasten zur Anzeige historischer Befehle usw.

Daher ist es unmöglich, ein Pseudo-Terminal mit dem nativen Modul zu implementieren des Knotens auf der Serverseite, und wir müssen weiterhin das Prinzip des Terminals und die Implementierungsrichtung auf der Knotenseite untersuchen.

Pseudo-Terminal

Pseudo-Terminal ist kein echtes Terminal, sondern ein vom Kernel bereitgestellter „Dienst“. Terminaldienste umfassen normalerweise drei Schichten:

Die für Zeichengeräte bereitgestellte Eingabe- und Ausgabeschnittstelle der obersten Ebene, die Leitungsdisziplin der mittleren Ebene und den Hardwaretreiber der untersten Ebene

Unter ihnen die Die Schnittstelle der obersten Ebene wird häufig durch die Implementierung von Systemaufruffunktionen wie (Lesen, Schreiben) übergeben. Der zugrunde liegende Hardwaretreiber ist für die Master-Slave-Gerätekommunikation des Pseudo-Terminals verantwortlich, die vom Kernel bereitgestellt wird relativ abstrakt, aber tatsächlich ist es für die Funktion „Verarbeitung“ von Eingabe- und Ausgabeinformationen verantwortlich, z. B. für die Verarbeitung von Interrupt-Zeichen (Strg + C) und einigen Rollback-Zeichen (Rücktaste und Löschen) während des Eingabevorgangs, während die Ausgabe-Neuzeile konvertiert wird Zeichen n bis rn usw.

Ein Pseudo-Terminal ist in zwei Teile unterteilt: das Master-Gerät und das Slave-Gerät. Sie sind unten über eine bidirektionale Pipe (Hardware-Treiber) verbunden, die die Standardleitungsdisziplin implementiert. Alle Eingaben vom Pseudo-Terminal-Master werden auf dem Slave widergespiegelt und umgekehrt. Die Ausgabeinformationen des Slave-Geräts werden auch über eine Pipe an das Master-Gerät gesendet, sodass die Shell im Slave-Gerät des Pseudo-Terminals ausgeführt werden kann, um die Terminalfunktion abzuschließen.

Das Slave-Gerät des Pseudo-Terminals kann die Tab-Vervollständigung des Terminals und andere Shell-Spezialbefehle tatsächlich simulieren. Unter der Voraussetzung, dass das native Modul des Knotens die Anforderungen nicht erfüllen kann, müssen wir uns daher auf die unterste Ebene konzentrieren Sehen Sie sich an, welche Funktionen das Betriebssystem bietet. Derzeit stellt die Glibc-Bibliothek die Schnittstelle posix_openpt bereit, der Prozess ist jedoch etwas umständlich:

Verwenden Sie posix_openpt, um ein Pseudo-Terminal-Master-Gerät zu öffnen. grantpt legt die Berechtigungen des Slave-Geräts fest. unlockpt entsperrt das entsprechende Slave-Gerät Name des Slave-Geräts (ähnlich wie /dev/pts /123) Master-Gerät (Slave) liest und schreibt, führt Operationen aus

Daher ist eine PTY-Bibliothek mit besserer Kapselung entstanden, durch die alle oben genannten Funktionen erreicht werden können nur eine Forkpty-Funktion. Durch das Schreiben eines Knoten-C++-Erweiterungsmoduls und die Verwendung der pty-Bibliothek zur Implementierung eines Terminals, das die Befehlszeile vom Gerät in einem Pseudo-Terminal ausführt.

Zum Thema Pseudo-Terminal-Sicherheit werden wir am Ende des Artikels darauf eingehen.

Pseudo-Terminal-Implementierungsideen

Entsprechend den Eigenschaften der Master- und Slave-Geräte des Pseudo-Terminals verwalten wir das Pseudo-Terminal im Übergeordneter Prozess, in dem sich das Master-Gerät befindet. Der Lebenszyklus und seine Ressourcen werden im Unterprozess des Slave-Geräts ausgeführt. Die Informationen und Ergebnisse während der Ausführung werden über eine bidirektionale Pipeline an das Master-Gerät und den Prozess des Masters übertragen Gerät stellt Standardausgabe nach außen bereit.

Erfahren Sie hier die Implementierungsideen von pty.js:

pid_t pid = pty_forkpty(&master, name, NULL, &winp);

 switch (pid) {
 case -1:
  return Nan::ThrowError("forkpty(3) failed.");
 case 0:
  if (strlen(cwd)) chdir(cwd);

  if (uid != -1 && gid != -1) {
  if (setgid(gid) == -1) {
   perror("setgid(2) failed.");
   _exit(1);
  }
  if (setuid(uid) == -1) {
   perror("setuid(2) failed.");
   _exit(1);
  }
  }

  pty_execvpe(argv[0], argv, env);

  perror("execvp(3) failed.");
  _exit(1);
 default:
  if (pty_nonblock(master) == -1) {
  return Nan::ThrowError("Could not set master fd to nonblocking.");
  }

  Local<Object> obj = Nan::New<Object>();
  Nan::Set(obj,
  Nan::New<String>("fd").ToLocalChecked(),
  Nan::New<Number>(master));
  Nan::Set(obj,
  Nan::New<String>("pid").ToLocalChecked(),
  Nan::New<Number>(pid));
  Nan::Set(obj,
  Nan::New<String>("pty").ToLocalChecked(),
  Nan::New<String>(name).ToLocalChecked());

  pty_baton *baton = new pty_baton();
  baton->exit_code = 0;
  baton->signal_code = 0;
  baton->cb.Reset(Local<Function>::Cast(info[8]));
  baton->pid = pid;
  baton->async.data = baton;

  uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);

  uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));

  return info.GetReturnValue().Set(obj);
 }

Erstellen Sie zuerst das Master-Slave-Gerät über pty_forkpty (Posix-Implementierung von forkpty, kompatibel mit Systemen wie sunOS und Unix), legen Sie dann die Berechtigungen (setuid, setgid) im untergeordneten Prozess fest und führen Sie den Systemaufruf pty_execvpe (Kapselung) aus von execvpe) und dann Die Eingabeinformationen des Hauptgeräts werden hier ausgeführt (die vom untergeordneten Prozess ausgeführte Datei ist sh und hört auf stdin)

Der übergeordnete Prozess stellt verwandte Objekte der Knotenebene zur Verfügung B. den fd des Hauptgeräts (über den fd Sie können ein net.Socket-Objekt für die bidirektionale Datenübertragung erstellen) und die Nachrichtenwarteschlange von libuv registrieren &baton->async ;async-Nachricht wird ausgelöst und die Funktion pty_after_waitpid wird ausgeführt;

Der letzte übergeordnete Prozess erstellt einen untergeordneten Prozess, indem er uv_thread_create aufruft, um auf die Exit-Nachricht des vorherigen untergeordneten Prozesses zu warten (durch Ausführen des Systemaufrufs wait4, Blockierung). Der Prozess wartet auf eine bestimmte PID und die Exit-Informationen werden im dritten Parameter gespeichert. Die Funktion pty_waitpid kapselt die Funktion wait4 und führt am Ende der Funktion uv_async_send(&baton->async) aus, um die Nachricht auszulösen.

Nach der Implementierung des PTY-Modells auf der untersten Ebene müssen einige stdio-Vorgänge auf der Knotenebene durchgeführt werden. Da das Pseudo-Terminal-Hauptgerät durch Ausführen eines Systemaufrufs im übergeordneten Prozess erstellt wird und der Dateideskriptor des Hauptgeräts über fd der Knotenschicht zugänglich gemacht wird, werden auch die Eingaben und Ausgaben des Pseudo-Terminals gelesen und gelesen Schreiben Sie gemäß fd, um den entsprechenden Dateityp zu erstellen, z. B. PIPE, FILE, um ihn zu vervollständigen. Tatsächlich wird das Pseudo-Terminal-Master-Gerät auf Betriebssystemebene als PIPE mit bidirektionaler Kommunikation betrachtet. Erstellen Sie über net.Socket(fd) einen Socket auf der Knotenebene, um bidirektionale E/A des Datenflusses zu implementieren. Das Slave-Gerät des Pseudo-Terminals verfügt auch über denselben Eingang wie das Master-Gerät, sodass der entsprechende Befehl im Sub-Gerät ausgeführt wird. Der Prozess und die Ausgabe des Unterprozesses werden ebenfalls über PIPE im Hauptgerät reflektiert und lösen dann das Datenereignis des Socket-Objekts der Knotenschicht aus.

Die Beschreibung der Eingabe und Ausgabe des übergeordneten Prozesses, des Master-Geräts, des untergeordneten Prozesses und des Slave-Geräts hier ist etwas verwirrend, deshalb werde ich sie hier erklären. Die Beziehung zwischen dem übergeordneten Prozess und dem Hauptgerät ist: Der übergeordnete Prozess erstellt das Hauptgerät über einen Systemaufruf (kann als PIPE betrachtet werden) und erhält den FD des Hauptgeräts. Der übergeordnete Prozess realisiert die Eingabe und Ausgabe an den untergeordneten Prozess (Slave-Gerät), indem er den Connect-Socket des fd erstellt. Nachdem der untergeordnete Prozess über forkpty erstellt wurde, wird die Operation login_tty ausgeführt und stdin, stderr und stderr des untergeordneten Prozesses zurückgesetzt und alle auf den fd des Slave-Geräts (das andere Ende der PIPE) kopiert. Daher sind die Eingabe und Ausgabe des untergeordneten Prozesses alle mit dem fd des Slave-Geräts verknüpft. Die Ausgabedaten des untergeordneten Prozesses werden über PIPE übertragen, und die Befehle des übergeordneten Prozesses werden von PIPE gelesen. Einzelheiten finden Sie in der Forkpty-Implementierung in der Referenzliteratur.

Darüber hinaus bietet die PTY-Bibliothek Einstellungen für die Pseudo-Terminal-Größe, sodass wir die Layoutinformationen der Pseudo-Terminal-Ausgabeinformationen über Parameter anpassen können Bietet auch Anpassungsbefehle auf der Webseite. Für die Linienbreite und -höhe müssen Sie nur die Größe des Pseudo-Terminalfensters auf der PTY-Ebene festlegen.

Sicherheitsgarantie für Webterminals

Bei der Implementierung des Pseudo-Terminal-Hintergrunds basierend auf der von glibc bereitgestellten PTY-Bibliothek gibt es keine Sicherheitsgarantie. Wir möchten ein Verzeichnis auf dem Server direkt über das Webterminal betreiben, können aber über den Pseudo-Terminal-Hintergrund direkt Root-Berechtigungen erhalten. Dies ist für den Dienst nicht tolerierbar, da es sich direkt auf die Sicherheit des Servers auswirkt ist: Ein „System“, in dem Benutzer gleichzeitig online sind, die Zugriffsrechte jedes Benutzers konfiguriert werden können, auf bestimmte Verzeichnisse zugegriffen werden kann, Bash-Befehle optional konfiguriert werden können, Benutzer voneinander isoliert sind und Benutzer nichts vom Strom wissen Umgebung, und die Umgebung ist einfach und leicht bereitzustellen.

Die am besten geeignete Technologie ist Docker. Als Isolierung auf Kernelebene kann es die Hardwareressourcen voll ausnutzen und ist sehr praktisch für die Zuordnung hostbezogener Dateien. Aber Docker ist nicht allmächtig. Wenn das Programm in einem Docker-Container ausgeführt wird, wird die Zuweisung eines Containers zu jedem Benutzer viel komplizierter und unterliegt nicht der Kontrolle des Betriebs- und Wartungspersonals. Dies ist das sogenannte DooD (). Docker aus Docker) – Verwenden Sie Binärdateien wie das Volume „/usr/local/bin/docker“ und verwenden Sie den Docker-Befehl des Hosts, um das Geschwister-Image zu öffnen, um den Build-Dienst auszuführen. Es gibt viele Mängel bei der Verwendung des Docker-in-Docker-Modus, die in der Branche häufig diskutiert werden, insbesondere auf Dateisystemebene, die in den Referenzen zu finden sind. Daher eignet sich die Docker-Technologie nicht zur Lösung von Sicherheitsproblemen beim Benutzerzugriff für Dienste, die bereits in Containern ausgeführt werden.

Als nächstes müssen wir eine Einzelmaschinenlösung in Betracht ziehen. Derzeit denkt der Autor nur an zwei Lösungen:

Befehls-ACL, eingeschränktes Bash-Chroot über die Befehls-Whitelist implementieren, für jeden Benutzer einen Systembenutzer erstellen und den Benutzerzugriffsbereich einsperren

Erstens Befehl Die Whitelist-Methode ist diejenige, die entfernt werden sollte. Erstens gibt es keine Garantie dafür, dass die Bash verschiedener Linux-Versionen gleich ist. Zweitens kann sie aufgrund der Tab-Befehlsvervollständigungsfunktion nicht effektiv ausgeschöpft werden Der vom Pseudo-Terminal bereitgestellte Wert und das Vorhandensein von Sonderzeichen wie delete können nicht effektiv mit dem aktuell eingegebenen Befehl übereinstimmen. Daher weist die Whitelist-Methode zu viele Lücken auf und sollte aufgegeben werden.

Eingeschränkter Bash, ausgelöst durch /bin/bash -r, kann Benutzer explizit vom „cd-Verzeichnis“ ausschließen, weist jedoch viele Mängel auf:

Nicht ausreichend, um die Ausführung völlig nicht vertrauenswürdiger Software zu ermöglichen. Wenn ein Befehl ausgeführt wird, bei dem es sich um ein Shell-Skript handelt, deaktiviert rbash alle in der Shell erstellten Einschränkungen für die Ausführung des Skripts. Wenn Benutzer bash oder dash von rbash aus ausführen, erhalten sie eine unbegrenzte Shell. Es gibt viele Möglichkeiten, aus einer eingeschränkten Bash-Shell auszubrechen, die nicht leicht vorhersehbar sind.

Schließlich scheint es nur eine Lösung zu geben, nämlich Chroot. chroot ändert das Stammverzeichnis des Benutzers und führt den Befehl im angegebenen Stammverzeichnis aus. Sie können nicht aus dem angegebenen Stammverzeichnis herausspringen, sodass Sie nicht auf alle Verzeichnisse des Originalsystems gleichzeitig zugreifen können. Chroot erstellt eine Systemverzeichnisstruktur, die vom Originalsystem isoliert ist, sodass verschiedene Befehle des Originalsystems nicht ausgeführt werden können Wird im „neuen System“ verwendet, weil es neu und leer ist und schließlich isoliert und transparent ist, wenn es von mehreren Benutzern verwendet wird, was unseren Anforderungen voll und ganz entspricht.

Daher haben wir uns letztendlich für Chroot als Sicherheitslösung für Webterminals entschieden. Die Verwendung von Chroot erfordert jedoch viel zusätzlichen Verarbeitungsaufwand, einschließlich nicht nur der Erstellung neuer Benutzer, sondern auch der Initialisierung von Befehlen. Oben wurde auch erwähnt, dass das „neue System“ leer ist und keine ausführbaren Binärdateien wie „ls, pmd“ usw. vorhanden sind, sodass eine Initialisierung des „neuen Systems“ erforderlich ist. Viele Binärdateien sind jedoch nicht nur statisch mit vielen Bibliotheken verknüpft, sondern sind zur Laufzeit auch auf dynamische Linkbibliotheken (DLLs) angewiesen. Aus diesem Grund ist es auch erforderlich, viele DLLs zu finden, von denen jeder Befehl abhängt, was äußerst umständlich ist. Um Benutzern zu helfen, diesen langweiligen Prozess loszuwerden, wurde Jailkit ins Leben gerufen.

Jailkit, wirklich einfach zu bedienen

Jailkit wird, wie der Name schon sagt, dazu verwendet, Benutzer einzusperren. Das Jailkit verwendet intern chroot, um das Benutzerstammverzeichnis zu erstellen, und stellt eine Reihe von Anweisungen zum Initialisieren und Kopieren von Binärdateien und allen ihren DLLs bereit. Diese Funktionen können über Konfigurationsdateien ausgeführt werden. Daher wird Jailkit in der tatsächlichen Entwicklung mit Initialisierungs-Shell-Skripten verwendet, um eine Isolierung des Dateisystems zu erreichen.

Die Initialisierungs-Shell bezieht sich hier auf das Vorverarbeitungsskript. Da chroot das Stammverzeichnis für jeden Benutzer festlegen muss, wird in der Shell für jeden Benutzer ein entsprechender Benutzer mit Befehlszeilenberechtigungen erstellt und die Jailkit-Konfigurationsdatei übergeben kopiert grundlegende Binärdateien und ihre DLLs, wie grundlegende Shell-Befehle, Git, Vim, Ruby usw., und führt schließlich eine zusätzliche Verarbeitung für bestimmte Befehle durch und setzt Berechtigungen zurück.

Bei der Verarbeitung der Dateizuordnung zwischen dem „neuen System“ und dem ursprünglichen System sind noch einige Fähigkeiten erforderlich. Der Autor hat einmal andere Verzeichnisse als das von chroot festgelegte Benutzerstammverzeichnis in Form von Softlinks zugeordnet. Beim Zugriff auf die Softlinks im Gefängnis wurde jedoch immer noch ein Fehler gemeldet und die Datei konnte nicht gefunden werden Merkmale von chroot: Es ist nicht gestattet, auf das Dateisystem außerhalb des Root-Verzeichnisses zuzugreifen. Wenn die Zuordnung über Hardlinks erfolgt, ist es möglich, die Hardlink-Dateien im von chroot festgelegten Benutzer-Root-Verzeichnis zu ändern. Die Erstellung usw. kann nicht korrekt dem Verzeichnis des ursprünglichen Systems zugeordnet werden und der Hardlink kann nicht mit dem Verzeichnis verbunden werden, sodass der Hardlink die Anforderungen nicht erfüllt und schließlich über mount --bind implementiert wird. B. mount --bind /home/ttt/abc /usr/local/abc, das durch Verzeichnisinformationen (Block) des bereitgestellten Verzeichnisses (/usr/local/abc) geschützt ist und die Zuordnungsbeziehung zwischen den bereitgestellten Verzeichnissen aufrechterhält und das gemountete Verzeichnis im Speicher wird durch den Speicher geleitet. Die Zuordnungstabelle fragt den Block von /home/ttt/abc ab und führt dann Vorgänge aus, um eine Verzeichniszuordnung zu erreichen.

Schließlich müssen Sie nach der Initialisierung des „neuen Systems“ Jail-bezogene Befehle über das Pseudoterminal ausführen:

sudo jk_chrootlaunch -j /usr/local/jailuser/${creater} -u $ {creater} -x /bin/bashr

Nach dem Öffnen des Bash-Programms kommunizieren Sie mit der Web-Terminal-Eingabe (über Websocket), die das Hauptgerät über PIPE empfängt.

Ich habe das Obige für Sie zusammengestellt und hoffe, dass es Ihnen in Zukunft hilfreich sein wird.

Verwandte Artikel:

Was sollten Sie bei der Verwendung von React.setState beachten?

Wie verwende ich den gemeinsamen Header des Seite in Vue Componentization (ausführliches Tutorial)

Über Funktionsdrosselung und Funktionsstabilisierung in JS (ausführliches Tutorial)

Wie man three.js verwendet Implementierung von 3D-Kino

So implementieren Sie die seitlich verschiebbare Menükomponente in Vue

Das obige ist der detaillierte Inhalt vonSo implementieren Sie die Mehrbenutzer-Webterminalanzeige mit node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn