Heim  >  Artikel  >  Web-Frontend  >  node.js implementiert den Webterminalbetrieb für mehrere Benutzer

node.js implementiert den Webterminalbetrieb für mehrere Benutzer

php中世界最好的语言
php中世界最好的语言Original
2018-04-14 13:35:391549Durchsuche

Dieses Mal werde ich Ihnen node.js vorstellen, um den Mehrbenutzer-Webterminalbetrieb zu realisieren. Was sind die Vorsichtsmaßnahmen für node.js, um den Mehrbenutzer-Webterminalbetrieb zu realisieren? , lass uns einen Blick darauf werfen.

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

Ein Terminal ähnelt in unserem Verständnis einem Befehlszeilentool. Laienhaft ausgedrückt ist es ein Prozess, der eine Shell ausführen kann. 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 überwacht den Ausgang des untergeordneten Prozesses über den Systemaufruf wait4() 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 und Anpassung der Ausgabeinformationen, Sicherheitszugriff Steuerungs- und Berechtigungsverwaltung usw. Vor der konkreten Implementierung des Web-Terminals muss bewertet werden, welche dieser Funktionen am wichtigsten sind. Es ist ganz klar: die funktionale Implementierung der Shell, die Benutzererfahrung und die Sicherheit (das Web-Terminal ist eine im Online-Server bereitgestellte Funktion). , daher muss die Sicherheit gewährleistet sein. Nur unter der Voraussetzung, diese beiden Funktionen sicherzustellen, kann das Web-Pseudo-Terminal offiziell gestartet werden.

Betrachten wir zunächst die technische Umsetzung dieser beiden Funktionen (die serverseitige Technologie nutzt NodeJS):

Das Node-native-Modul stellt das Repl-Modul bereit, mit dem interaktive Eingaben implementiert und Ausgaben ausgeführt werden können. Es bietet auch Tab-Vervollständigung, benutzerdefinierte Ausgabestile und andere Funktionen. Es kann jedoch nur knotenbezogene Befehle ausführen, sodass es nichts erreichen kann Wir wollen den Zweck der System-Shell ausführen Das native Knotenmodul child_porcess stellt spawn bereit, eine uv_spawn-Funktion, die die zugrunde liegende libuv kapselt. Das zugrunde liegende Ausführungssystem ruft fork und execvp auf, um Shell-Befehle auszuführen. Es bietet jedoch keine anderen Funktionen des Pseudo-Terminals, wie z. B. die automatische Vervollständigung von Tabulatoren, Pfeiltasten zur Anzeige historischer Befehle usw.

Daher ist es unmöglich, ein Pseudo-Terminal mithilfe des nativen Moduls des Knotens auf der Serverseite zu implementieren. Es ist erforderlich, das Prinzip des Pseudo-Terminals und die Implementierungsrichtung auf der Knotenseite weiter zu untersuchen.

Pseudoterminal

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

Die Eingabe- und Ausgabeschnittstelle der obersten Ebene, die dem Zeichengerät bereitgestellt wird, die Zeilendisziplin der mittleren Ebene und der Hardwaretreiber der untersten Ebene

Unter diesen wird die Schnittstelle der obersten Ebene häufig durch Systemaufruffunktionen wie (Lesen, Schreiben) implementiert, während der zugrunde liegende Hardwaretreiber für die Master-Slave-Gerätekommunikation des Pseudo-Terminals verantwortlich ist, die vom Kernel bereitgestellt wird; Die Zeilendisziplin sieht relativ abstrakt aus, ist aber funktionell gesehen für die „Verarbeitung“ von Eingabe- und Ausgabeinformationen verantwortlich, beispielsweise für die Verarbeitung von Interrupt-Zeichen (Strg) während des Eingabevorgangs. + c) und einige Rückschrittzeichen (Rücktaste und Löschen) usw., während das Ausgabe-Neuzeilenzeichen n in rn usw. konvertiert wird.

Ein Pseudo-Terminal ist in zwei Teile unterteilt: das Master-Gerät und das Slave-Gerät. Sie sind ü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 auf die unterste Ebene konzentrieren und sehen, welche Funktionen das Betriebssystem bereitstellt . . 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, um die Berechtigungen des Slave-Geräts festzulegen. unlockpt, um das entsprechende Slave-Gerät zu entsperren und den Namen des Slave-Geräts abzurufen (ähnlich wie /dev/pts/123). Das Master-Gerät (Slave-Gerät) liest und schreibt Operationen und führt sie aus

Daher ist eine PTY-Bibliothek mit besserer Kapselung entstanden, die alle oben genannten Funktionen nur mit einer Forkpty-Funktion erreichen kann. 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.

Was das Thema Pseudo-Terminal-Sicherheit betrifft, werden wir es am Ende des Artikels besprechen.

Pseudo-Terminal-Implementierungsideen

Gemäß den Eigenschaften des Master-Slave-Geräts des Pseudo-Terminals verwalten wir den Lebenszyklus und seine Ressourcen des Pseudo-Terminals im übergeordneten Prozess, in dem sich das Master-Gerät befindet, und führen die Shell darin aus Untergeordneter Prozess, in dem sich das Slave-Gerät befindet. Während der Ausführung werden Informationen und Ergebnisse über eine bidirektionale Pipe an das Hauptgerät übertragen, und der Prozess, in dem sich das Hauptgerät befindet, stellt Standardausgaben nach außen bereit.

Lernen Sie hier von den Umsetzungsideen 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);
 }

Erstens über pty_forkpty (Posix-Implementierung von forkpty, kompatibel mit sunOS und Unix und andere Systeme) erstellen ein Master-Slave-Gerät und führen dann nach dem Festlegen der Berechtigungen im untergeordneten Prozess (Setuid, Setgid) den Systemaufruf pty_execvpe (Kapselung von execvpe) aus. Anschließend werden die Eingabeinformationen des Master-Geräts eingegeben wird hier ausgeführt (die vom untergeordneten Prozess ausgeführte Datei wird auf stdin überwacht);

Der übergeordnete Prozess stellt verwandte Objekte der Knotenschicht zur Verfügung, z. B. den FD des Hauptgeräts (über den das net.Socket-Objekt für die bidirektionale Datenübertragung erstellt werden kann) und registriert gleichzeitig die Nachrichtenwarteschlange von libuv &baton-> async, das ausgelöst wird, wenn der untergeordnete Prozess beendet wird&baton ->async message, run pty_after_waitpid function

Schließlich erstellt der übergeordnete Prozess 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, Blockieren des Prozesses, der auf eine bestimmte PID wartet, und die Exit-Informationen werden im dritten Parameter gespeichert). ), pty_waitpid-Funktion Die Funktion wait4 ist gekapselt und uv_async_send(&baton->async) wird am Ende der Funktion ausgeführt, 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, daher 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 erstellt die Verbindung des fd Der Socket implementiert die Eingabe und Ausgabe an den untergeordneten Prozess (Slave-Gerät). Der untergeordnete Prozess übergibt forkpty Nach der Erstellung wird die Operation login_tty ausgeführt, um stdin, stderr und stderr des untergeordneten Prozesses zurückzusetzen, und alle werden 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 Referenz-Forkpty-Implementierung

Darüber hinaus stellt die PTY-Bibliothek die Größeneinstellung des Pseudo-Terminals bereit, sodass wir die Layoutinformationen der Ausgabeinformationen des Pseudo-Terminals über Parameter anpassen können, sodass auch die Funktion zum Anpassen der Breite und Höhe der Befehlszeile im Web bereitgestellt wird Legen Sie einfach das Pseudoterminal in der PTY-Ebene fest. Legen Sie einfach die Fenstergröße fest, die in Zeichen gemessen wird.

Sicherheitsgarantie für Web-Terminals

Bei der Implementierung des Pseudo-Terminal-Backends auf Basis 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, da es die Hardware-Ressourcen vollständig ausnutzt und sehr praktisch für die Zuordnung der bezogenen Dateien des Hosts ist. 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) – durch Volumen Verwenden Sie für Binärdateien wie „/usr/local/bin/docker“ den Docker-Befehl des Hosts, um das Geschwister-Image zu öffnen und 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 der Ebene des Dateisystems , 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 Lösungen auf einer einzelnen Maschine in Betracht ziehen. Derzeit denkt der Autor nur an zwei Möglichkeiten:

Befehls-ACL, implementiert durch Befehls-Whitelist-eingeschränktes Bash-Chroot, erstellt einen Systembenutzer für jeden Benutzer und schränkt den Benutzerzugriffsbereich ein

Erstens sollte die Befehls-Whitelist-Methode eliminiert werden. 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 vom Pseudo-Terminal bereitgestellt und Das Vorhandensein von Sonderzeichen wie „Löschen“ kann nicht effektiv mit dem aktuell eingegebenen Befehl übereinstimmen. Daher weist die Whitelist-Methode zu viele Lücken auf und sollte aufgegeben werden.

Eingeschränktes Bash, ausgelöst durch /bin/bash -r, kann Benutzer explizit vom „cd-Verzeichnis“ einschränken, 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 der eingeschränkten Bash auszubrechen Shell, was nicht leicht vorherzusagen ist.

Letztendlich 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 springen, 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, so 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 die Konfigurationsdatei ausgeführt werden. Daher wird in der tatsächlichen Entwicklung Jailkit 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 der Basisbenutzer über die Jailkit-Konfigurationsdatei kopiert. Binärdateien und ihre DLLs, wie grundlegende Shell-Anweisungen, Git, Vim, Ruby usw.; schließlich wird für bestimmte Befehle eine zusätzliche Verarbeitung durchgeführt und Berechtigungen werden zurückgesetzt.

Bei der Handhabung der Dateizuordnung zwischen dem „neuen System“ und dem ursprünglichen System sind noch einige Kenntnisse 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 --bind-Implementierung, z. B. mount --bind /home/ttt/abc /usr/local/abc Es schirmt die Verzeichnisinformationen (Block) des gemounteten Verzeichnisses (/usr/local/abc) ab und behält die Zuordnungsbeziehung zwischen dem gemounteten Verzeichnis und dem gemounteten Verzeichnis im Speicher bei /usr/ Zugriff auf lokal/. abc fragt den Block von /home/ttt/abc über die Speicherzuordnungstabelle ab und führt dann Vorgänge aus, um eine Verzeichniszuordnung zu erreichen.

Schließlich müssen Sie nach der Initialisierung des „neuen Systems“ gefängnisbezogene Befehle über das Pseudo-Terminal ausführen:

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

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

Ich glaube, dass Sie die Methode beherrschen, nachdem Sie den Fall in diesem Artikel gelesen haben. Weitere spannende Informationen finden Sie in anderen verwandten Artikeln auf der chinesischen PHP-Website!

Empfohlene Lektüre:

So implementieren Sie ein Echart-Diagramm in AngularJS

So implementieren Sie einen Interlaced-Farbwechsel in JS

Das obige ist der detaillierte Inhalt vonnode.js implementiert den Webterminalbetrieb für mehrere Benutzer. 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