疫情还没结束,小编只能宅在家里,哪哪也去不了,今天突发奇想给大家分享一篇教程关于Python paramiko 模块浅谈与SSH主要功能模拟解析。
大家都知道,通过SSH服务可以远程连接到Linux服务器,查看上面的日志状态,批量配置远程服务器,文件上传,文件下载等,Python的paramiko模块同样实现了这一功能。
首先我们需要安装这一模块,pycharm环境中如下操作
一,安装paramiko模块
PyCharm→Preferences→Project:项目名→Project Interpreter
点击箭头所指加号,在搜索框输入选中,并安装
完成后会在Project Interpreter中显示,如上上图
二, 基于用户名和密码的 sshclient 方式登录
import paramiko # 创建SSH对象 ssh = paramiko.SSHClient() # 允许连接不在know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接服务器 ssh.connect(hostname='192.168.199.146', port=22, username='fishman', password='9') # 执行命令 stdin, stdout, stderr = ssh.exec_command('df') # 获取命令结果 res,err = stdout.read(),stderr.read() result = res if res else err print(result.decode()) # 关闭连接 ssh.close()
解析:首先导入模块,然后创建一个SSH对象,然后通过命令连接远程机器,默认端口是22,这里我连接的是192.168.199.146的机器,密码为9,然后执行df命令,获取返回的结果如下
我们可以比较一下通过SSH得到的结果
三,基于用户名和密码的 transport 方式登录
基于SSHClient是传统的连接服务器、执行命令、关闭的一个操作,有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,上面方法则无法实现,可以通过如下方式来操作
#SSHClient 封装 Transport import paramiko # 实例化一个transport对象 transport = paramiko.Transport(('192.168.199.146', 22)) # 建立连接 transport.connect(username='fishman', password='9') # 将sshclient的对象的transport指定为以上的transport ssh = paramiko.SSHClient() ssh._transport = transport # 执行命令,和传统方法一样 stdin, stdout, stderr = ssh.exec_command('df') print (stdout.read().decode()) # 关闭连接 transport.close()
四,基于公钥密钥连接
4.1 SSH配置免密登陆
我们知道通过配置公钥密钥,可以实现SSH的免密登陆,比如现在连接fishman机器会提示我们输入密码,然后才能登陆
通过SSH原理与运用中介绍,我们知道,要实现免密公钥登陆,需要将我们自己机器的公钥存储在要登陆的远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。
首先我们生成自己的公钥通过命令 ssh-keygen,这里会提示这个公钥保存的文件路径,默认即可然后回车,这里由于我本地已经有生成的公钥,所以提示是否要覆盖,我这里就不重写了,如果本地没有生成过继续回车,提示要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。默认也可以直接回车
得到公钥后一种方法是通过命令将公钥直接传到远程机器ssh-copy-id user@host,如我们登陆的机器:
ssh-copy-id fishman@192.168.199.146
另一种方法是进入公钥文件去复制,进入.ssh/id_rsa.pub通过more或者cat复制公钥
~ more .ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyQ2GtkbKcFAP/kX/+65e91Q8EJ+twpyqjEBzlVmZ2mxy8Zw7b/AfeV60ehloCq7p521IGJaXWbr3bky7Jljc6x7cAZsRW7mZRPFelPYa5iJ4lIshU3RwhbTYW6RyrG+InF3ognJ7bggpPmJbWFxqqpZwdkEilpnv0+8b17frSbF1xaQQh57vDjG78xr0pys2MBzylqrs1RNiTcRz86zJT7SbujYY/bpiUB78w46CmRlwzHOk8Zg0hcegqSMHjboQyhkSX0xcFv2ZLHm5Gpfba3863M+62Q1r2U+QW5Ki1f3nsGwHWQIRuHNWldUzWKYRPsKJHVvwFwkYDG3sZZMWR ljf@meijiabaodeiMac.lan ~ cat .ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyQ2GtkbKcFAP/kX/+65e91Q8EJ+twpyqjEBzlVmZ2mxy8Zw7b/AfeV60ehloCq7p521IGJaXWbr3bky7Jljc6x7cAZsRW7mZRPFelPYa5iJ4lIshU3RwhbTYW6RyrG+InF3ognJ7bggpPmJbWFxqqpZwdkEilpnv0+8b17frSbF1xaQQh57vDjG78xr0pys2MBzylqrs1RNiTcRz86zJT7SbujYY/bpiUB78w46CmRlwzHOk8Zg0hcegqSMHjboQyhkSX0xcFv2ZLHm5Gpfba3863M+62Q1r2U+QW5Ki1f3nsGwHWQIRuHNWldUzWKYRPsKJHVvwFwkYDG3sZZMWR ljf@meijiabaodeiMac.lan ~
复制后,我们登陆远程机器,在目录下.ssh文件夹中发现有以下文件,其中id_ras id_ras.pub保存该机器的公钥私钥,
know_hosts:ssh会把你每个你访问过计算机的公钥(public key)都记录在~/.ssh/known_hosts。当下次访问相同计算机时,OpenSSH会核对公钥。如果公钥不同,OpenSSH会发出警告, 避免你受到DNS Hijack之类的攻击。
我们需要将刚才复制的我的公钥保存到authorized_keys文件中
通过vi打开编辑然后粘贴退出保存
此时,我们退出登陆,然后重新登陆,发现就不需要输入登陆密码了,可以实现免密登陆,美滋滋。
另外我们要注意,.ssh目录的权限为700,其下文件authorized_keys和私钥的权限为600。否则会因为权限问题导致无法免密码登录
另外,将公钥拷贝到服务器的~/.ssh/authorized_keys文件中方法有如下几种:
1、将公钥通过scp拷贝到服务器上,然后追加到~/.ssh/authorized_keys文件中,这种方式比较麻烦。scp -P 22 ~/.ssh/id_rsa.pub user@host:~/。
2、通过ssh-copy-id命令最简单,也是第一种方法,ssh-copy-id "-p22 user@host"即可,有时候user@host需要加引号,前面写-p端口号
3、可以通过cat ~/.ssh/id_rsa.pub | ssh -p 22 user@host ‘cat ~/.ssh/authorized_keys',这个也是比较常用的方法,因为可以更改端口号。
4.2 基于公钥密钥的 SSHClient 方式登录
进入路径查看我本地id_rsa文件路径
import paramiko private_key = paramiko.RSAKey.from_private_key_file('/Users/ljf/.ssh/id_rsa') # 创建SSH对象 ssh = paramiko.SSHClient() # 允许连接不在know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接服务器 ssh.connect(hostname='192.168.199.146', port=22, username='fishman', pkey=private_key) # 执行命令 stdin, stdout, stderr = ssh.exec_command('df') # 获取命令结果 res,err = stdout.read(),stderr.read() result = res if res else err print(result.decode()) # 关闭连接 ssh.close()
同理。可以用transport封装
import paramiko private_key = paramiko.RSAKey.from_private_key_file('/Users/ljf/.ssh/id_rsa') # 实例化一个transport对象 transport = paramiko.Transport(('192.168.199.146', 22)) # 建立连接 transport.connect(username='fishman', pkey=private_key) ssh = paramiko.SSHClient() ssh._transport = transport stdin, stdout, stderr = ssh.exec_command('df') # 获取命令结果 res,err = stdout.read(),stderr.read() result = res if res else err print(result.decode()) # 关闭连接 ssh.close()
同理,可以直接根据私钥字符串连接,首先在本地获取私钥字符串
"""-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAskNhrZGynBQD/5F//uuXvdUPBCfrcKcqoxAc5VZmdpscvGcO 2/wH3letHoZaAqu6edtSBiWl1m6925MuyZY3Ose3AGbEVu5mUTxXpT2GuYieJSLI VN0cIW02FukcqxviJxd6IJye24IKT5iW1hcaqqWcHZBIpaZ79PvG9e360mxdcWkE Iee7w4xu/Ma9KcrNjAc8paq7NUTYk3Ec/OsyU+0m7o2GP26YlAe/MOOgpkZcMxzp PGYNIXHoKkjB426EMoZEl9MXBb9mSx5uRqX22t/OtzPutkNa9lPkFuSotX957BsB 1kCEbhzVpXVM1imET7CiR1b8BcJGAxt7GWTFkQIDAQABAoIBAH54j7Q0yiMxkCSh dc3GF8H2htDMAZ3K+9T0eYu74LYFFj4UX9Zy2KJGUex2JSX/8CzEDU2PKDkaGFjP 80HR8R0i1BLU1jdWrAC2bvgszoiTBKAULU0IEg0lDlryyAQdpDVX0q2QcKQLfoU6 HMmHWsP2+ut+kgv0Mb19Y4rXbDwctmx53BJ0Ykk3RaQe8FiLOAAdFzxbGIUdIwLh N8PEh9HW/wMFvJVEAgNu3oD0bONH+PZKasxqnOQlHrILSPQrNiXBOxCYr9u8cFx7 hl1V0yhQHnCF+JK2se6VXQnJ1pUVtWc3kRTtwas3x7IRPdNJDbj8WT6/n6ocUm+Q OgEY9gECgYEA3CtYVABizz8GsHpged/xDkOKf9gET5m1kX/awdd3m6lkoWboB5OL ym6YwxWpl6Fs0XHRAih3w2434MyTxqdgZf5ixMgq00PBr7itGycokLm1KO1GFjbZ 4tog3qwX6vejHsDl+TMWojxmkEjwQ1uPcQOeY96CvOafHH+kP5zmfAUCgYEAz0Ym 1y07sSfiKSDEO+99r7deex8fssk/xcFI3PgD/4zwu0zGF3QIB124cbZX6Sy6ut0d jzX+RQm5OGUNR1m4Qg1syeNIEF/2mV0rL7+qPGIpYIsuoeuBwedNj7qTIDx4wKtm aJcwdErEQwRJ7UOX9KEdPkKivyf4A+uT5fCsJR0CgYBE7tFF46UMLDiE8pvYLLRF egIYCuM2pPKDLpuoSzToqL2YBycokBqZc80ib1rc3a67WL5OxarRpmWaXZL7BJaa +G2mHOHDqZgv00tnj/gUcAB3Yuqps9y+OPtHnGwUphoNW+nk/wjcHLsj+6I2BKnB gZeKvzUBvdcdTh13yUEknQKBgHBkmNEbPP/+IXutwdrCLYQnyXq30Mdwqzz/ZxQz BHABK9RUeCHlkCj2X/qBJsBQudxz5ABxBbTH5gC3gvDKrMhcYT5EGSKP9rcIt09H /faKP+eS8TFp882CMCOcxwS25b+L8ZcLTIHyvOOeIrweZ/qFlsbY+UjwUmNFzcfk rmPdAoGBAJ9NWhf16aLQxUrPkUvHK9k7ONUadamBxA6NNvHMZxow81/p9VQK71o7 iUdJmC/+VOvGqbA3AbtqkbjBMUWGjEeVKLxMnCZngfu9J6bnWDUaYbQz3gVY63ca KFWjRXO6GtynW0Dec0Nj/q22V5J+2ZCkIvSAQ+cI04d0Ij7RdKPl -----END RSA PRIVATE KEY-----""" private_key = paramiko.RSAKey(file_obj=StringIO(key_str)) transport = paramiko.Transport(('192.168.199.146', 22)) transport.connect(username='fishman', pkey=private_key) ssh = paramiko.SSHClient() ssh._transport = transport stdin, stdout, stderr = ssh.exec_command('df') res,err = stdout.read(),stderr.read() result = res if res else err print(result.decode()) # 关闭连接 ssh.close()
以上得到的结果都相同
这里需要注意,如果你是Linux及OSX系统,那么自带SSH,以上操作都没问题,如果你是Windows,那么通过Xsheel也可以生成一个,通过Xsheel--》工具--》新建用户秘钥生成向导,
如果没有装Xsheel,可以将Linux的公钥copy过来,这样在这里可以读取本地存的公钥信息
通过命令sz ~/.ssh/id_rsa,将公钥保存为一个txt文件,paramiko.RSAKey.from_private_key_file(公钥文件)
五,SFTPClient用于连接远程服务器并执行上传下载
我们计划将本地LocalFile.txt上传到fishman机器的test路径下remote.txt,这是该路径下原来的文件,其中remote.txt为空,没有内容,通过sftp.put('LocalFile.txt', '/home/fishman/test/remote.txt')即可上传
基于用户名密码上传下载
import paramiko # 实例化一个trans对象# 实例化一个transport对象 transport = paramiko.Transport(('192.168.199.146', 22)) # 建立连接 transport.connect(username='fishman', password='9') # 实例化一个 sftp对象,指定连接的通道 sftp = paramiko.SFTPClient.from_transport(transport) # LocalFile.txt 上传至服务器 /home/fishman/test/remote.txt # sftp.put('LocalFile.txt', '/home/fishman/test/remote.txt') # 将LinuxFile.txt 下载到本地 fromlinux.txt文件中 sftp.get('/home/fishman/test/LinuxFile.txt', 'fromlinux.txt') transport.close()
同理,我们可以下载远程机器上test路径下的LinuxFile.txt文件到本地fromLinux.txt
基于公钥密钥上传下载
import paramiko private_key = paramiko.RSAKey.from_private_key_file('/Users/ljf/.ssh/id_rsa') transport = paramiko.Transport(('192.168.199.146', 22)) transport.connect(username='fishman', password='9') sftp = paramiko.SFTPClient.from_transport(transport) # LocalFile.txt 上传至服务器 /home/fishman/test/remote.txt # sftp.put('LocalFile.txt', '/home/fishman/test/remote.txt') # 将LinuxFile.txt 下载到本地 fromlinux.txt文件中 sftp.get('/home/fishman/test/LinuxFile.txt', 'fromlinux.txt') transport.close()
5 实现输入命令立马返回结果的功能 以上操作都是基本的连接,如果我们想实现一个类似xshell工具的功能,登录以后可以输入命令回车后就返回结果:
import paramiko import os import select import sys # 建立一个socket trans = paramiko.Transport(('192.168.2.129', 22)) # 启动一个客户端 trans.start_client() # 如果使用rsa密钥登录的话 ''' default_key_file = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') prikey = paramiko.RSAKey.from_private_key_file(default_key_file) trans.auth_publickey(username='super', key=prikey) ''' # 如果使用用户名和密码登录 trans.auth_password(username='super', password='super') # 打开一个通道 channel = trans.open_session() # 获取终端 channel.get_pty() # 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样 channel.invoke_shell() # 下面就可以执行你所有的操作,用select实现 # 对输入终端sys.stdin和 通道进行监控, # 当用户在终端输入命令后,将命令交给channel通道,这个时候sys.stdin就发生变化,select就可以感知 # channel的发送命令、获取结果过程其实就是一个socket的发送和接受信息的过程 while True: readlist, writelist, errlist = select.select([channel, sys.stdin,], [], []) # 如果是用户输入命令了,sys.stdin发生变化 if sys.stdin in readlist: # 获取输入的内容 input_cmd = sys.stdin.read(1) # 将命令发送给服务器 channel.sendall(input_cmd) # 服务器返回了结果,channel通道接受到结果,发生变化 select感知到 if channel in readlist: # 获取结果 result = channel.recv(1024) # 断开连接后退出 if len(result) == 0: print("\r\n**** EOF **** \r\n") break # 输出到屏幕 sys.stdout.write(result.decode()) sys.stdout.flush() # 关闭通道 channel.close() # 关闭链接 trans.close()
6 支持tab自动补全
import paramiko import os import select import sys import tty import termios ''' 实现一个xshell登录系统的效果,登录到系统就不断输入命令同时返回结果 支持自动补全,直接调用服务器终端 ''' # 建立一个socket trans = paramiko.Transport(('192.168.2.129', 22)) # 启动一个客户端 trans.start_client() # 如果使用rsa密钥登录的话 ''' default_key_file = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') prikey = paramiko.RSAKey.from_private_key_file(default_key_file) trans.auth_publickey(username='super', key=prikey) ''' # 如果使用用户名和密码登录 trans.auth_password(username='super', password='super') # 打开一个通道 channel = trans.open_session() # 获取终端 channel.get_pty() # 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样 channel.invoke_shell() # 获取原操作终端属性 oldtty = termios.tcgetattr(sys.stdin) try: # 将现在的操作终端属性设置为服务器上的原生终端属性,可以支持tab了 tty.setraw(sys.stdin) channel.settimeout(0) while True: readlist, writelist, errlist = select.select([channel, sys.stdin,], [], []) # 如果是用户输入命令了,sys.stdin发生变化 if sys.stdin in readlist: # 获取输入的内容,输入一个字符发送1个字符 input_cmd = sys.stdin.read(1) # 将命令发送给服务器 channel.sendall(input_cmd) # 服务器返回了结果,channel通道接受到结果,发生变化 select感知到 if channel in readlist: # 获取结果 result = channel.recv(1024) # 断开连接后退出 if len(result) == 0: print("\r\n**** EOF **** \r\n") break # 输出到屏幕 sys.stdout.write(result.decode()) sys.stdout.flush() finally: # 执行完后将现在的终端属性恢复为原操作终端属性 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) # 关闭通道 channel.close() # 关闭链接 trans.close()