Tabla de Contenidos
Conexión con servidores SSH utilizando paramiko
Módulo perteneciente al curso Python avanzado para proyectos de seguridad
Paramiko es una librería escrita en Python que soporta los protocolos SSHV1, SSHV2, permitiendo la creación de clientes y realizar conexiones a servidores SSH. Depende de la librería pycrypto para todas las operaciones de cifrado y permite la creación de túneles cifrados locales, remotos y dinámicos.
Entre las principales ventajas de esta librería podemos destacar:
- Permite encapsular las dificultades que implica realizar scripts automatizados contra servidores SSH de una forma cómoda y fácil de entender para cualquier programador.
- Soporta protocolo SSH2 por medio de la librería PyCrypto, que la emplea para implementar todos aquellos detalles de criptografía de clave pública y privada.
- Permite autenticación por clave pública, autenticación mediante password, creación de túneles SSH.
- Nos permite escribir clientes SSH robustos con las mismas funcionalidades que tienen otros clientes SSH como Putty u OpenSSH-Client.
- Soporta transferencia de ficheros de forma segura utilizando el protocolo SFTP.
En Python se importa el módulo paramiko y la clase más importante que es SSHClient. Hay varias formas de conectarnos a un servidor SSH con paramiko.
La primera es a través de la clase SSHClient() que proporciona un objeto sobre el cual disponemos para conectarnos a un determinado host introduciendo las credenciales de usuario y contraseña.
El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local.
Puede encontrar el siguiente código en el fichero paramiko-conectar.py:
import paramiko host = 'localhost' username = 'username' password = 'password' sshCliente = paramiko.SSHClient() #La siguiente línea añade la clave del servidor automáticamente al archivo know_hosts sshCliente.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: print("creando conexión") sshCliente.connect(host, username=username, password=password) print("conectado") except Exception as exception: print("Error al conectar: ",exception) finally: print("cerrando conexión") sshCliente.close()
Si al ejecutar el script anterior no consigue establecer la conexión con el servidor SSH, entonces lanzará la excepción correspondiente Error al conectar: Authentication failed.
Ejecutando comandos con paramiko
La clase SSHClient del módulo paramiko nos ofrece la posibilidad, además de conectarnos con un servidor SSH, de poder ejecutar comandos sobre dicho servidor. Para ello podemos utilizar el método exec_command('comando_ejecutar') al cual le pasamos el comando a ejecutar.
El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local y si la conexión ha sido ok lanza el comando uname -a que permite obtener la versión del kernel y del sistema operativo.
Puede encontrar el siguiente código en el fichero paramiko-conectar-comando.py:
import paramiko host = 'localhost' username = 'linux' password = 'linux' sshCliente = paramiko.SSHClient() #La siguiente línea añade la clave del servidor automáticamente al archivo know_hosts sshCliente.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: print("creando conexión") sshCliente.connect(host, username=username, password=password) print("conectado") stdin,stdout,stderr = sshCliente.exec_command("uname -a") for line in stdout.readlines(): print(line.strip()) except Exception as exception: print("Error al conectar: ",exception) finally: print("cerrando conexión") sshCliente.close()
Si la ejecución ha ido bien, mostraría la versión del kernel y del sistema operativo:
Linux linux-HP-EliteBook-8470p 5.3.0-42-generic #34~18.04.1-Ubuntu SMP Fri Feb 28 13:42:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Actividad práctica: Completa el script que permite ejecutar un comando en un servidor SSH
En este ejemplo declaramos una función que acepta por parámetros la ip, el usuario y la contraseña para conectarnos. Además, en el caso de que la conexión se realice con éxito, ejecutará el comando que le pasemos como cuarto parámetro.
Para ejecutar el comando hacemos uso del método exec_command() del objeto ssh_sesión que hemos obtenido a partir de la sesión abierta al logearnos en el servidor.
Sustituir las xxx por variables o clases definidas en el módulo de paramiko.
import paramiko def ssh_comando(ip, user, passwd, comando): client = paramiko.xxx() #Se puede registrar en un fichero de log todas las conexiones que se produzcan. paramiko.util.xxx('paramiko.log') #Cargar las claves almacenadas del sistema client.xxx() client.xxx(paramiko.xxx()) client.connect(ip, username=xxx, password=xxx) ssh_session = client.xxx().xxx() if xxx.active: ssh_session.xxx(comando) print(ssh_session.xxx(1024)) client.close() ssh_comando('localhost', 'usuario', 'password','ls -la')
Ejecución del script con el comando ls -la:
$ python3 ssh_comando_solucion.py b'total 664\ndrwxr-xr-x 32 linux linux 4096 ago 9 18:02 .\ndrwxr-xr-x 3 root root 4096 mar 27 00:17 ..\n-rw------- 1 linux linux 51096 ago 12 17:51 .bash_history\n-rw-r--r-- 1 linux linux 220 mar 27 00:16 .bash_logout\n-rw-r--r-- 1 linux linux 3771 mar 27 00:16 .bashrc\ndrwxr-xr-x 31 linux linux 4096 jul 31 17:32 .cache\ndrwxrwxr-x 4 linux linux 4096 jul 17 21:21 .cinnamon\ndrwxr-xr-x 30 linux linux 4096 jul 29 20:25 .config\ndrwx------ 3 root root 4096 jun 30 23:40 .dbus\ndrwxr-xr-x 5 linux linux 4096 ago 13 21:32 Descargas\ndrwxrwxr-x 2 linux linux 4096 jun 29 15:11 displayed\n-rw-r--r-- 1 linux linux 27 mar 27 00:17 .dmrc\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 Documentos\ndrwxr-xr-x 4 linux linux 4096 jul 5 19:16 .dvdcss\ndrwxr-xr-x 11 linux linux 16384 ago 14 18:29 Escritorio\ndrwx------ 3 linux linux 4096 jun 13 18:22 .gconf\n-rw-r--r-- 1 linux linux 58 jun 26 13:13 .gitconfig\ndrwx------ 3 linux linux 4096 mar 27 00:17 .gnupg\n-rw-r--r-- 1 linux linux 22 m'
Solución
import paramiko def ssh_comando(ip, user, passwd, comando): client = paramiko.SSHClient() #Se puede registrar en un fichero de log todas las conexiones que se produzcan. paramiko.util.log_to_file('paramiko.log') #Cargar las claves almacenadas del sistema client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(ip, username=user, password=passwd) ssh_session = client.get_transport().open_session() if ssh_session.active: ssh_session.exec_command(comando) print(ssh_session.recv(1024)) client.close() ssh_comando('localhost', 'usuario', 'password','ls -la')
Conexión con la clase Transport
Otra forma de conectarnos a un servidor SSH es mediante la clase Transport que proporciona otro tipo de objeto para poder autenticarnos contra el servidor.
Podemos consultar la ayuda de la clase Transport para ver los métodos que podemos invocar para conectarnos y obtener más información sobre el servidor SSH.
>>> import paramiko >>> help(paramiko.transport)
Esta clase proporciona el método auth_password() para autenticarnos con el servidor SSH a partir del usuario y password:
El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local.
Puede encontrar el siguiente código en el fichero paramiko-conectar-transport.py:
import paramiko host = 'localhost' username = 'linux' password = 'linux' sshTransport = paramiko.Transport(host) try: print("creando conexión con la clase Transport") sshTransport.start_client() sshTransport.auth_password(username=username, password=password) if sshTransport.is_authenticated(): print("conectado y autenticado el servidor sssh en el host",host) except Exception as exception: print("Error al conectar: ",exception) finally: print("cerrando conexión") sshTransport.close()
Si al ejecutar el script anterior no consigue establecer la conexión con el servidor SSH, entonces lanzará la excepción correspondiente Error al conectar: Authentication failed.
Ejecutar comandos con la clase Transport
La clase Transport nos ofrece la posibilidad, además de conectarnos con un servidor SSH, de poder ejecutar comandos sobre dicho servidor.
Para ello podemos utilizar el método exec_command('comando_ejecutar'), al cual le pasamos el comando a ejecutar, pero la principal diferencia con respecto al caso anterior tenemos que utilizar el método open_session() para abrir una nueva sesión en el servidor para posteriormente poder ejecutar comandos:
El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local y si la conexión ha sido ok lanza el comando uname -a que permite obtener la versión del kernel y del sistema operativo.
Puede encontrar el siguiente código en el fichero paramiko-transport-ejecutar-comando.py:
import paramiko host = 'localhost' username = 'linux' password = 'linux' comando ="uname -a" sshTransport = paramiko.Transport(host) try: print("creando conexión con la clase Transport") sshTransport.start_client() sshTransport.auth_password(username=username, password=password) if sshTransport.is_authenticated(): print("conectado y autenticado el servidor sssh en el host",host) print("conectado y autenticado el servidor sssh en el host",sshTransport.getpeername()) channel = sshTransport.open_session() channel.exec_command(comando) respuesta = channel.recv(1024) print('Comando %r/(%r)--> %s' %(comando,username,respuesta)) except Exception as exception: print("Error al conectar: ",exception) finally: print("cerrando conexión") sshTransport.close()
Si la ejecución ha ido bien, mostraría la versión del kernel y del sistema operativo:
$ python3 paramiko-transport-ejecutar-comando.py
creando conexión con la clase Transport
conectado y autenticado el servidor sssh en el host localhost
conectado y autenticado el servidor sssh en el host ('127.0.0.1', 22)
Comando 'uname -a'/('linux')--> b'Linux linux-HP-EliteBook-8470p 5.3.0-42-generic #34~18.04.1-Ubuntu SMP Fri Feb 28 13:42:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux\n'
cerrando conexión
$ python3 paramiko_transport.py
Introduce el hostname: localhost
Introduce el puerto: 22
Introduce usuario: linux
Introduce password:
Introduce comando: ls -l
('127.0.0.1', 22)
Comando 'ls -l'('linux')-->b'total 60\ndrwxr-xr-x 5 linux linux 4096 ago 13 21:32 Descargas\ndrwxrwxr-x 2 linux linux 4096 jun 29 15:11 displayed\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 Documentos\ndrwxr-xr-x 12 linux linux 16384 ago 14 15:23 Escritorio\ndrwx------ 2 linux linux 4096 jul 29 21:08 Idyll\ndrwxr-xr-x 2 linux linux 4096 ago 4 12:43 Im\xc3\xa1genes\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 M\xc3\xbasica\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 Plantillas\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 P\xc3\xbablico\ndrwxrwxr-x 3 linux linux 4096 mar 27 00:19 PycharmProjects\ndrwxr-xr-x 3 linux linux 4096 mar 27 00:18 snap\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 V\xc3\xaddeos\n'
Actividad práctica: Completa el script que permite ejecutar un comando en un servidor SSH utilizando la clase Transport.
En este ejemplo declaramos una función que acepta por parámetros el host, el usuario, la contraseña y un comando a ejecutar.
Para ejecutar el comando hacemos uso del método exec_command() del objeto channel que hemos obtenido a partir de la sesión abierta al logearnos en el servidor. Para ello utilizamos el método open_session() de la clase Transport.
#!/usr/bin/env python3 import paramiko import getpass def run_ssh_command(hostname, user, passwd, command): transport = paramiko.xxx(hostname) try: transport.xxx() except Exception as exception: print(xxx) try: transport.xxx(username=user,password=passwd) except Exception as e: print(e) if transport.xxx(): print(transport.xxx()) channel = transport.xxx() channel.xxx(xxx) response = channel.xxx(1024) print('Comando %r(%r)-->%s' % (xxx,xxx,xxx)) if __name__ == '__main__': hostname = input("Introduce el hostname: ") port = input("Introduce el puerto: ") username = input("Introduce usuario: ") password = getpass.xxx(prompt="Introduce password: ") comando = input("Introduce comando: ") run_ssh_command(xxx,xxx, xxx, xxx)
Sustituir las xxx por variables o clases definidas en el módulo de paramiko.
Ejecución del script y salida:
$ python3 paramiko_transport.py
Introduce el hostname: localhost
Introduce el puerto: 22
Introduce usuario: linux
Introduce password:
Introduce comando: ls -l
('127.0.0.1', 22)
Comando 'ls -l'('linux')-->b'total 60\ndrwxr-xr-x 5 linux linux 4096 ago 13 21:32 Descargas\ndrwxrwxr-x 2 linux linux 4096 jun 29 15:11 displayed\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 Documentos\ndrwxr-xr-x 12 linux linux 16384 ago 14 15:23 Escritorio\ndrwx------ 2 linux linux 4096 jul 29 21:08 Idyll\ndrwxr-xr-x 2 linux linux 4096 ago 4 12:43 Im\xc3\xa1genes\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 M\xc3\xbasica\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 Plantillas\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 P\xc3\xbablico\ndrwxrwxr-x 3 linux linux 4096 mar 27 00:19 PycharmProjects\ndrwxr-xr-x 3 linux linux 4096 mar 27 00:18 snap\ndrwxr-xr-x 2 linux linux 4096 mar 27 00:17 V\xc3\xaddeos\n'
Solución
#!/usr/bin/env python3 import paramiko import getpass def run_ssh_command(hostname, user, passwd, command): transport = paramiko.Transport(hostname) try: transport.start_client() except Exception as exception: print(exception) try: transport.auth_password(username=user,password=passwd) except Exception as e: print(e) if transport.is_authenticated(): print(transport.getpeername()) channel = transport.open_session() channel.exec_command(command) response = channel.recv(1024) print('Comando %r(%r)-->%s' % (command,user,response)) if __name__ == '__main__': hostname = input("Introduce el hostname: ") port = input("Introduce el puerto: ") username = input("Introduce usuario: ") password = getpass.getpass(prompt="Introduce password: ") comando = input("Introduce comando: ") run_ssh_command(hostname,username, password, comando)
Tratamiento de excepciones
Al intentar conectarnos con el servidor SSH se podrían producir errores relacionados con la conexión o con la autenticación del usuario.
Para controlar estos errores podríamos añadir una gestión de excepciones que podemos personalizar para estos casos. El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local en el puerto 22.
Si la conexión ha sido ok obtenemos los algoritmos de cifrado soportados por el servidor a través del método transport.get_security_options() y accediendo a la propiedad ciphers.
Puede encontrar el siguiente código en el fichero paramiko-excepciones.py:
import paramiko import socket host = 'localhost' usuario = 'usuario' password = 'password' try: ssh_client = paramiko.SSHClient() #mostrar info debug paramiko.common.logging.basicConfig(level=paramiko.common.DEBUG) #añadir la clave del servidor al fichero know_hosts ssh_client.load_system_host_keys() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) response = ssh_client.connect(host, port = 22, username = usuario, password = password) print('conectado con el host en el puerto 22') transport = ssh_client.get_transport() security_options = transport.get_security_options() print(security_options.kex) print(security_options.ciphers) except paramiko.BadAuthenticationType as exception: print("BadAuthenticationException:",exception) except paramiko.SSHException as sshException: print("SSHException:",sshException) except socket.error as socketError: print("socketError:",socketError) finally: print("cerrando connection") ssh_client.close()
La siguiente salida podría ser la ejecución del anterior script:
$ python3 paramiko_excepciones.py DEBUG:paramiko.transport:starting thread (client mode): 0x7c97940 DEBUG:paramiko.transport:Local version/idstring: SSH-2.0-paramiko_2.7.1 DEBUG:paramiko.transport:Remote version/idstring: SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3 INFO:paramiko.transport:Connected (version 2.0, client OpenSSH_7.6p1) DEBUG:paramiko.transport:kex algos:['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group14-sha256', 'diffie-hellman-group14-sha1'] server key:['ssh-rsa', 'rsa-sha2-512', 'rsa-sha2-256', 'ecdsa-sha2-nistp256', 'ssh-ed25519'] client encrypt:['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'] server encrypt:['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'] client mac:['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'] server mac:['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'] client compress:['none', 'zlib@openssh.com'] server compress:['none', 'zlib@openssh.com'] client lang:[''] server lang:[''] kex follows?False DEBUG:paramiko.transport:Kex agreed: curve25519-sha256@libssh.org DEBUG:paramiko.transport:HostKey agreed: ecdsa-sha2-nistp256 DEBUG:paramiko.transport:Cipher agreed: aes128-ctr DEBUG:paramiko.transport:MAC agreed: hmac-sha2-256 DEBUG:paramiko.transport:Compression agreed: none DEBUG:paramiko.transport:kex engine KexCurve25519 specified hash_algo <built-in function openssl_sha256> DEBUG:paramiko.transport:Switch to new keys ... DEBUG:paramiko.transport:userauth is OK INFO:paramiko.transport:Authentication (password) failed. SSHException: Authentication failed. cerrando connection </div></div></div>
En la ejecución anterior vemos como ha saltado la excepción correspondiente a autenticación errónea correspondiente a la excepción paramiko.SSHException. En la salida también vemos información de debug de paramiko al añadir la línea: paramiko.common.logging.basicConfig(level=paramiko.common.DEBUG)
Operaciones sobre archivos mediante el cliente SFTP
El cliente SFTP de paramiko proporciona los mismos métodos que un cliente FTP normal. Todos los métodos se pueden consultar desde la documentación oficial: <a href=“https://docs.paramiko.org/en/3.3/api/sftp.html
Entre los métodos proporcionados por el cliente SFTP, podemos encontrar:
get (remote, local). Permite obtener un fichero remoto a un directorio localput (local, remote). Permite enviar un archivo local al servidor remotochdir (path). Permite cambiar el directorio de trabajo actualchmod (path, mode). Permite cambiar permisos en un archivomkdir (path, mode = 511). Permite crear un directoriorename (old, new)Permite cambiar el nombre de un archivo o directorioremove (file). Permite eliminar un archivormdir (path): . Permite eliminar un directorio
El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local y si la conexión es ok, utiliza el cliente sftp de paramiko para obtener un listado de directorios del directorio sobre el cual se ejecuta el script.
En este caso estamos utilizando el método open_sftp() para obtener el cliente SFTP y posteriormente utilizamos el método listdir() para obtener la lista de directorios.
Puede encontrar el siguiente código en el fichero paramiko_sftp_listdir.py:
#!/usr/bin/env python3 import getpass import paramiko HOSTNAME = 'localhost' PORT = 22 def sftp_list_files(username, password, hostname=HOSTNAME,port=PORT): ssh_client = paramiko.SSHClient() paramiko.common.logging.basicConfig(level=paramiko.common.DEBUG) ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh_client.connect(hostname, port, username, password) print(ssh_client) # Crea un objeto SFTPClient() sftp = ssh_client.open_sftp() print(sftp) # Obtener archivos dirlist = sftp.listdir('.') print(dirlist) ssh_client.close() if __name__ == '__main__': hostname = input("Enter the target hostname: ") port = input("Enter the target port: ") username = input("Enter your username: ") password = getpass.getpass(prompt="Enter your password: ") sftp_list_files(username, password, hostname, port)
En el script anterior podemos ver como con paramiko es posible crear conexiones SFTP a través de una instancia SFTPClient del propio cliente SSH (SSHClient) utilizando el método open_sftp().
La siguiente salida podría ser la ejecución del anterior script donde se solicita al usuario el host,puerto, usuario y contraseña:
$ python3 paramiko_sftp.py Enter the target hostname: localhost Enter the target port: 22 Enter your username: linux Enter your password: DEBUG:paramiko.transport:starting thread (client mode): 0xbb838908 DEBUG:paramiko.transport:Local version/idstring: SSH-2.0-paramiko_2.7.1 DEBUG:paramiko.transport:Remote version/idstring: SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3 INFO:paramiko.transport:Connected (version 2.0, client OpenSSH_7.6p1) DEBUG:paramiko.transport:kex algos:['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group14-sha256', 'diffie-hellman-group14-sha1'] server key:['ssh-rsa', 'rsa-sha2-512', 'rsa-sha2-256', 'ecdsa-sha2-nistp256', 'ssh-ed25519'] client encrypt:['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'] server encrypt:['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'] client mac:['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'] server mac:['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'] client compress:['none', 'zlib@openssh.com'] server compress:['none', 'zlib@openssh.com'] client lang:[''] server lang:[''] kex follows?False DEBUG:paramiko.transport:Kex agreed: curve25519-sha256@libssh.org DEBUG:paramiko.transport:HostKey agreed: ssh-ed25519 DEBUG:paramiko.transport:Cipher agreed: aes128-ctr DEBUG:paramiko.transport:MAC agreed: hmac-sha2-256 DEBUG:paramiko.transport:Compression agreed: none DEBUG:paramiko.transport:kex engine KexCurve25519 specified hash_algo <built-in function openssl_sha256> DEBUG:paramiko.transport:Switch to new keys ... DEBUG:paramiko.transport:Adding ssh-ed25519 host key for [localhost]:22: b'0e5cf2777a3deee280f04e14a7d85f23' DEBUG:paramiko.transport:userauth is OK INFO:paramiko.transport:Authentication (password) successful! <paramiko.client.SSHClient object at 0x7fe8be715860> DEBUG:paramiko.transport:[chan 0] Max packet in: 32768 bytes DEBUG:paramiko.transport:Received global request "hostkeys-00@openssh.com" DEBUG:paramiko.transport:Rejecting "hostkeys-00@openssh.com" global request from server. DEBUG:paramiko.transport:[chan 0] Max packet out: 32768 bytes DEBUG:paramiko.transport:Secsh channel 0 opened. DEBUG:paramiko.transport:[chan 0] Sesch channel 0 request ok INFO:paramiko.transport.sftp:[chan 0] Opened sftp connection (server version 3) <paramiko.sftp_client.SFTPClient object at 0x7fe8badde898> DEBUG:paramiko.transport.sftp:[chan 0] listdir(b'.') ['.pki', '.npm', '.node_repl_history', '.dvdcss', 'Público', '.bashrc', 'Documentos', '.xsession-errors', '.PyCharmCE2019.3', 'snap', '.dmrc', '.gnupg', '.ssh', '.python_history-03876.tmp', '.zoom', '.Xauthority', '.bash_history', 'displayed', '.profile', '.sudo_as_admin_successful', 'Imágenes', '.gvfs', '.java', 'Plantillas', 'Música', '.cache', '.cinnamon', 'Escritorio', 'Descargas', '.gtkrc-xfce', '.local', '.python_history', 'Vídeos', '.wget-hsts', 'Idyll', 'PycharmProjects', '.ICEauthority', '.bash_logout', '.idyll', '.config', '.dbus', '.mozilla', '.xsession-errors.old', '.gtkrc-2.0', '.thunderbird', '.gconf', '.gitconfig']
Descarga de ficheros con el cliente SFTP
Podríamos utilizar el módulo paramiko para obtener una sesión FTP y descargar un fichero de un servidor de forma segura.
El siguiente script trata de establecer una conexión con el servidor SSH que se encuentra en la máquina local y utiliza cliente sftp de paramiko para descargar un fichero en el directorio sobre el cuál se ejecuta el script. En este caso estamos utilizando el método get(remote, local) que permite obtener un fichero remoto a un directorio local.
Puede encontrar el siguiente código en el fichero ssh_descarga_fichero_sftp.py:
#!/usr/bin/env python3 import getpass import paramiko HOSTNAME = 'localhost' PORT = 22 FILE_PATH = '/tmp/test_sftp.txt' def sftp_download(username, password, hostname=HOSTNAME,port=PORT): ssh_transport = paramiko.Transport(hostname, int(port)) ssh_transport.connect(username=username, password=password) sftp_session = paramiko.SFTPClient.from_transport(ssh_transport) file_path = input("Enter filepath: ") or FILE_PATH target_file = file_path.split('/')[-1] sftp_session.get(file_path, target_file) print("Downloaded file from: %s" %file_path) sftp_session.close() if __name__ == '__main__': hostname = input("Enter the target hostname: ") port = input("Enter the target port: ") username = input("Enter your username: ") password = getpass.getpass(prompt="Enter your password: ") sftp_download(username, password, hostname, port)
El objetivo del script es que el usuario introduzca los datos del servidor del cual quiere descargar el fichero, incluyendo la ruta del fichero.
Acceder a un servidor vía SFTP utilizando el módulo PySftp
PySftp es un módulo que actúa a modo de wrapper de la librería Paramiko. Los métodos que ofrece PySftp son abstracciones que le permiten a un desarrollador trabajar más rápido encapsulando muchas de las funciones que se necesitan para interactuar con un servidor via SFTP.
Se trata de un módulo que expone ciertas características de alto nivel basadas en Paramiko, entre las que podemos destacar la transferencia de archivos de manera recursiva. Para instalar PySftp en tu entorno con Pip usando el siguiente comando:
python -m pip install pysftp
Por ejemplo, podríamos listar el contenido de un directorio usando el siguiente código. Después de abrir una conexión, necesitas cambiar de directorio usando ya sea el método cwd() o chdir() y pasando como primer argumento la dirección del directorio remoto.
Puede encontrar el siguiente código en el fichero listdir_pysftp.py:
#!/usr/bin/env python3 import pysftp import getpass HOSTNAME = 'localhost' PORT = 22 def sftp_getfiles(username, password, hostname=HOSTNAME,port=PORT): with pysftp.Connection(host=hostname, username=username, password=password) as sftp: print("Conexión establecida con el servidor ... ") # Cambiar a al directorio raiz del servidor remoto sftp.cwd('/') # Obtener estructura del directorio raiz list_directory = sftp.listdir_attr() for directory in list_directory: print(directory.filename, directory.longname,directory) # conexión se cierra automaticamente al final del bloque with if __name__ == '__main__': hostname = input("Introduce el nombre del host: ") port = input("Introduce el puerto: ") username = input("Introduce usuario: ") password = getpass.getpass(prompt="Introduce password: ") sftp_getfiles(username, password, hostname, port)
El script anterior devuelve un objeto del tipo SFTPAttributes por cada archivo/directorio que encuentra en el directorio remoto.
Ejemplo de ejecución del script:
$ python3 listdir_pysftp.py Introduce el nombre del host: localhost Introduce el puerto: 22 Introduce usuario: linux Introduce password: Conexión establecida con el servidor ... drwxr-xr-x 2 root root 12288 Mar 27 00:16 bin drwxr-xr-x 1 0 0 12288 27 Mar 00:16 bin drwxr-xr-x 3 root root 4096 Mar 27 00:17 boot drwxr-xr-x 1 0 0 4096 27 Mar 00:17 boot drwxrwxr-x 2 root root 4096 Mar 26 22:58 cdrom drwxrwxr-x 1 0 0 4096 26 Mar 22:58 cdrom drwxr-xr-x 21 root root 4620 Aug 11 20:23 dev drwxr-xr-x 1 0 0 4620 11 Aug 20:23 dev drwxr-xr-x 151 root root 12288 Aug 8 19:08 etc drwxr-xr-x 1 0 0 12288 08 Aug 19:08 etc drwxr-xr-x 3 root root 4096 Mar 27 00:17 home drwxr-xr-x 1 0 0 4096 27 Mar 00:17 home
En la salida vemos como el objeto retornado contiene un campo llamado longname, que contiene una cadena con los atributos de los archivos en formato unix. El contenido de esta cadena dependerá del servidor SFTP y de los permisos que tenga cada archivo/directorio.
Descarga de un fichero utilizando el módulo PySftp
Con el objetivo de descargar un archivo remoto, podríamos usar el método get() que espera como primer argumento el directorio del archivo que va a ser descargado y como segundo argumento el directorio local donde se guardará el archivo descargado.
Puede encontrar el siguiente código en el fichero descarga_fichero_pysftp.py:
#!/usr/bin/env python3 import pysftp import getpass HOSTNAME = 'localhost' PORT = 22 def sftp_getfiles(username, password, hostname=HOSTNAME,port=PORT): with pysftp.Connection(host=hostname, username=username, password=password) as sftp: print("Conexión establecida con el servidor ... ") # Define el archivo que quieres descargar del servidor remoteFilePath = '/tmp/test_sftp.txt' # Define el directorio local donde se debería guardar el archivo localFilePath = 'test_sftp.txt' sftp.get(remoteFilePath, localFilePath) # conexión se cierra automaticamente al final del bloque with if __name__ == '__main__': hostname = input("Introduce el nombre del host: ") port = input("Introduce el puerto: ") username = input("Introduce usuario: ") password = getpass.getpass(prompt="Introduce password: ") sftp_getfiles(username, password, hostname, port)
En el script anterior lo que hacemos es acceder al directorio que encuentra en la ruta /tmp/test_sftp.txt del servidor y lo guardamos en el directorio local en el fichero test_sftp.txt.



