Asyncssh
Utilisation de asyncssh
Section titled âUtilisation de asyncsshâDans cet article je vais essayer dâillustrer simplement comment utiliser le module asyncssh de Python pour :
- Scripter des actions distantes
- Etablir un tunnel ssh permettant de faire du port forwarding
Alors si vous vous ĂȘtes dĂ©jĂ confrontĂ© Ă ces problĂ©matiques vous avez peut-ĂȘtre dĂ©jĂ croisĂ© la route du module Python paramiko. Alors personellement je ne recommande pas lâusage de paramiko (en gros jâai perdu un temps monstrueux avec paramiko, câest peut-ĂȘtre ma faute attention, notamment lorsque je devais faire des portages dâapplication entre windows 7 / windows 10 et Linux) et du coup depuis ce jour je suis passĂ© Ă asyncssh et ça fonctionne parfaitement.
La seule difficultĂ© avec asyncssh vient du async ⊠En effet câest un module destinĂ© Ă ĂȘtre utilisĂ© dans un mode de programmation asynchrone avec asyncio il faut donc ĂȘtre un peu Ă lâaise avec les async/await
dans Python đ€š ! Si ce nâest pas le cas je vous invite Ă aller jeter un oeil sur le mooc Python 3 en semaine 8 vous avez une magnifique introduction Ă la programmation asynchrone.
Connection Ă un serveur
Section titled âConnection Ă un serveurâLa premiĂšre Ă©tape est bien Ă©videmment la connection au serveur ssh. Pour cela on passe par la fonction connect
de asyncssh qui prend entre autre arguments :
- Un nom dâutilisateur
username
- Une authentification avec deux solutions possibles :
- mot de passe classique dans ce cas il suffit de spĂ©cifier lâargument
password
- ClĂ© ssh et dans ce cas il faut spĂ©cifier lâargument
client_keys
qui est une liste contenant la oĂč les clĂ©s ssh ainsi que lâargumentpassphrase
qui est donc la passphrase de la clé ssh.
- mot de passe classique dans ce cas il suffit de spĂ©cifier lâargument
client = await asyncssh.connect( server, username=username, password=password)
Une fois connecté, exécuter des commandes !
Section titled âUne fois connectĂ©, exĂ©cuter des commandes !âUne fois la connection avec notre serveur Ă©tablie nous pouvons commencer Ă faire des choses intĂ©ressantes (enfin ça dĂ©pend de vous ça). Mais pour commencer la mĂ©thode essentielle Ă connaĂźtre est la mĂ©thode run
qui comme son nom lâindique va nous permettre dâexĂ©cuter des commandes Ă distance. Dans sa version basique la mĂ©thode run
prend une chaĂźne de caractĂšre par exemple si je veux lancer la commande ls ~/Documents
et bien câest ce quâon fait !
out = await client.run("ls ~/Documents")
Alors le out
que lâon rĂ©cupĂšre est une instance de asyncssh.SSHCompletedProcess
on sâen fout un peu tout ce quâil faut savoir câest quâil y a dans ce truc 3 attributs qui sont utiles :
out.returncode
si différent de0
y a un truc qui a planté quelque part !out.stdout
la sortie standardout.stderr
la sortie dâerreur
Alors juste vous le savez peut-ĂȘtre mais suivant la commande que vous exĂ©cutez ne regardez pas que dans le stderr
si vous avez des erreurs car il existe tout un tas de logiciels qui écrivent les erreurs dans le stdout
et certain ont mĂȘme le gĂ©nie dâecrire des infos dans le stderr
âŠ
Ou bien lire et écrire des fichiers
Section titled âOu bien lire et Ă©crire des fichiersâDe la mĂȘme maniĂšre il est possible une fois la connection Ă©tablie de lire et/ou Ă©crire des fichiers via sftp si vous voulez tout savoir. Pour cela il suffit de crĂ©er un client sftp Ă partir du client ssh une fois la connexion Ă©tablie.
async with client.start_sftp_client() as sftp: ## Read, write file, create directory, ... ## all on remote file system pass
Une fois le client sftp créé nous pouvons utiliser tout une ribambelle (oui jâaime bien ce mot) de mĂ©thodes par exemple :
sftp.exists( remote_path )
sftp.mkdir( remote_path )
sftp.open( fname, mode)
sftp.chmod( remote_path, mode )
- âŠ
Pour la liste complÚte des méthodes RTFM
Et avec un petit rebond câest tout aussi simple !
Section titled âEt avec un petit rebond câest tout aussi simple !âAlors le truc gĂ©nial đ€© de la fonction asyncssh.connect
est quâelle accepte un argument optionnel tunnel
. Cet argument permet de spécifier un client ssh déjà connecté. En gros si pour accéder à la machine machine-C
depuis machine-A
on doit forcément passer par la machine machine-B
et bien avec cet argument tunnel
ce sera super simple il suffira de faire un truc du genre :
proxy = await asyncssh.connect( "machine-B", username=username, password=password)client = await asyncssh.connect( "machine-C", username=username, password=password, tunnel=proxy)
Il existe également une version avec context manager qui prends la forme suivante :
async with asyncssh.connect( "machine-B", username=username, password=password) as proxy: async with asyncssh.connect( "machine-C", username=username, password=password, tunnel=proxy) as client: # do something crazy with my ssh client
Port forwarding et tunnel ssh
Section titled âPort forwarding et tunnel sshâPour finir je vais vous montrer lâautre truc super sympa de asyncssh
Ă savoir que lâon peut depuis un boĂ»t de Python construire un tunnel ssh qui va nous permettre de faire du port forwarding. Deux trucs sympa :
- Le mĂȘme code va fonctionner sous Linux, Mac et Windows donc pas de prise de tĂȘte
- On peut comme ça automatiser et surtout cacher plein dâopĂ©rations aux end-users. Car je sais pas vous mais moi si je demande aux gens de faire un tunnel ssh Ă la main avec OpenSSH ma boite mail va exploser avec les âça marche pasâ !!
async with asyncssh.connect(hostname, username=username) as client: listener = await client.forward_local_port("", local_port, remote_ip, remote_port) await listener.wait_closed()
Le Python précédent est équivalent à la commande OpenSSH suivante pour les connaisseurs :
ssh -L local_port:remote_ip:remote_port username@hostname
Bon alors par contre le code Python prĂ©cĂ©dent est bloquant, câest-Ă -dire quâune fois le tunnel créé on ne peut rien faire dâautre ce qui peut sâavĂ©rer gĂ©nant. Une solution assez simple pour ne pas se retrouver bloquĂ© est simplement de dĂ©lĂ©guer la crĂ©ation du tunnel Ă un process sĂ©parĂ© de notre process principal. Pour cela un petit coup de multiprocessing
et ça roule !
def standalone_tunnel(hostname, username, password, local_post, remote_port, remote_ip ): async def do_job(hostname, username, password, local_post, remote_port, remote_ip ): async with asyncssh.connect(hostname, username=username, password=password) as client: listener = await client.forward_local_port("", local_port, remote_ip, remote_port) await listener.wait_closed() try: asyncio.get_event_loop().run_until_complete(do_job(hostname, username, password, local_post, remote_port, remote_ip)) except Exception as e: print("Port Forwarding Error : " + str(e))
import multiprocessingp = multiprocessing.Process(target=standalone_tunnel, args=(hostname, username, password, local_post, remote_port, remote_ip))p.start()
Conclusion
Section titled âConclusionâJe viens de vous montrer en mode express comment le module asyncssh
de Python peut trĂšs facilement nous permettre de scripter des actions nĂ©cessitant des connections Ă des serveurs distants via ssh. Il y a Ă©videmment plein dâapplications possibles Ă cela mais je vais juste vous en prĂ©senter deux que jâai eu Ă rĂ©aliser.
La premiĂšre application a Ă©tĂ© la mise en place dâun outil pour faire de la visualisation distante. En effet Ă partir de Mars 2020 et une certaine pandĂ©mie mondiale il a fallu dans le labo oĂč je suis que les utilisateurs puissent travailler Ă distance, notamment avec des softs disposant dâinterface graphique un peu lourde. Pour cela jâai dĂ©veloppĂ© une solution assez simple oĂč chaque utilisateur lance sur un serveur du labo un serveur VNC et ensuite via un tunnel ssh (qui forward le port VNC de lâutilisateur vers un port local de son portable en faisant un rebond via la passerelle ssh) peut accĂ©der Ă sa session graphique via le client VNC installĂ© localement. Et bien Ă©videmment si je demande aux utilisateurs de faire tout ça Ă la main đ€Ż donc jâai packagĂ© tout ça dans une petite application Python Ă base de asyncssh et PyQt pour le graphique et en deux clics les utilisateurs peuvent se connecter !
La seconde application que jâai pu avoir de asyncssh
est le dĂ©veloppement dâun utilitaire Python pour lancer un jupyter notebook sur un noeuds de calcul du cluster et faire le port forwarding qui va bien pour que lâutilisateur puisse ensuite ouvrir son notebook et travailler dans son navigateur en local. Pour cela le process est assez simple :
- Connexion Ă la frontal du cluster (besoin de deux rebonds dans mon cas mais facile avec asyncssh đ)
- Ecriture sur le filesystem du cluster dâun script de soumission pour le gestionnaire de job (slurm)
- Soumission du job slurm
- Une fois le job slurm commencé, ouverture des fichiers de log du job pour récupérer le nom sur lequel le job est parti et le port sur lequel jupyter a démarré
- Connexion ssh au noeud du cluster oĂč mon job tourne
- Création du tunnel ssh pour le port forwarding
- Câest bon il nây a plus quâĂ ouvrir son navigateur đ