[python] paramiko实现SSH和SFTP

目录

1.SSHClient

2.SSHClient 封装 Transport

3.基于公钥密钥连接

3.1 基于公钥密钥连接(SSHClient)

3.2 基于公钥密钥连接(SSHClient 封装 Transport)

4.基于私钥字符串进行连接

5.SFTPClient

5.1 基于用户名密码上传下载

5.2 基于公钥密钥上传下载

6.一个ssh和sftp工具脚本


API文档  http://docs.paramiko.org/

环境准备

CentOS7, Python3.9 非封闭环境 使用pip安装下载包记录

[root@k8s-node2 ~]# pip3 install paramiko
Collecting paramiko
  Downloading paramiko-2.11.0-py2.py3-none-any.whl (212 kB)
……
Collecting pynacl>=1.0.1
  Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB)
……
Collecting six
  Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting bcrypt>=3.1.3
  Downloading bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (61 kB)
……
Collecting cryptography>=2.5
  Downloading cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.2 MB)
……
Collecting cffi>=1.1
  Downloading cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (441 kB)
……
Collecting pycparser
  Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
……
Installing collected packages: pycparser, cffi, six, pynacl, cryptography, bcrypt, paramiko
Successfully installed bcrypt-3.2.2 cffi-1.15.1 cryptography-37.0.4 paramiko-2.11.0 pycparser-2.21 pynacl-1.5.0 six-1.16.0
……


使用方法

1.SSHClient

用于连接远程服务器并执行基本命令

基于用户名密码连接:

>>> import paramiko

# 创建SSH对象

>>> ssh = paramiko.SSHClient()
>>> ssh
<paramiko.client.SSHClient object at 0x7f59f9a8cfd0>

(接下来是对这个SSH对象的操作)

# 允许连接不在know_hosts文件中的主机

>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 连接服务器

>>> ssh.connect(hostname='192.168.183.122', port=22, username='test', password='test')

# 执行命令

>>> stdin, stdout, stderr = ssh.exec_command('df')

# 获取命令结果

>>> result = stdout.read()
>>> print(result.decode('utf-8'))
Filesystem     1K-blocks    Used Available Use% Mounted on
devtmpfs          915780       0    915780   0% /dev
tmpfs             931512       0    931512   0% /dev/shm
tmpfs             931512   10536    920976   2% /run
tmpfs             931512       0    931512   0% /sys/fs/cgroup
/dev/sda3       18555904 8269368  10286536  45% /
/dev/sda1         303780  166724    137056  55% /boot
tmpfs             186304      12    186292   1% /run/user/42
tmpfs             186304       0    186304   0% /run/user/0
tmpfs             186304       0    186304   0% /run/user/1000

>>>

# 关闭连接

>>> ssh.close()

# 关闭之后就不能ssh到对端了

>>> ssh
<paramiko.client.SSHClient object at 0x7f59f9a8cfd0>
>>>
>>> ssh.exec_command('df')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/client.py", line 510, in exec_command
    chan = self._transport.open_session(timeout=timeout)
AttributeError: 'NoneType' object has no attribute 'open_session'
>>>



2.SSHClient 封装 Transport

>>> import paramiko
>>> transport = paramiko.Transport(('192.168.183.122', 22))
>>> transport.connect(username='test', password='test')
>>>
>>> ssh = paramiko.SSHClient()
>>> ssh._transport = transport
>>>
>>> stdin, stdout, stderr = ssh.exec_command('df -h')
>>> res=stdout.read()
>>> print(res.decode('utf-8'))
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        895M     0  895M   0% /dev
tmpfs           910M     0  910M   0% /dev/shm
tmpfs           910M   22M  888M   3% /run
tmpfs           910M     0  910M   0% /sys/fs/cgroup
/dev/sda3        18G  7.9G  9.9G  45% /
/dev/sda1       297M  163M  134M  55% /boot
tmpfs           182M   12K  182M   1% /run/user/42
tmpfs           182M     0  182M   0% /run/user/0
tmpfs           182M     0  182M   0% /run/user/1000

>>>
>>> transport.close()
>>>


3.基于公钥密钥连接

private_key = paramiko.RSAKey.from_private_key_file('/somepath/id_rsa')

3.1 基于公钥密钥连接(SSHClient

本端/客户端 node2 用户root
对端/服务端 node1 用户test

前提条件:
(1)客户端需要生成自己的私钥(客户端用户执行ssh-keygen命令),例如:/root/.ssh/id_rsa
(2)服务端需要有文件authorized_keys,且其中有客户端用户的公钥(客户端执行ssh-copy-id命令),即客户端能够免密登录服务端

[root@k8s-node2 .ssh]# ssh-copy-id  -i  id_rsa.pub test@192.168.183.122
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
test@192.168.183.122's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'test@192.168.183.122'"
and check to make sure that only the key(s) you wanted were added.

[root@k8s-node2 .ssh]#
[root@k8s-node2 .ssh]# ssh test@192.168.183.122
Last login: Fri Jul 22 19:53:46 2022 from 192.168.183.123
[test@k8s-node1 ~]$ exit
logout
Connection to 192.168.183.122 closed.
[root@k8s-node2 .ssh]#

参考:ssh首次登录避免输入yes、两台服务器间免密钥登录,3.方法二 https://blog.csdn.net/wy_hhxx/article/details/93755208 

【例3-1】

>>> import paramiko
>>> ssh = paramiko.SSHClient()     #创建SSH对象
>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())   #允许连接不在know_hosts文件中的主机

# 实例化一个私钥对象

说明:这里试了其实不传这个私钥也行,不过看网上的其它帖子都带着pkey,help查看其默认值None,所以还是带着比较好,例3-2详细说明

>>> private_key = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')

# 连接服务器

>>> ssh.connect(hostname='192.168.183.122', port=22, username='test', pkey=private_key)

# 执行命令

>>> stdin, stdout, stderr = ssh.exec_command('hostname')

# 获取命令结果、关闭连接

>>> result = stdout.read()
>>> print(result.decode('utf-8'))
k8s-node1

>>> ssh.close()
>>>

例3-2和例3-3是自己的尝试,查阅资料一般都会指定pkey

【例3-2】如果pkey放置在默认位置,即 用户家目录/.ssh,连接服务器时可以不传入该参数

>>> import paramiko
>>> ssh = paramiko.SSHClient()     
>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> ssh.connect(hostname='192.168.183.122', port=22, username='test')  #pkey取默认路径的私钥
>>> stdin, stdout, stderr = ssh.exec_command('hostname')
>>>
>>> result = stdout.read()
>>> print(result.decode('utf-8'))
k8s-node1

>>>

【例3-3】如果pkey从默认位置移除了,必须指定其路径

[root@k8s-node2 .ssh]# mv id_rsa /root/pydir/

----------------------------------------------------
...
>>> ssh.connect(hostname='192.168.183.122', port=22, username='test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/client.py", line 435, in connect
    self._auth(
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/client.py", line 767, in _auth
    raise SSHException("No authentication methods available")
paramiko.ssh_exception.SSHException: No authentication methods available
>>>

----------------------------------------------------
...
>>> private_key1 = paramiko.RSAKey.from_private_key_file('/root/pydir/id_rsa')
>>> ssh.connect(hostname='192.168.183.122', port=22, username='test', pkey=private_key1)
>>>


3.2 基于公钥密钥连接(SSHClient 封装 Transport

>>> import paramiko
>>>
>>> private_key = paramiko.RSAKey.from_private_key_file('/root/pydir/id_rsa')
>>>
>>> transport = paramiko.Transport(('192.168.183.122', 22))
>>> transport.connect(username='test', pkey=private_key)
>>>
>>> ssh = paramiko.SSHClient()
>>> ssh._transport = transport
>>> stdin, stdout, stderr = ssh.exec_command('hostname')
>>> result=stdout.read()
>>> print(result.decode('utf-8'))
k8s-node1

>>> transport.close()
>>>

说明:transport.connect不传入pkey会报错 Oops, unhandled type 3 ('unimplemented')

>>> stdin, stdout, stderr = ssh.exec_command('hostname')
Oops, unhandled type 3 ('unimplemented')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/client.py", line 510, in exec_command
    chan = self._transport.open_session(timeout=timeout)
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/transport.py", line 920, in open_session
    return self.open_channel(
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/transport.py", line 1046, in open_channel
    event.wait(0.1)
  File "/usr/local/python3/lib/python3.9/threading.py", line 574, in wait
    signaled = self._cond.wait(timeout)
  File "/usr/local/python3/lib/python3.9/threading.py", line 316, in wait
    gotit = waiter.acquire(True, timeout)
KeyboardInterrupt
>>>
>>>
>>> stdin, stdout, stderr = ssh.exec_command('hostname',timeout=5)
Oops, unhandled type 3 ('unimplemented')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/client.py", line 510, in exec_command
    chan = self._transport.open_session(timeout=timeout)
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/transport.py", line 920, in open_session
    return self.open_channel(
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/transport.py", line 1055, in open_channel
    raise SSHException("Timeout opening channel.")
paramiko.ssh_exception.SSHException: Timeout opening channel.
>>>

查看help,SSHClient和Transport的connect方法pkey默认值都是None,不知为什么上一节SSHClient尝试不传pkey(在默认路径)可以连上

>>> help(transport)
……
connect(self, hostkey=None, username='', password=None, pkey=None, gss_host=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True, gss_trust_dns=True)

>>> help(paramiko)
……
connect(self, hostname, port=22, username=None, password=None, pkey=None, key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, compress=False, sock=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True, gss_host=None, banner_timeout=None, auth_timeout=None, gss_trust_dns=True, passphrase=None, disabled_algorithms=None)

4.基于私钥字符串进行连接

key_str = """some content"""
private_key = paramiko.RSAKey(file_obj=StringIO(key_str))

和上一节差不多,只是读取private_key的方式变了

>>> import paramiko
>>> from io import StringIO
>>> key_str = """-----BEGIN RSA PRIVATE KEY-----
... MIIEowIBAAKCAQEAwPFheM/5KrPzfltEtehOAHRNlOcLjA0dO7x88C4YowrIll+r
......
... 62chR6A59nDlCjVbemRalq9x3TQriTqDxV0/cd3MPow21fsl7rc/6nL+Yo6YIS4U
... TI+jFuQbM7K8DD0xuG7pPMEn5r1MOy3iSp4bScBFxpKBgOlo/hh8
... -----END RSA PRIVATE KEY-----"""
>>> private_key = paramiko.RSAKey(file_obj=StringIO(key_str))
>>> transport = paramiko.Transport(('192.168.183.122', 22))
>>> transport.connect(username='test', pkey=private_key)
>>>
>>> ssh = paramiko.SSHClient()
>>> ssh._transport = transport
>>> stdin, stdout, stderr = ssh.exec_command('df')
>>> result = stdout.read()
>>> print(result.decode('utf-8'))
k8s-node1

>>> ssh.close()
>>>

5.SFTPClient

用于连接远程服务器并执行上传下载

5.1 基于用户名密码上传下载

>>> import paramiko
>>> transport = paramiko.Transport(('192.168.183.122',22))
>>> transport.connect(username='test',password='test')
>>>
>>> sftp = paramiko.SFTPClient.from_transport(transport)

(1)上传 sftp.put('srcpath', 'destpath')

注意:目标路径要写到文件名,只写到文件夹会报错

>>> sftp.put('/root/pydir/testfile', '/home/test')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 759, in put
    return self.putfo(fl, remotepath, file_size, callback, confirm)
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 714, in putfo
    with self.file(remotepath, "wb") as fr:
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 372, in open
    t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 822, in _request
    return self._read_response(num)
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 874, in _read_response
    self._convert_status(msg)
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 907, in _convert_status
    raise IOError(text)
OSError: Failure
>>>

将本端文件/root/pydir/testfile 上传至对端路径/home/test并命名为testfile2

>>> sftp.put('/root/pydir/testfile', '/home/test/testfile2')
<SFTPAttributes: [ size=0 uid=1000 gid=1000 mode=0o100664 atime=1658566650 mtime=1658566650 ]>
>>>

查看对端/home/test

[test@k8s-node1 ~]$ pwd
/home/test
[test@k8s-node1 ~]$ ll
total 0
-rw-rw-r-- 1 test test 0 Jul 23 01:57 testfile2
[test@k8s-node1 ~]$

(2)下载 sftp.get('srcpath', 'destpath')

注意:目标路径要写到文件名,只写到文件夹会报错

>>> sftp.get('/home/test/testfile2', '/tmp')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/sftp_client.py", line 810, in get
    with open(localpath, "wb") as fl:
IsADirectoryError: [Errno 21] Is a directory: '/tmp'
>>>

将对端/home/test/testfile2下载到本端/tmp并重命名为testfile

>>> sftp.get('/home/test/testfile2', '/tmp/testfile')
>>>
>>> transport.close()
>>>
------------------------------------------------------
[root@k8s-node2 ~]# ls -l /tmp/testfile
-rw-r--r-- 1 root root 0 Jul 23 17:28 /tmp/testfile
[root@k8s-node2 ~]#

5.2 基于公钥密钥上传下载

>>> import paramiko
>>> private_key = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
>>>
>>> ssh_client = paramiko.SSHClient()
>>> ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>>
>>> sftp_client = ssh_client.open_sftp()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.9/site-packages/paramiko/client.py", line 558, in open_sftp
    return self._transport.open_sftp_client()
AttributeError: 'NoneType' object has no attribute 'open_sftp_client'
>>>
>>>
>>> ssh_client.connect(hostname='192.168.183.122', port=22, username='test')
>>> sftp_client = ssh_client.open_sftp()
>>> 

(1)上传 sftp_client.put('srcpath', 'destpath')

>>> sftp_client.put('/root/pydir/infofile','/home/test/infofile')
<SFTPAttributes: [ size=190 uid=1000 gid=1000 mode=0o100664 atime=1658570348 mtime=1658570348 ]>
>>> 

(2)下载 sftp_client.get('srcpath', 'destpath')

>>> sftp_client.get('/home/test/infofile','/tmp/infofile_from_node1')

---------------------------------------------------------------------
[root@k8s-node2 pydir]# ls -l /tmp/infofile_from_node1
-rw-r--r-- 1 root root 190 Jul 23 17:59 /tmp/infofile_from_node1
[root@k8s-node2 pydir]#

6.一个ssh和sftp工具脚本

把上述方法写到一个脚本 sshSftpTool.py

#!/usr/bin/env python
import paramiko

def ssh_connect(host, username, password, port = 22):
    try:
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(host, port, username, password)
    except Exception as e:
        print("ssh " + host + " failed!")
        print(e)
        sys.exit()
    return ssh_client

def ssh_exec(ssh_client, cmd):
    stdin, stdout, stderr = ssh_client.exec_command(cmd)
    result = stdout.read()
    print("Exec cmd:\n",cmd,"\nResult:\n",result.decode('utf-8'))

def ssh_close(ssh_client):
    return ssh_client.close()

def sftp_open(ssh_client):
    return ssh_client.open_sftp()

def sftp_put(sftp_client, srcFile, destFile):
    return sftp_client.put(srcFile, destFile)

def sftp_get(sftp_client, srcFile, destFile):
    return sftp_client.get(srcFile, destFile)

def sftp_close(sftp_client):
    return sftp_client.close()

测试如下

>>> from sshSftpTool import *
>>> ssh_cli = ssh_connect('192.168.183.122', 'test', 'test')
>>> ssh_exec(ssh_cli,"cd;touch newfile")
Exec cmd:
 cd;touch newfile
Result:

>>> ssh_exec(ssh_cli,"ls -l newfile")
Exec cmd:
 ls -l newfile
Result:
 -rw-rw-r-- 1 test test 0 Jul 23 04:29 newfile

>>> sftp_cli = sftp_open(ssh_cli)
>>> sftp_cli.get('/home/test/newfile','/tmp/newfile_from_node1')
>>> sftp_cli.close()
>>> ssh_cli.close()
>>>
----------------------------------------------------------------
[test@k8s-node1 ~]$ pwd
/home/test
[test@k8s-node1 ~]$ ll
total 0
-rw-rw-r-- 1 test test 0 Jul 23 01:57 testfile2
[test@k8s-node1 ~]$ 

参考资料:

paramiko模块  https://zhuanlan.zhihu.com/p/112194504
 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值