Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:python_avanzado_proyectos_seguridad:conexion_servidores_ssh_utilizando_paramiko

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 local
  • put (local, remote). Permite enviar un archivo local al servidor remoto
  • chdir (path). Permite cambiar el directorio de trabajo actual
  • chmod (path, mode) . Permite cambiar permisos en un archivo
  • mkdir (path, mode = 511). Permite crear un directorio
  • rename (old, new) Permite cambiar el nombre de un archivo o directorio
  • remove (file). Permite eliminar un archivo
  • rmdir (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 &lt;built-in function openssl_sha256&gt;
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!
&lt;paramiko.client.SSHClient object at 0x7fe8be715860&gt;
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)
&lt;paramiko.sftp_client.SFTPClient object at 0x7fe8badde898&gt;
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.

informatica/programacion/cursos/python_avanzado_proyectos_seguridad/conexion_servidores_ssh_utilizando_paramiko.txt · Última modificación: por tempwin