目录
3.2 基于公钥密钥连接(SSHClient 封装 Transport)
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