Python网络模块Paramiko基本使用

时间:2024-01-05 20:11:14

一、Paramiko简介

首先来看谁创造了paramiko,是一个名叫Jeff Forcier创建了paramiko项目。项目主页:http://www.paramiko.org,可以去看上面有很多相关的信息。然后这个项目是开源的,源码维护在github上,源码地址:https://github.com/paramiko/paramiko。

这个paramiko是Python下面一个非常著名的ssh项目,很多人贡献源码,当然这个Jeff Forcier它是一个主要的维护者。

我们首先看一下paramiko的起源,最开始它是用Python的对这个ssh进行一个封装,封装就是对一些面向对象的方法,就是把变量和方法给它包装起来。提供一些外部的api给大家很方便的使用它,比如说ssh,它很复杂但是通过它的一个包装把它很简单的提供给大家使用,那么问题来了。什么是ssh呢?

二、Ssh基本原理

简单来说ssh是用于计算机之间加密登陆的网络协议,协议就是端对端的一种通讯交互,我们可以看一下这个ssh它有什么特点?

传统的网络服务程序,如rsh、FTP、POP和Telnet其本质上都是不安全的;因为它们在网络上用明文传送数据、用户帐号和用户口令,很容易受到中间人(man-in-the-middle)攻击方式的攻击。就是存在另一个人或者一台机器冒充真正的服务器接收用户传给服务器的数据,然后再冒充用户把数据传给真正的服务器。

而SSH是目前较可靠,专为远程登录会话和其它网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理过程中的信息泄露问题。通过SSH可以对所有传输的数据进行加密,也能够防止DNS欺骗和IP欺骗。

ssh之另一项优点为其传输的数据可以是经过压缩的,所以可以加快传输的速度。SSH有很多功能,它既可以代替Telnet,又可以为FTP、POP、甚至为PPP提供一个安全的“通道”。

ssh是Linux,MAC上的标配,比如说你的电脑是苹果电脑,它是osx系统,默认的它就有ssh是可以直接用的。ssh的命令行基本上就是ssh+username和ip,默认端口是22。它的使用是非常简单的,我们一旦知道一台机子的它的IP跟用户名和密码,就能ssh来进行登录这样就可以进行交互式的操作,而且上面看它的特点它是,进过加密的是相对安全的,那我们本次所用的paramiko和ssh这个相互之间有什么特点呢?

关于SSH更多可以看:安全连接工具OpenSSH介绍

三、Paramiko vs Ssh shell

首先ssh它是自带的使用非常的简单,但是paramiko呢它是一个更高层次的封装,可以实现更复杂的命令,一个实际工作中遇到的问题就是,面对自动化运维面对若干台机器Shell脚本,用ssh指令写Shell脚本是非常麻烦的。而且Shell脚本的话本身它的语法是不太好操作,但paramiko是基于Python的它明显是一个更好的选择,这个Python语言它本身更就是语法非常的简洁,语法非常的好写。paramiko就是基于Python的所以说用paramiko来操作这些指令的话是相对容易的。然后paramiko又跟好的扩展性。我们之后就可以看到其它用paramiko的一些扩展,但是用ssh的直接用它和这个Shell的话,扩展性是很差的。我们可以看出paramiko是有很大的优势的。

paramiko目前使用非常广泛,首先在DevOps这个领域内它的使用广泛,而且很多paramiko二次开发工具,比如fabic,这个是Jeff Forcier本人在paramiko的基础上开发。当然也有其它的开发者,基于paramiko进行了其它的适用于自己项目的二次开发。

四、Paramiko基于用户名和密码的SSHClient方式连接

使用paramiko模块有两种连接方式,一种是通过paramiko.SSHClient()函数,另外一种是通过paramiko.Transport()函数。

1. 新建paramiko.SSHClient

>>> import paramiko
>>> client = paramiko.SSHClient()
1
2
>>> import paramiko
>>> client = paramiko.SSHClient()

Paramiko连接远程服务器,它的过程是这样的,首先是新建一个SSHClient,这个SSHClient是什么呢?它是Paramiko提供给我们的一个api中的类,因为Paramiko它是一个基于ssh协议的一个封装的类库,它提供了一些好用的api给我们使用,让我们来完成各种各样的操作。这个SSHClient就是它提供的一个要访问远程,和文件传输的一个最基本的接口。

2. 设置hot key机制

>>> client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
1
>>> client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

另外一个就是要设置它的hot key,处理服务器端发来的公钥的机制。允许将信任的主机自动加入到host_allow列表,此方法必须放在connect方法的前面。Paramiko它提供给我们一个比较简单的设置的办法。

3. 调用API connect

>>> client.connect(hostname="10.10.0.112", port=22, username="root", password="123456", timeout=10)
1
>>> client.connect(hostname="10.10.0.112", port=22, username="root", password="123456", timeout=10)

最后就是调用它的的connect API连接。它的connect方法有很多的参数,有IP、端口、用户名、密码、还有一些其它的很多参数。我们在用其中一个比较重要的参数就是timeout就是超时时间,就是建立这个链接的时候我们想让它,如果多久没有直接连接,就返回异常。

4. 执行命令

成功建立连接之后,就可以进行对远程服务器执行命令了。它其实也是非常简单的,调用方法exec_command()即可,有好几个参数,比如说command、bufsize,我们现在就只用它的command参数,打印远程服务器时间。

>>> stdin, stdout, stderr = client.exec_command('date')
1
>>> stdin, stdout, stderr = client.exec_command('date')

这个command返回的是一个元组,有三个变量,分别是有输入(stdin)输出(stdout)和错误(stderr),我们用的最多的是它的输出。如果直接打印输出,其实,是把一些内部的信息给打印出来了。可以使用read或者readlines直接打印出结果:

>>> print(stdout.read())
b'Tue Oct 24 05:18:14 EDT 2017\n'
1
2
>>> print(stdout.read())
b'Tue Oct 24 05:18:14 EDT 2017\n'

decode方法只是让输出好看一些。

>>> print(stdout.read().decode())
Sun Oct 22 23:24:06 EDT 2017
1
2
>>> print(stdout.read().decode())
Sun Oct 22 23:24:06 EDT 2017

或者使用迭代的方式打印:

>>> for i in stdout:
... print(i, end='')
...
Sun Oct 22 23:27:01 EDT 2017
1
2
3
4
>>> for i in stdout:
...     print(i, end='')
...
Sun Oct 22 23:27:01 EDT 2017

这里print使用end参数去掉了自动换行功能(\n),因为执行完命令再得到结果时就已经带了\n,如
print(stdout.read())所示。所以这里如果不去掉,print时就会发现多出现一行空格。

5. 关闭连接

当命令执行完了之后,就可以调用close方法把连接关闭了。

>>> client.close()
1
>>> client.close()

五、Paramiko基于密钥的SSHClient方式连接

首先需要建立好服务端和客户端的秘钥对(把客户端公钥信息上传到服务器/User/.ssh/authorized_keys文件中)。

然后指定本地的私钥文件,使用不同的算法初始化不同的类,一般常用的就是RSA算法。如果建立密钥对时设置有密码,需要指定password为设定的密,如果没有则不用指定password参数。

>>> import paramiko
>>> pkey = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
1
2
>>> import paramiko
>>> pkey = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')

其余的使用方法跟普通的用户名密码连接方式操作基本相同,如下:

>>> client = paramiko.SSHClient()
>>> client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> client.connect(hostname='10.10.0.112', port=22, username='root', pkey=pkey)
>>> stdin, stdout, stderr = client.exec_command('date')
>>> print(stdout.read().decode())
Sun Oct 22 23:52:56 EDT 2017
>>> client.close()
1
2
3
4
5
6
7
>>> client = paramiko.SSHClient()
>>> client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> client.connect(hostname='10.10.0.112', port=22, username='root', pkey=pkey)      
>>> stdin, stdout, stderr = client.exec_command('date')
>>> print(stdout.read().decode())                      
Sun Oct 22 23:52:56 EDT 2017
>>> client.close()

六、Paramiko基于Transport方式连接

基于传统的连接服务器、执行命令、关闭连接这样的一个操作能够满足单次执行,互相之间没有联系的操作,但有时候需要登录上服务器执行多个操作,比如执行多个命令、上传/下载文件,传统方式则满足不了。但可以通过Transport方式来操作。

# 实例化一个transport对象;
>>> transport = paramiko.Transport(('10.10.0.112', 22))

# 建立连接;
>>> transport.connect(username='root', password='123456')

# 将sshclient的对象的transport指定为以上的transport;
>>> ssh = paramiko.SSHClient()
>>> ssh._transport = transport

# 执行命令,和传统方法一样;
>>> stdin, stdout, stderr = ssh.exec_command('df -hl')
>>> print(stdout.read().decode())

# 关闭连接;
>>> transport.close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 实例化一个transport对象;
>>> transport = paramiko.Transport(('10.10.0.112', 22))
# 建立连接;
>>> transport.connect(username='root', password='123456')
# 将sshclient的对象的transport指定为以上的transport;
>>> ssh = paramiko.SSHClient()
>>> ssh._transport = transport
# 执行命令,和传统方法一样;
>>> stdin, stdout, stderr = ssh.exec_command('df -hl')
>>> print(stdout.read().decode())
# 关闭连接;
>>> transport.close()

同样,对于秘钥方式的transport连接,只需要变更connect连接方法的参数即可:

>>> transport.connect(username='root', pkey=pkey)
1
>>> transport.connect(username='root', pkey=pkey)

七、Paramiko上传下载文件

Paramiko除了执行命令之外,还可以用来上传下载文件。一般选择SCP或者SFTP。

SCP

SCP全写是:Secure Copy,是基于SSH协议的文件拷贝方法,可以在本机与远程主机或两个远程主机之间进行文件拷贝。SCP的实现需要通过SCP协议以及SCP程序。

SFTP

在计算机领域,SSH文件传输协议 (英语:SSH File Transfer Protocol,也称Secret File Transfer Protocol,Secure FTP或SFTP) 是一数据流连线,提供档案存取、传输和管理功能的网络传输协定。由互联网工程任务组 (IETF) 设计,透过SSH 2.0 的扩充提供安全档案传输能力,但也能够被其它协定使用。

Paramiko选择SFTP作为上传下载工具,它提供给我们paramiko.SFTPClient这个类,然后这个类提供了很多的这个函数来供我们来调用来处理SFTP它的各种各样的运用,看一下这个编码的实现。

# 实例化一个transport对象;
>>> transport = paramiko.Transport(('10.10.0.112', 22))

# 建立transport连接;
>>> transport.connect(username='root', password='123456')

# 实例化一个sftp对象,指定连接的通道;
>>> sftp = paramiko.SFTPClient.from_transport(transport)

# 使用put方法上传文件;
>>> sftp.put(localpath='/root/ParamikoClient.py',remotepath='/tmp/ParamikoClient.py')

# 使用get方法下载文件;
>>> sftp.get(remotepath='/tmp/ParamikoClient.py',localpath='/tmp/ParamikoClient.py')

# 关闭通道;
>>> sftp.close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 实例化一个transport对象;
>>> transport = paramiko.Transport(('10.10.0.112', 22))
# 建立transport连接;
>>> transport.connect(username='root', password='123456')
# 实例化一个sftp对象,指定连接的通道;
>>> sftp = paramiko.SFTPClient.from_transport(transport)
# 使用put方法上传文件;
>>> sftp.put(localpath='/root/ParamikoClient.py',remotepath='/tmp/ParamikoClient.py')
# 使用get方法下载文件;
>>> sftp.get(remotepath='/tmp/ParamikoClient.py',localpath='/tmp/ParamikoClient.py')
# 关闭通道;
>>> sftp.close()

sftp还有很多方法,如remove、rename、chmod、chown、listdir、mkdir、rmdir、open、truncate、symlink、unlink等等。

另外如果批量上传下载,可以使用如下方式:

>>> files = sftp.listdir('/root')
>>> for i in files:
... sftp.get(remotepath=os.path.join('/root/', i), localpath=os.path.join('/tmp', i))
1
2
3
>>> files = sftp.listdir('/root')
>>> for i in files:
...     sftp.get(remotepath=os.path.join('/root/', i), localpath=os.path.join('/tmp', i))

八、编写优雅实现

1. 消除硬编码

第一个就是它有硬编码的情况,什么是硬编码,什么是硬编码就是说在代码里面,你用的一些变量它是写死的,比如说我们刚才调用connect APId的时候它的,ip地址端口用户名和密码都是我们写死的,什么192.168.3.106,用户名,密码,如果它的对端的这个端口它变了,或者说是密码用户名一般不会变。它的密码改变我们是不是要去修改代码,如果在实际工作中这样是非常不方便的,而且是有一定风险的,修改代码就意味着它可能是bu*生的边缘。所以说我们最好把这个可配置的代码,和变量分离开来。就是说消除硬编码的一个办法就是让它可配置。

2. 异常捕捉

其次就是异常的捕捉,我们可以看到刚开始我们想列了,那个设置hot key机制时候,它调用connect方法,就抛出来一个sshaexception这个异常,但是我们并没有对它进行处理。这样的话就会在 实际的工作中它就非常的不好,甚至是有一些风险,因为不捕获这个异常的话,可能你的代码,就没办法在执行下去了,然后你写了这个软件可能就当掉了实际生产环境下,是一个非常严重的问题。

3. 封装

另外就是进行一下封装,我们看到Paramiko它是对这个,它其实就是对ssh协议的一个封装,然而我们在实际的使用Paramiko的过程中,可以针对自己项目的需要。对它进行二次封装,就是说我封装成我们大家,一起,团队的人它更方便的使用它,甚至它可以不知道Paramiko这个的存在,而调用你封装的接口。来进行一个更方便的操作。

那我们就看实际的过程中我们是怎样来解决这几个问题的,首先就是消除硬编码的问题,我们引入另外一个configparser这个库,configparser就是可以把这个配置很方便的读进来。

>>> import configparser
>>> config = configparser.ConfigParser()
1
2
>>> import configparser
>>> config = configparser.ConfigParser()

然后它有一个read的API就是读取文件。

那现在我们就一起来建立一个文件,叫做config.conf

[ssh]
host=10.10.0.112
port=22
username=root
password=123456
timeout=1.0
1
2
3
4
5
6
[ssh]
host=10.10.0.112
port=22
username=root
password=123456
timeout=1.0

然后我们read这个config.conf。那么这些硬编码的地方,就可以换成对应的配置,如下:

client.connect(hostname = config.get('ssh','host'),
port = config.getint('ssh','port'),
username = config.get('ssh','username'),
password = config.get('ssh','password'),
timeout = config.getfloat('ssh','timeout'))
1
2
3
4
5
client.connect(hostname = config.get('ssh','host'),
    port = config.getint('ssh','port'),
    username = config.get('ssh','username'),
    password = config.get('ssh','password'),
    timeout = config.getfloat('ssh','timeout'))

这样我们把这些变量给替换成用这个配置来解析出来它实际的值。我们就是用这个configparser来完成的,现在它运行的是非常的好,然后我们就把这些实际的参数给用配置给它隐藏起来,如果将来我们配置一旦更改的时候我们只需要更改这个config.conf就可以。

完整代码如下:

import paramiko
import configparser

config = configparser.ConfigParser()
config.read('config.conf')

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname = config.get('ssh','host'),
port = config.getint('ssh','port'),
username = config.get('ssh','username'),
password = config.get('ssh','password'),
timeout = config.getfloat('ssh','timeout'))

stdin, stdout, stderr = client.exec_command('date')
print(stdout.read().decode())
client.close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import paramiko
import configparser
config = configparser.ConfigParser()
config.read('config.conf')
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname = config.get('ssh','host'),
    port = config.getint('ssh','port'),
    username = config.get('ssh','username'),
    password = config.get('ssh','password'),
    timeout = config.getfloat('ssh','timeout'))
stdin, stdout, stderr = client.exec_command('date')
print(stdout.read().decode())
client.close()

然后我们来看一下,如何进行异常的捕获。首先这个connect它会抛出这个异常的时候我们其实就应该用Python的try来捕获它,然后用这个except Exception as e:可以把异常给打印出来。

import os
import paramiko
import configparser

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

def connect():
try:
config = configparser.ConfigParser()
config.read('config.conf')
client.connect(
hostname = config.get('ssh','host'),
port = config.getint('ssh','port'),
username = config.get('ssh','username'),
password = config.get('ssh','password'),
timeout = config.getfloat('ssh','timeout')
)
except Exception as e:
print(e)
try:
client.close()
os._exit(1)
except:
pass

connect()
stdin, stdout, stderr = client.exec_command('date')
print(stdout.read().decode())
client.close()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import os
import paramiko
import configparser
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
def connect():
    try:
        config = configparser.ConfigParser()
        config.read('config.conf')
        client.connect(
            hostname = config.get('ssh','host'),
            port = config.getint('ssh','port'),
            username = config.get('ssh','username'),
            password = config.get('ssh','password'),
            timeout = config.getfloat('ssh','timeout')
        )
    except Exception as e:
        print(e)
        try:
            client.close()
            os._exit(1)
        except:
            pass
connect()
stdin, stdout, stderr = client.exec_command('date')
print(stdout.read().decode())
client.close()

其实在连接这个地方抛出捕获的时候我们就在这把它给捕获了,并可以处理掉,不然到后面这个exec_command它也是会抛出异常。

接下来我们就对它进行一个封装,如何封装,封装它是一个面向对象的一个特点,就会面向对象方法的一个特点,所以说我们把一些细节给隐藏起来,怎么隐藏呢,就是通过类。

我们可以新建一个ParamikoClient这个类,整体代码如下:

import paramiko
import configparser

class ParamikoClient:
def __init__(self, file):
self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.config = configparser.ConfigParser()
self.config.read(file)

def connect(self):
try:
self.client.connect(
hostname = self.config.get('ssh','host'),
port = self.config.getint('ssh','port'),
username = self.config.get('ssh','username'),
password = self.config.get('ssh','password'),
timeout = self.config.getfloat('ssh','timeout')
)
except Exception as e:
print(e)
try:
self.client.close()
except:
pass

def runcmd(self, cmd):
stdin, stdout, stderr = self.client.exec_command(cmd)
return stdout.read().decode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import paramiko
import configparser
class ParamikoClient:
    def __init__(self, file):
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.config = configparser.ConfigParser()
        self.config.read(file)
    def connect(self):
        try:
            self.client.connect(
                hostname = self.config.get('ssh','host'),
                port = self.config.getint('ssh','port'),
                username = self.config.get('ssh','username'),
                password = self.config.get('ssh','password'),
                timeout = self.config.getfloat('ssh','timeout')
            )
        except Exception as e:
            print(e)
            try:
                self.client.close()
            except:
                pass
    def runcmd(self, cmd):
        stdin, stdout, stderr = self.client.exec_command(cmd)
        return stdout.read().decode()

主要三个方法,第一个方法就是在初始化时把配置文件传进来,第二个方法connect用来连接使用,第三个方法就是用来执行命令的。注意,提供给其他脚本调用的类方法都需要使用return返回而不是print。

这个类做好之后,就可以使用了,可以直接在交互模式下把ParamikoClient类导进来,如下:

>>> from ParamikoClient import ParamikoClient
1
>>> from ParamikoClient import ParamikoClient

然后就可以初始化这个类,并传入配置文件:

>>> client = ParamikoClient('config.conf')
1
>>> client = ParamikoClient('config.conf')

调用它的connect方法来接服务器:

>>> client.connect()
1
>>> client.connect()

然后就可以直接调用runcmd方法来执行命令:

>>> print(client.runcmd('date'))
Mon Oct 23 01:51:00 EDT 2017
1
2
>>> print(client.runcmd('date'))
Mon Oct 23 01:51:00 EDT 2017

我们这样就看到到了这个从它编码,到进行一个更优雅实现的这么一个过程。

其实还有一个更好的做法就是把这个ParamikoClient这个类单独放到一个文件里面,封装性会更好。

Github地址:https://github.com/paramiko/paramiko


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (3)or分享 (0)