Maison >interface Web >js tutoriel >node.js implémente le fonctionnement du terminal Web pour plusieurs utilisateurs

node.js implémente le fonctionnement du terminal Web pour plusieurs utilisateurs

php中世界最好的语言
php中世界最好的语言original
2018-04-14 13:35:391588parcourir

Cette fois, je vais vous présenter node.js pour réaliser un fonctionnement de terminal Web multi-utilisateurs. Quelles sont les précautions pour que node.js réalise un fonctionnement de terminal Web multi-utilisateurs. , jetons un coup d'oeil.

Le terminal (ligne de commande), en tant que fonction courante des IDE locaux, dispose d'un support très puissant pour les opérations git et les opérations sur les fichiers des projets. Pour WebIDE, en l'absence de pseudo-terminal web, fournir uniquement une interface de ligne de commande encapsulée est totalement incapable de satisfaire les développeurs. Ainsi, afin d'offrir une meilleure expérience utilisateur, le développement de pseudo-terminaux web a été mis sur le devant de la scène. ordre du jour.

Recherche

Selon nous, un terminal est similaire à un outil de ligne de commande. En termes simples, il s'agit d'un processus qui peut exécuter un shell. Chaque fois que vous entrez une série de commandes sur la ligne de commande et appuyez sur Entrée, le processus terminal créera un processus enfant pour exécuter la commande saisie. Le processus terminal surveille la sortie du processus enfant via l'appel système wait4() et génère des sorties. via la sortie standard exposée.

Si vous implémentez une fonction de terminal similaire à la localisation côté Web, vous devrez peut-être faire plus : garantie de délai et de fiabilité du réseau, expérience utilisateur shell aussi proche que possible de la localisation, largeur et hauteur de l'interface utilisateur du terminal Web et adaptation des informations de sortie, sécurité Accès gestion des contrôles et des autorisations, etc. Avant d'implémenter spécifiquement le terminal Web, il est nécessaire d'évaluer lesquelles de ces fonctions sont les plus essentielles : l'implémentation fonctionnelle du shell, l'expérience utilisateur et la sécurité (le terminal Web est une fonction fournie dans le serveur en ligne). , la sécurité doit donc être garantie). Ce n'est que sous réserve d'assurer ces deux fonctions que le pseudo-terminal Web pourra être officiellement lancé.

Considérons d'abord la mise en œuvre technique de ces deux fonctions (la technologie côté serveur utilise nodejs) :

Le module natif du nœud fournit le module repl, qui peut être utilisé pour implémenter une entrée interactive et exécuter une sortie. Il fournit également la complétion par onglets, des styles de sortie personnalisés et d'autres fonctions. Cependant, il ne peut exécuter que des commandes liées au nœud, il ne peut donc pas réaliser cela. nous voulons exécuter. Le but du shell système. Le module natif du nœud child_porcess fournit spawn, une fonction uv_spawn qui encapsule la libuv sous-jacente. Le système d'exécution sous-jacent appelle fork et execvp pour exécuter les commandes shell. Cependant, il ne fournit pas d'autres fonctionnalités du pseudo-terminal, telles que la saisie semi-automatique des onglets, les touches fléchées pour afficher les commandes historiques, etc.

Par conséquent, il est impossible d'implémenter un pseudo-terminal en utilisant le module natif du nœud côté serveur. Il est nécessaire de continuer à explorer le principe du pseudo-terminal et la direction de mise en œuvre côté nœud.

Pseudo-terminal

Le pseudo terminal n'est pas un véritable terminal, mais un « service » fourni par le noyau. Les services terminaux se composent généralement de trois couches :

L'interface d'entrée et de sortie de niveau supérieur fournie au périphérique de caractère, à la discipline de ligne de niveau intermédiaire et au pilote matériel de niveau inférieur

Parmi eux, l'interface de niveau supérieur est souvent implémentée via des fonctions d'appel système, telles que (lecture, écriture) ; tandis que le pilote matériel sous-jacent est responsable de la communication du périphérique maître-esclave du pseudo-terminal, qui est fournie par le noyau ; la discipline de ligne semble relativement abstraite, mais en fait, fonctionnellement parlant, elle est responsable du « traitement » des informations d'entrée et de sortie, comme le traitement des caractères d'interruption (ctrl) pendant le processus d'entrée. + c) et certains caractères de retour arrière (retour arrière et suppression), etc., tout en convertissant le caractère de nouvelle ligne de sortie n en rn, etc.

Un pseudo-terminal est divisé en deux parties : le périphérique maître et le périphérique esclave. Ils sont connectés en bas via un canal bidirectionnel (pilote matériel) qui implémente la discipline de ligne par défaut. Toute entrée du pseudo-terminal maître est répercutée sur l'esclave et vice versa. Les informations de sortie du périphérique esclave sont également envoyées au périphérique maître via un tube, de sorte que le shell puisse être exécuté dans le périphérique esclave du pseudo-terminal pour terminer la fonction du terminal.

Le périphérique esclave du pseudo-terminal peut véritablement simuler la complétion des onglets du terminal et d'autres commandes spéciales du shell. Par conséquent, en partant du principe que le module natif du nœud ne peut pas répondre aux besoins, nous devons nous concentrer sur la couche inférieure et voir quelles fonctions le système d'exploitation fournit. . Actuellement, la bibliothèque glibc fournit l'interface posix_openpt, mais le processus est un peu lourd :

Utilisez posix_openpt pour ouvrir un périphérique maître pseudo-terminal grantpt afin de définir les autorisations du périphérique esclave unlockpt pour déverrouiller le périphérique esclave correspondant et obtenir le nom du périphérique esclave (similaire à /dev/pts/123). Le périphérique maître (esclave) lit et écrit et effectue des opérations

Par conséquent, une bibliothèque pty avec une meilleure encapsulation a émergé, qui peut réaliser toutes les fonctions ci-dessus grâce à une simple fonction forkpty. En écrivant un module d'extension node C++ et en utilisant la bibliothèque pty pour implémenter un terminal qui exécute la ligne de commande depuis le périphérique dans un pseudo-terminal.

Concernant la question de la sécurité des pseudo-terminaux, nous en parlerons en fin d’article.

Idées d'implémentation de pseudo-terminaux

Selon les caractéristiques du périphérique maître-esclave du pseudo terminal, nous gérons le cycle de vie et ses ressources du pseudo terminal dans le processus parent où se trouve le périphérique maître, et exécutons le shell dans le processus enfant où se trouve le périphérique esclave. Pendant l'exécution, les informations et les résultats sont transmis au périphérique principal via un canal bidirectionnel, et la sortie standard est fournie par le processus où se trouve le périphérique principal.

Apprenez des idées d'implémentation de pty.js ici :

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);
 }

Tout d'abord, via pty_forkpty (implémentation posix de forkpty, compatible avec sunOS et Unix et autres systèmes) créez un périphérique maître-esclave, puis après avoir défini les autorisations dans le processus enfant (setuid, setgid), exécutez l'appel système pty_execvpe (encapsulation de execvpe), puis les informations d'entrée du périphérique maître seront exécuté ici (le fichier exécuté par le processus enfant Pour sh, écoutera stdin

); Le processus parent expose les objets associés à la couche nœud, tels que le fd du périphérique principal (à travers lequel l'objet net.Socket peut être créé pour une transmission de données bidirectionnelle), et enregistre en même temps la file d'attente de messages de libuv&baton-> async, qui est déclenché lorsque le processus enfant quitte&baton ->message async, exécutez la fonction pty_after_waitpid

Enfin, le processus parent crée un processus enfant en appelant uv_thread_create pour écouter le message de sortie du processus enfant précédent (en exécutant l'appel système wait4, bloquant le processus à l'écoute d'un pid spécifique, et les informations de sortie sont stockées dans le troisième paramètre. ), fonction pty_waitpid La fonction wait4 est encapsulée et uv_async_send(&baton->async) est exécuté à la fin de la fonction pour déclencher le message.

Après avoir implémenté le modèle pty au niveau de la couche inférieure, certaines opérations stdio doivent être effectuées au niveau de la couche nœud. Étant donné que le périphérique principal du pseudo-terminal est créé en exécutant un appel système dans le processus parent et que le descripteur de fichier du périphérique principal est exposé à la couche nœud via fd, alors l'entrée et la sortie du pseudo-terminal sont également lues et écrit selon le fd pour créer le type de fichier correspondant, tel que PIPE, FILE à compléter. En fait, au niveau du système d'exploitation, le dispositif maître du pseudo-terminal est considéré comme un PIPE, avec une communication bidirectionnelle. Créez un socket au niveau de la couche nœud via net.Socket(fd) pour implémenter les E/S bidirectionnelles du flux de données. Le périphérique esclave du pseudo-terminal a également la même entrée que le périphérique maître, de sorte que la commande correspondante soit exécutée dans le sous-terminal. processus, et la sortie du sous-processus est également reflétée dans le périphérique principal via PIPE, puis déclenchera l'événement de données de l'objet Socket de la couche nœud.

La description de l'entrée et de la sortie du processus parent, du périphérique maître, du processus enfant et du périphérique esclave ici est un peu déroutante, je vais donc l'expliquer ici. La relation entre le processus parent et le périphérique principal est la suivante : le processus parent crée le périphérique principal via un appel système (peut être considéré comme un PIPE) et obtient le fd du périphérique principal. Le processus parent crée la connexion du fd Le socket implémente l'entrée et la sortie vers le processus enfant (périphérique esclave). Le processus enfant passe forkpty Après la création, l'opération login_tty est effectuée pour réinitialiser stdin, stderr et stderr du processus enfant, et tous sont copiés sur le fd du périphérique esclave (l'autre extrémité du PIPE). Par conséquent, l'entrée et la sortie du processus enfant sont toutes associées au fd du périphérique esclave. Les données de sortie du processus enfant passent par PIPE et les commandes du processus parent sont lues à partir de PIPE. Pour plus de détails, veuillez consulter l'implémentation de référence de forkpty

De plus, la bibliothèque pty fournit le réglage de la taille du pseudo-terminal, nous pouvons donc ajuster les informations de mise en page des informations de sortie du pseudo-terminal via des paramètres, cela fournit donc également la fonction d'ajuster la largeur et la hauteur de la ligne de commande sur le Web. côté, définissez simplement le pseudo-terminal dans la couche pty. Définissez simplement la taille de la fenêtre, qui est mesurée en caractères.

Garantie de sécurité du terminal Web

Il n'y a aucune garantie de sécurité lors de l'implémentation d'un backend pseudo-terminal basé sur la bibliothèque pty fournie par la glibc. Nous souhaitons exploiter directement un répertoire sur le serveur via le terminal Web, mais nous pouvons obtenir directement les autorisations root via le fond du pseudo-terminal. Ceci est intolérable pour le service car cela affecte directement la sécurité du serveur. est : un "système" dans lequel les utilisateurs sont en ligne en même temps, les droits d'accès de chaque utilisateur peuvent être configurés, des répertoires spécifiques sont accessibles, les commandes bash peuvent être configurées en option, les utilisateurs sont isolés les uns des autres, les utilisateurs ignorent le courant environnement, et l’environnement est simple et facile à déployer.

Le choix technologique le plus approprié est Docker. En tant qu'isolation au niveau du noyau, il peut utiliser pleinement les ressources matérielles et est très pratique pour mapper les fichiers associés de l'hôte. Mais Docker n'est pas tout-puissant. Si le programme s'exécute dans un conteneur Docker, alors l'attribution d'un conteneur à chaque utilisateur deviendra beaucoup plus compliquée, et il n'est pas sous le contrôle du personnel d'exploitation et de maintenance. C'est ce qu'on appelle DooD (. docker hors du docker) -- via le volume Pour les fichiers binaires tels que "/usr/local/bin/docker", utilisez la commande docker de l'hôte pour ouvrir l'image sœur afin d'exécuter le service de build. Il existe de nombreuses lacunes dans l'utilisation du mode docker-in-docker qui sont souvent évoquées dans l'industrie, notamment au niveau du système de fichiers , que l'on retrouve dans les références. Par conséquent, la technologie Docker n’est pas adaptée pour résoudre les problèmes de sécurité d’accès des utilisateurs aux services déjà exécutés dans des conteneurs.

Ensuite, nous devons envisager des solutions sur une seule machine. À l'heure actuelle, l'auteur ne pense qu'à deux options :

La commande ACL, implémentée via la commande whitelist restreint bash chroot, crée un utilisateur système pour chaque utilisateur et restreint la portée d'accès de l'utilisateur

Tout d'abord, la méthode de liste blanche des commandes doit être éliminée. Tout d'abord, il n'y a aucune garantie que le bash des différentes versions de Linux soit le même ; fourni par le pseudo terminal et La présence de caractères spéciaux tels que delete ne peut pas correspondre efficacement à la commande actuellement saisie. Par conséquent, la méthode de la liste blanche présente trop de failles et devrait être abandonnée.

Le bash restreint, déclenché par /bin/bash -r, peut restreindre explicitement les utilisateurs du "répertoire cd", mais il présente de nombreux défauts :

Pas suffisant pour permettre l’exécution de logiciels totalement non fiables. Lorsqu'une commande qui s'avère être un script shell est exécutée, rbash désactive toutes les restrictions créées dans le shell pour exécuter le script. Lorsque les utilisateurs exécutent bash ou dash depuis rbash, ils obtiennent un shell illimité. Il existe de nombreuses façons de sortir d'un bash restreint coquille, ce qui n’est pas facile à prévoir.

En fin de compte, il semble qu’il n’y ait qu’une seule solution, qui est le chroot. chroot modifie le répertoire racine de l'utilisateur et exécute la commande dans le répertoire racine spécifié. Vous ne pouvez pas sortir du répertoire racine spécifié, vous ne pouvez donc pas accéder à tous les répertoires du système d'origine en même temps, chroot créera une structure de répertoires système isolée du système d'origine, de sorte que diverses commandes du système d'origine ne peuvent pas être exécutées. utilisé dans le « nouveau système » car Il est nouveau et vide enfin, il est isolé et transparent lorsqu'il est utilisé par plusieurs utilisateurs, ce qui répond pleinement à nos besoins.

Nous avons donc finalement choisi chroot comme solution de sécurité pour les terminaux web. Cependant, l'utilisation de chroot nécessite beaucoup de traitements supplémentaires, notamment la création de nouveaux utilisateurs, mais également l'initialisation des commandes. Il est également mentionné ci-dessus que le « nouveau système » est vide et qu'il n'y a pas de fichiers binaires exécutables, tels que « ls, pmd », etc., donc l'initialisation du « nouveau système » est nécessaire. Cependant, de nombreux fichiers binaires sont non seulement liés statiquement à de nombreuses bibliothèques, mais s'appuient également sur des bibliothèques de liens dynamiques (dll) lors de l'exécution. Pour cette raison, il est également nécessaire de trouver de nombreuses dll dont dépend chaque commande, ce qui est extrêmement fastidieux. Afin d'aider les utilisateurs à se débarrasser de ce processus ennuyeux, le jailkit a vu le jour.

jailkit, tellement utile

Jailkit, comme son nom l'indique, est utilisé pour emprisonner les utilisateurs. Le jailkit utilise chroot en interne pour créer le répertoire racine de l'utilisateur et fournit une série d'instructions pour initialiser et copier les fichiers binaires et toutes leurs dll. Ces fonctions peuvent être exploitées via le fichier de configuration. Par conséquent, dans le développement réel, jailkit est utilisé avec des scripts shell d'initialisation pour réaliser l'isolation du système de fichiers.

Le shell d'initialisation fait ici référence au script de prétraitement Puisque chroot doit définir le répertoire racine pour chaque utilisateur, un utilisateur correspondant est créé dans le shell pour chaque utilisateur avec des autorisations de ligne de commande, et l'utilisateur de base est copié via le fichier de configuration du jailkit. Fichiers binaires et leurs dll, tels que les instructions de base du shell, git, vim, ruby, etc. enfin, un traitement supplémentaire est effectué pour certaines commandes et les autorisations sont réinitialisées.

Certaines compétences sont encore nécessaires dans le processus de gestion du mappage de fichiers entre le « nouveau système » et le système d'origine. L'auteur a déjà mappé des répertoires autres que le répertoire racine de l'utilisateur défini par chroot sous forme de liens symboliques. Cependant, lors de l'accès aux liens symboliques dans la prison, une erreur était toujours signalée et le fichier était introuvable. caractéristiques de chroot. , n'a pas l'autorisation d'accéder au système de fichiers en dehors du répertoire racine ; si le mappage est établi via des liens physiques, il est possible de modifier les fichiers de liens physiques dans le répertoire racine de l'utilisateur défini par chroot, mais les opérations impliquant une suppression, la création, etc. ne peut pas être effectuée correctement. Il est mappé sur le répertoire du système d'origine et le lien physique ne peut pas se connecter au répertoire, donc le lien physique ne répond finalement pas aux exigences via le montage ; --bind implémentation, telle que mount --bind /home/ttt/abc /usr/local/abc Il protège les informations de répertoire (bloc) du répertoire monté (/usr/local/abc) et maintient la relation de mappage entre le répertoire monté et le répertoire monté dans la mémoire /usr/ Accès à local/. abc interrogera le bloc de /home/ttt/abc via la table de mappage de mémoire, puis effectuera des opérations pour réaliser le mappage de répertoire.

Enfin, après avoir initialisé le « nouveau système », vous devez exécuter les commandes liées à la prison via le pseudo terminal :

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

Après avoir ouvert le programme bash, communiquez avec l'entrée du terminal Web (via websocket) reçue par l'appareil principal via PIPE.

Je pense que vous maîtrisez la méthode après avoir lu le cas dans cet article. Pour des informations plus intéressantes, veuillez prêter attention aux autres articles connexes sur le site Web chinois de php !

Lecture recommandée :

Comment implémenter un graphique echart dans angulairejs

Comment implémenter un changement de couleur entrelacé dans js

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn