Recherche de site Web

Exécution de commandes sur une machine distante à partir de Java avec JSch


SSH est un outil quotidien de toute tâche d’administration système Linux. Il s’agit d’un moyen simple et sécurisé d’accéder à des machines distantes sur le réseau, de transférer des données et d’exécuter des commandes à distance. Outre le mode interactif, il existe de nombreux outils qui permettent d’automatiser les tâches à distance qui s’appuient également sur l’architecture serveur/client ssh existante. Pour un tel outil, vous pouvez lire sur ansible sur Ubuntu par exemple. Vous pouvez également trouver de nombreuses implémentations du client ssh, mais qu’en est-il de l’accès aux capacités fournies par ssh à partir du code ?

JSch est un projet qui implémente le protocole ssh en Java. Avec son aide, vous pouvez créer des applications capables de se connecter et d’interagir avec un serveur SSH distant ou local. De cette façon, votre application est capable de gérer n’importe quel aspect de la machine cible que vous pourriez compléter avec votre client ssh natif, ce qui constitue un autre ajout puissant à l’ensemble déjà vaste d’outils Java.

Dans cet article, nous allons importer JSch dans notre projet Java et développer les morceaux de code minimaux nécessaires pour créer une application capable de se connecter au serveur ssh d’une machine distante, d’exécuter des commandes dans le shell interactif distant, de fermer la session, puis de présenter le résultat. Cette application sera minimale, cependant, elle peut donner un indice de la puissance qu’elle fournit.

Dans ce tutoriel, vous allez apprendre :

  • Comment importer JSch dans votre projet Java
  • Comment configurer l’environnement de test
  • Comment implémenter l’interface UserInfo dans une classe personnalisée
  • Comment écrire une application qui lance une session ssh interactive

Exemple d’exécution JSch.

Configuration logicielle requise et conventions utilisées

Introduction

Avec l’aide de JSch, nous allons développer une application qui tentera de se connecter à localhost via ssh, en utilisant le test de nom d’utilisateur et le test de mot de passe. Nous supposerons le port 22 par défaut sur lequel le serveur ssh écoute, et nous accepterons l’empreinte digitale du serveur sans vérifier sa validité. Une fois la connexion réussie, nous exécuterons quelques commandes que nous pourrions émettre dans un shell distant, nous déconnecterons, puis nous imprimerons toute la sortie reçue.

ATTENTION
Le code source suivant n’est fourni qu’à des fins de démonstration ; n’utilisez jamais ce code en production ! Pour ne citer que deux pièges, ne faites confiance aux empreintes digitales du serveur par défaut et gérez correctement les exceptions.

Nos outils comprendront un bureau Fedora (à la fois en tant que client et serveur), un IDE NetBeans récent, et le dernier JSch stable (au moment de la rédaction). Notez cependant que ce ne sont que les outils de choix. Java est indépendant de la plate-forme, et le serveur cible pourrait être de l’autre côté de la planète, et pourrait être n’importe quel système d’exploitation qui exécute un serveur ssh approprié.

Configuration de l’environnement de test

Nous aurons besoin des informations d’identification ci-dessus pour travailler sur localhost. Dans notre exemple, cela signifie que nous avons besoin d’un utilisateur nommé « test », avec le mot de passe « test ». Nous aurons également besoin d’un serveur ssh en cours d’exécution.

Ajout de l’utilisateur test

Nous allons exécuter useradd en tant que root :

useradd test

Et définissez le mot de passe du nouvel utilisateur :

passwd test

Ici, nous devons fournir le mot de passe ci-dessus deux fois. Cela convient dans un environnement de test temporaire et également inaccessible du monde extérieur, mais n’utilisez pas de mots de passe faciles à deviner lorsqu’il peut y avoir la moindre chance d’accès non contrôlé.

Vérification du serveur ssh

Nous pouvons vérifier l’état du serveur ssh avec systemd :

systemctl status sshd

Et démarrez-le s’il ne fonctionne pas :

systemctl start sshd

Cette étape peut être nécessaire sur les installations de bureau, car certaines de ces configurations n’exécutent pas le serveur ssh par défaut.

Test de la connectivité avec le client natif

Si notre utilisateur est configuré et que le service est en cours d’exécution, nous devrions être en mesure de nous connecter en utilisant les informations ci-dessus :

ssh test@localhost

Nous devrons accepter l’empreinte digitale de l’hôte et fournir le mot de passe. Si nous arrivons au shell, notre environnement de test est terminé.

Obtention et importation de JSch dans notre projet

Téléchargement de l’archive

Nous aurons besoin de télécharger le byte code du projet JSch afin d’utiliser ses fonctionnalités. Vous pouvez trouver le lien approprié sur la page d’accueil de JSch. Nous aurons besoin de l’archive Java .jar.

Création du projet dans NetBeans

Au début, nous créons un nouveau projet vide appelé sshRemoteExample dans NetBeans. Nous pouvons simplement choisir « Nouveau projet » dans le menu Fichier.

Création d’un nouveau projet.

Nous choisirons la catégorie « Java » et le projet « Java Application ».

Choix de la catégorie pour le projet.

Nous devons fournir un nom pour le projet, dans ce cas « sshRemoteExample ».

Nommer le projet.

Sur la mise en page par défaut, nous pouvons trouver la fenêtre « Projets » sur la gauche. Là, nous allons cliquer avec le bouton droit de la souris sur le nœud « Bibliothèques » sous notre projet nouvellement créé, et sélectionner « Ajouter JAR/Dossier ». Une fenêtre de sélection de fichiers s’ouvrira, dans laquelle nous devons rechercher le fichier .jar que nous avons téléchargé à partir du site du développeur.

Ajout d’un fichier JAR en tant que bibliothèque.

Après la sélection, l’archive devrait apparaître dans les bibliothèques incluses, si nous ouvrons le nœud « Bibliothèques ».

JSch a été importé avec succès.

Nous devrons implémenter l’interface UserInfo afin de l’utiliser dans notre application. Pour ce faire, nous devrons ajouter une nouvelle classe java à notre projet en faisant un clic droit sur notre package sshremoteexample dans la fenêtre du projet, choisissez « Nouveau », puis « Classe Java... ».

Ajout d’une nouvelle classe Java au package.

Nous fournirons le nom « sshRemoteExampleUserinfo » comme nom de classe.

Attribution du nom à la nouvelle classe Java.

Ajout du code source

sshRemoteExampleUserinfo.java

Pour l’implémentation de notre interface, considérez la source suivante. C’est là que nous acceptons aveuglément l’empreinte digitale de la cible. Ne faites pas cela dans un scénario réel. Vous pouvez modifier le code source en cliquant sur la classe dans la fenêtre du projet, ou si elle est déjà ouverte, basculez-y avec les onglets en haut de la fenêtre du code source.

package sshremoteexample;

import com.jcraft.jsch.*;

public class sshRemoteExampleUserInfo implements UserInfo {
    private final String pwd;
    
    public sshRemoteExampleUserInfo (String userName, String password) {
        pwd = password;
    }
    
    @Override
    public String getPassphrase() {
        throw new UnsupportedOperationException("getPassphrase Not supported yet.");
    }
    @Override
    public String getPassword() {
        return pwd;
    }
    @Override
    public boolean promptPassword(String string) {
        /*mod*/
        return true;
    }
    @Override
    public boolean promptPassphrase(String string) {
        throw new UnsupportedOperationException("promptPassphrase Not supported yet.");
    }
    @Override
    public boolean promptYesNo(String string) {
        /*mod*/
        return true;
    }
    @Override
    public void showMessage (String string) {
    }
}

SshRemoteExample.java

Notre classe principale sera la classe sshRemoteExample avec la source suivante :

package sshremoteexample;

import com.jcraft.jsch.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class SshRemoteExample {

    public static void main(String[] args) {
        String host = "localhost";
        String user = "test";
        String password = "test";
        String command = "hostname\ndf -h\nexit\n";
        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(user,host, 22);
            session.setUserInfo(new sshRemoteExampleUserInfo(user, password));
            session.connect();
            Channel channel = session.openChannel("shell");
            channel.setInputStream(new ByteArrayInputStream(command.getBytes(StandardCharsets.UTF_8)));
            channel.setOutputStream(System.out);
            InputStream in = channel.getInputStream();
            StringBuilder outBuff = new StringBuilder();
            int exitStatus = -1;
            
            channel.connect();
            
            while (true) {                
                for (int c; ((c = in.read()) >= 0);) {
                    outBuff.append((char) c);
                }
                
                if (channel.isClosed()) {
                    if (in.available() > 0) continue;
                    exitStatus = channel.getExitStatus();
                    break;
                }
            }
            channel.disconnect();
            session.disconnect();
            
        // print the buffer's contents
        System.out.print (outBuff.toString());
        // print exit status
        System.out.print ("Exit status of the execution: " + exitStatus);
        if ( exitStatus == 0 ) {
            System.out.print (" (OK)\n");
        } else {
            System.out.print (" (NOK)\n");
        }
        
        } catch (IOException | JSchException ioEx) {
            System.err.println(ioEx.toString());
        }
    }   
}

Notez que dans cet exemple, nous codons en dur tous les détails nécessaires à la connexion : nom d’hôte cible, nom d’utilisateur/mot de passe et la chaîne de commande à exécuter dans la session distante. Ce n’est pas un exemple réel, mais cela sert son objectif de démonstration.

Nous pourrions modifier la cible et les informations d’identification pour exécuter la commande sur un hôte distant. Notez également que la session à distance disposera des privilèges de l’utilisateur qui se connecte. Je ne conseillerais pas d’utiliser un utilisateur avec des privilèges élevés – comme root – pour les tests, si la machine cible contient des données ou des services précieux.

Exécution de l’application

Nous pouvons exécuter notre application directement depuis l’IDE en cliquant sur « Exécuter le projet (sshRemoteExample) » dans le menu « Exécuter », ce qui fournira la sortie dans la fenêtre de sortie sous le code source. Nous pouvons également choisir « Nettoyer et compiler le projet (sshRemoteExample) » dans le même menu, auquel cas l’IDE produira l’archive Java .jar peut être exécutée sans l’IDE.

La sortie fournie affichera le chemin d’accès à l’archive, de la même manière que ce qui suit (le chemin exact peut varier en fonction des paramètres de votre IDE) :

To run this application from the command line without Ant, try:
java -jar "/var/projects/sshRemoteExample/dist/sshRemoteExample.jar"

Comme on peut le deviner, nous pouvons exécuter notre application construite à partir de la ligne de commande, et si tout se passe bien, elle fournira un résultat similaire à ce qui suit.

java -jar "/var/projects/sshShellExample/dist/sshShellExample.jar"
Last login: Mon Jul 29 14:27:08 2019 from 127.0.0.1
hostname
df -h
exit
[test@test1 ~]$ hostname
test1.linuxconfig.org
[test@test1 ~]$ df -h
Filesystem                               Size  Used Avail Use% Mounted on
devtmpfs                                 3,9G     0  3,9G   0% /dev
tmpfs                                    3,9G  127M  3,8G   4% /dev/shm
tmpfs                                    3,9G  1,7M  3,9G   1% /run
tmpfs                                    3,9G     0  3,9G   0% /sys/fs/cgroup
/dev/mapper/fedora_localhost--live-root   49G   15G   32G  32% /
tmpfs                                    3,9G  6,1M  3,9G   1% /tmp
/dev/sdb1                                275G  121G  140G  47% /mnt/hdd_open
/dev/sda2                                976M  198M  711M  22% /boot
/dev/mapper/fedora_localhost--live-home   60G   50G  6,9G  88% /home
/dev/sda1                                200M   18M  182M   9% /boot/efi
tmpfs                                    789M  9,7M  779M   2% /run/user/1000
tmpfs                                    789M     0  789M   0% /run/user/1001
[test@test1 ~]$ exit
logout
Exit status of the execution: 0 (OK)

Notez que votre sortie sera probablement différente, à tout le moins, dans le nom d’hôte, les noms de volume et les tailles - mais en général, vous devriez voir une sortie df -h complète que vous obtiendriez dans une session ssh.

Réflexions finales

Cet exemple simple visait à montrer la puissance du projet JSch, bien que d’une manière quelque peu simplifiée. Avec l’accès à la machine de test et à un client approprié, la commande simple suivante fournirait les mêmes informations :

ssh test@localhost "hostname; df -h"

Et ne créerait pas non plus de session interactive. La même fonctionnalité est fournie par JSch si vous ouvrez le canal en mode commande :

Channel channel = session.openChannel("command");

De cette façon, vous n’avez pas besoin de fermer la session avec la commande exit shell.

La véritable puissance de ce projet réside dans la capacité de se connecter et d’interagir avec la machine distante à l’aide de commandes natives du shell, de traiter la sortie et de décider de la prochaine action de manière programmatique. Imaginez une application multithread qui gère peut-être des centaines de serveurs par elle-même, et vous aurez une image.

Articles connexes: