转载至:https://blog.csdn.net/y2701310012/article/details/41855171
paramiko是使用Python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式远程连接服务器。
1. 安装
在安装paramiko前,首先要安装PyCrypto模块。安装脚本为:
-
git
clone https://github.com/dlitz/pycrypto.git
-
cd pycrypto && sudo python setup.py install
-
cd ..
-
git
clone https://github.com/paramiko/paramiko.git
-
cd paramiko && sudo python setup.py install
-
cd ..
-
sudo rm -rf pycrypto paramiko
2. 使用paramiko
paramiko API 中最为基本的类是“paramiko.SSHClient". 它提供了与服务器连接和文件传输的最基本的接口。一个最简单的例子:
-
import paramiko
-
-
ssh = paramiko.SSHClient()
-
ssh.connect(
'127.0.0.1', username =
'ubuntu', password=
'123')
它将创建一个新的SSHClient实例,然后调用“connect()”来连接本地的SSH服务,没有比这更简单的了不是吗?
HOST Keys
SSH认证另一种方法是采用密钥的方式。无论何时你使用ssh来连接一个远程的服务器,host key的信息将被自动存储在家目录下的".ssh/known_hosts"文件中。如果你通过ssh新连接一个host,将会看到以下信息:
-
The authenticity
of host
'localhost (::1)' can't be
-
established.
-
RSA
key fingerprint
is
-
22:fb:
16:
3c:
24:
7f:
60:
99:
4f:f4:
57:d6:d1:
09:
9e:
28.
-
Are you sure you want
to
continue connecting
-
(yes/no)?
敲入“yes”,key的信息将被保存到“known_hosts”文件中。这些密钥很重要,因为它是与主机之间的信任机制。如果key被破坏或更改,那么客户端会拒绝连接并不会通知你,而paramiko也采用相同的规则。如果在“hnown_hosts”中没有保存相关的信息,SSHClient 默认行为是拒绝连接。如果是工作在系统反反复复安装的实验环境中时,这将变得无比的烦人。设置host key的规则调用的方法叫ssh client 实例的"set_missing_host_key_policy()",它设定了你所期望的方法来管理host key.如果你像我一样懒惰,你可以用"paramiko.AutoAddPolicy()"方法来自动接收未知的key:
-
import paramiko
-
-
ssh = paramiko.SSHClient()
-
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
ssh.connect(
'127.0.0.1', username =
'ubuntu', password=
'123')
connect方法
connect
(
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
)
Parameters | hostname (str) – the server to connect to port (int) – the server port to connect to username (str) – the username to authenticate as (defaults to the current local username) password (str) – a password to use for authentication or for unlocking a private key pkey (.PKey) – an optional private key to use for authentication key_filename (str) – the filename, or list of filenames, of optional private key(s) to try for authentication timeout (float) – an optional timeout (in seconds) for the TCP connect allow_agent (bool) – set to False to disable connecting to the SSH agent look_for_keys (bool) – set to False to disable searching for discoverable private key files in ~/.ssh/ compress (bool) – set to True to turn on compression sock (socket) – an open socket or socket-like object (such as a Channel) to use for communication to the target host gss_auth (bool) – True if you want to use GSS-API authentication gss_kex (bool) – Perform GSS-API Key Exchange and user authentication gss_deleg_creds (bool) – Delegate GSS-API client credentials or not gss_host (str) – The targets name in the kerberos database. default: hostname banner_timeout (float) – an optional timeout (in seconds) to wait for the SSH banner to be presented. |
Raises | BadHostKeyException – if the server’s host key could not be verified AuthenticationException – if authentication failed SSHException – if there was any other error connecting or establishing an SSH session socket.error – if a socket error occurred while connecting |
连接到SSH服务并身份认证。host key将根据系统host key(load_system_host_keys())和本地host key(load_host_keys())来进行检查。如果在系统host key和本地host key中都没有查到相关信息,则使用未知host key策略(set_missing_host_key_policy).认证失败抛出SSHException异常。认证优先级是:
- The pkey or key_filename passed in (if any)
- Any key we can find through an SSH agent
- Any “id_rsa”, “id_dsa” or “id_ecdsa” key discoverable in ~/.ssh/
- Plain username/password auth, if a password was given
ssh.connect('10.227.129.234',username='ubuntu', compress = True, key_filename='/home/ubuntu/.ssh/test.pem',password='123')
-
import getpass
-
-
key_filename =
'/home/ubuntu/.ssh/test.pem'
-
try:
-
key = paramiko.RSAKey.from_private_key_file(key_filename)
-
except paramiko.PasswordRequiredException:
-
password = getpass.getpass(
'RSA key password: ')
-
pkey = paramiko.RSAKey.from_private_key_file(path, password)
-
-
ssh = paramiko.SSHClien()
-
ssh.connect(
'10.227.129.234',username=
'ubuntu', compress =
True, pkey= pkey)
这里明确指明pkey是RSA加密方式,如果是DSS加密,则将RSAKey改成DSSKey即可,可通过私钥文件第一行中的说明信息中自动判断是RSA加密还是DSS加密。
-
if isinstance(key_filename,str):
-
key_file=open(key_filename,
'r')
-
key_head=key_file.readline()
-
key_file.seek(
0)
-
if
'DSA'
in key_head:
-
keytype=paramiko.DSSKey
-
elif
'RSA'
in key_head:
-
keytype=paramiko.RSAKey
执行命令
exec_command
(
command,
bufsize=-1,
timeout=None,
get_pty=False
)
Execute a command on the SSH server. A new Channel is opened and the requested command is executed. The command’s input and output streams are returned as Pythonfile-like objects representing stdin, stdout, and stderr.
Parameters | command (str) – the command to execute bufsize (int) – interpreted the same way as by the built-in file()function in Python timeout (int) – set command’s channel timeout. SeeChannel.settimeout.settimeout |
Returns | the stdin, stdout, and stderr of the executing command, as a 3-tuple |
Raises | SSHException:if the server fails to execute the command |
现在既然已经连接好了,那么可以执行一些命令并返回命令执行结果。与其他Unix-like应用程序一样,SSH使用input、output和error来进行输入、输出和错误输出。error将被输出到标准错误输出,output输出到标准输出,如果你要将数据传回应用程序,将数据写到标准输入即可。
-
...
-
>>> ssh.connect(
'127.0.0.1', username=
'jesse',password=
'lol')
-
>>> stdin, stdout, stderr = ssh.exec_command(
"uptime")
-
>>> type(stdin)
-
paramiko.channel.ChannelFile
-
>>> stdout.readlines()
-
[
'13:35 up 11 days, 3:13, 4 users, load averages: 0.14 0.18 0.16\n']
这个例子中,Paramiko打开了一个新的"paramiko.Channel"实例,它代表了与远端机器连接的安全通道,Channel实例表现与python socket实例是一样的。当我们调用"exec_command()"方法时,这个Channel实例便打开,”paramiko.ChannelFile“和”file-like“实例代表了发送到和来自远端机器的数据。
从远端机器传回的ChannelFile,在标准输出和标准错误输出中使用"read()"来不断地显示内容。如果返回过多的数据而填满缓冲区,则会挂起等待程序读取。这种情况下既不是调用”readlines()“也不是"read()".如果你想在内部暂存这些数据,你可以使用”readline“来迭代。
对于系统管理的任务,同样需要对执行命令的输出结果进行分析,得益于python的强大的字符串处理能力,这简直就是小菜一碟不是吗?那就来跑跑命名吧,不过这需要一个password:
stdin, stdout, stderr = ssh.exec_command("sudo dmesg")
哦....,这里使用了sudo 命令。远程主机将在交互模式下要求我输入一个password,不用担心:
-
ssh.connect(
'127.0.0.1', username=
'jesse', password=
'lol')
-
stdin, stdout, stderr = ssh.exec_command(
"sudo fdisk -l")
-
stdin.write(
'lol\n')
-
stdin.flush()
-
data = stdout.read().splitlines()
-
for line
in data:
-
if line:
-
print line
-
#!/usr/bin/python
-
-
import paramiko
-
import cmd
-
-
class RunCommand(cmd.Cmd):
-
"""add_host
-
Simple shell to run a command on the host """
-
-
prompt =
'ssh > '
-
-
def __init__(self):
-
cmd.Cmd.__init__(self)
-
self.hosts = []
-
self.connections = []
-
-
def do_add_host(self, args):
-
"""Add the host to the host list"""
-
if args:
-
self.hosts.append(args.split(
','))
-
else:
-
print
"usage: host "
-
-
def do_connect(self, args):
-
"""run
-
Connect to all hosts in the hosts list"""
-
-
for host
in self.hosts:
-
client = paramiko.SSHClient()
-
client.set_missing_host_key_policy(
-
paramiko.AutoAddPolicy())
-
client.connect(host[
0],
-
username=host[
1],
-
password=host[
2])
-
self.connections.append(client)
-
-
def do_run(self, command):
-
"""Execute this command on all hosts in the list"""
-
if command:
-
for host, conn
in zip(self.hosts, self.connections):
-
stdin, stdout, stderr = conn.exec_command(command)
-
stdin.close()
-
for line
in stdout.read().splitlines():
-
print
'host: %s: %s' % (host[
0], line)
-
else:
-
print
"usage: run "
-
-
def do_close(self, args):
-
for conn
in self.connections:
-
conn.close()
-
-
if __name__ ==
'__main__':
-
RunCommand().cmdloop()
输出:
-
ssh >
add_host 127
.0
.0
.1,
jesse,
lol
-
ssh >
connect
-
ssh >
run
uptime
-
host: 127
.0
.0
.1: 14
:49
up 11
days, 4
:27, 8
users,
-
load
averages: 0
.36 0
.25 0
.19
-
ssh >
close
这个例子仅仅是演示了伪交互式shell的概念,如果要使用它还有很多地方有待完善:更好的打印多行stdout输出,处理标准错误,添加一个结束方法,线程地处理返回的命令/数据等等。
和所有的shell一样,当需要可视化数据时,处理上限是是限制。如pssh、OSH、Fabric等工具,管理返回的数据方法都不尽相同,他们都有不同的方法来聚合来自不同主机的输出。
交互式
invoke_shell
(
term='vt100',
width=80,
height=24,
width_pixels=0,
height_pixels=0
)
Start an interactive shell session on the SSH server. A new Channel is opened and connected to a pseudo-terminal using the requested terminal type and size.
Parameters | term (str) – the terminal type to emulate (for example, "vt100") width (int) – the width (in characters) of the terminal window height (int) – the height (in characters) of the terminal window width_pixels (int) – the width (in pixels) of the terminal window height_pixels (int) – the height (in pixels) of the terminal window |
Reuturns | a new Channel connected to the remote shell |
Raises | SSHException:if the server fails to invoke a shell |
以上都是通过paramiko模块来远程操作服务器,如果想通过paramiko模块直接用ssh协议登录到远端机器上怎么办?同样:
-
import paramiko
-
import interactive
-
-
# log
-
paramiko.util.log_to_file(
'/tmp/test')
-
-
#create ssh connection
-
ssh=paramiko.SSHClient()
-
ssh.load_system_host_keys()
-
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
ssh.connect(
'10.1.6.190',port=
22,username=
'root',password=
'xxxxxx',compress=
True)
-
-
#create interactive shell connection
-
channel=ssh.invoke_shell()
-
-
#creat interactive pip
-
interactive.interactive_shell(channel)
-
-
#close connection
-
channel.close()
-
ssh.close()
文件传输
Paramiko的文件操作时通过SFTP来实现,就像ssh客户端命令执行一样。首先就像先前一样,创建一个paramiko.SSHClient实例:
-
import paramiko
-
ssh = paramiko.SSHClient()
-
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
ssh.connect(
'127.0.0.1', username=
'jesse', password=
'lol')
这次,我们创建了与远端机器的连接之后调用"open_sftp()"命令,"open_sftp()"返回一个"paramiko.SFTPClient"的客户端实例,支持所有sftp操作(stat, put, get, mkdir, listdir, remove, rename, symlink, unlink etc.可以用dir(ftp)来查看)。 在这个例子中,我们使用"get"操作来下载远端文件”remotefile.py“到本地系统中,并重命名为"localfaile.py"。
-
ftp = ssh.open_sftp()
-
ftp.get(
'remotefile.py',
'localfile.py')
-
ftp.close()
写入一个文件到远端的主机中也采用同样的方法,只需将local和remote参数对调即可:
-
ftp = ssh.open_sftp()
-
ftp.put(
'localfile.py',
'remotefile.py')
-
ftp.close()
使用paramiko提供的sftp客户端的一个优点是对诸如 stat/chmod/chown等文件属性操作的支持。你可能会很轻松地写出如"glob.glob()"这样的函数来传输某个远端文件夹下的特定文件名模式的文件,你也可以基于文件的权限、大小等搜索文件。
然而,有一点必须要注意:相比scp(secure copy)来说,sftp作为一个协议是有一些限制的。当从远端机器上下载文件时,scp可以使用Unix通配符,而sftp需要完整的文件路径,例如:
ftp.get("*.py",".")
我们原本期望的是:下载所有名称为.py结束的文件到本地机器,但是sftp却不识别此方案:
-
>>> ftp.get(
"./*.py",
'.')
-
Traceback (most recent call last):
-
File
"", line
1,
in
-
File
"/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
-
line
567,
in get
-
fr = self.file(remotepath,
'rb')
-
File
"/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
-
line
238,
in open
-
t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
-
File
"/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
-
line
589,
in _request
-
return self._read_response(num)
-
File
"/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
-
line
636,
in _read_response
-
self._convert_status(msg)
-
File
"/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
-
line
662,
in _convert_status
-
raise IOError(errno.ENOENT, text)
-
IOError: [Errno
2] No such file
需要注意的是,sftp的put和get函数都只支持单个文件,如果你需要传输整个目录的话,需要手动使用sftp.mkdir建立一个目录,然后自己遍历整个文件夹,并对每个文件使用put或get函数, pysftp则实现了上传下载文件夹。下面是来自 stackoverflow中对paramiko上传下载文件夹问题的一个回复,他用一个SSHSession类做了一个封装:
-
import paramiko
-
import socket
-
import os
-
from stat
import S_ISDIR
-
-
class SSHSession(object):
-
# Usage:
-
# Detects DSA or RSA from key_file, either as a string filename or a
-
# file object. Password auth is possible, but I will judge you for
-
# using it. So:
-
# ssh=SSHSession('targetserver.com','root',key_file=open('mykey.pem','r'))
-
# ssh=SSHSession('targetserver.com','root',key_file='/home/me/mykey.pem')
-
# ssh=SSHSession('targetserver.com','root','mypassword')
-
# ssh.put('filename','/remote/file/destination/path')
-
# ssh.put_all('/path/to/local/source/dir','/path/to/remote/destination')
-
# ssh.get_all('/path/to/remote/source/dir','/path/to/local/destination')
-
# ssh.command('echo "Command to execute"')
-
-
def __init__(self,hostname,username='root',key_file=None,password=None):
-
#
-
# Accepts a file-like object (anything with a readlines() function)
-
# in either dss_key or rsa_key with a private key. Since I don't
-
# ever intend to leave a server open to a password auth.
-
#
-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
self.sock.connect((hostname,
22))
-
self.t = paramiko.Transport(self.sock)
-
self.t.start_client()
-
keys = paramiko.util.load_host_keys(os.path.expanduser(
'~/.ssh/known_hosts'))
-
key = self.t.get_remote_server_key()
-
# supposed to check for key in keys, but I don't much care right now to find the right notation
-
if key_file
is
not
None:
-
if isinstance(key,str):
-
key_file=open(key,
'r')
-
key_head=key_file.readline()
-
key_file.seek(
0)
-
if
'DSA'
in key_head:
-
keytype=paramiko.DSSKey
-
elif
'RSA'
in key_head:
-
keytype=paramiko.RSAKey
-
else:
-
raise Exception(
"Can't identify key type")
-
pkey=keytype.from_private_key(key_file)
-
self.t.auth_publickey(username, pkey)
-
else:
-
if password
is
not
None:
-
self.t.auth_password(username,password,fallback=
False)
-
else:
raise Exception(
'Must supply either key_file or password')
-
self.sftp=paramiko.SFTPClient.from_transport(self.t)
-
-
def command(self,cmd):
-
# Breaks the command by lines, sends and receives
-
# each line and its output separately
-
#
-
# Returns the server response text as a string
-
-
chan = self.t.open_session()
-
chan.get_pty()
-
chan.invoke_shell()
-
chan.settimeout(
20.0)
-
ret=
''
-
try:
-
ret+=chan.recv(
1024)
-
except:
-
chan.send(
'\n')
-
ret+=chan.recv(
1024)
-
for line
in cmd.split(
'\n'):
-
chan.send(line.strip() +
'\n')
-
ret+=chan.recv(
1024)
-
return ret
-
-
def put(self,localfile,remotefile):
-
# Copy localfile to remotefile, overwriting or creating as needed.
-
self.sftp.put(localfile,remotefile)
-
-
def put_all(self,localpath,remotepath):
-
# recursively upload a full directory
-
os.chdir(os.path.split(localpath)[
0])
-
parent=os.path.split(localpath)[
1]
-
for walker
in os.walk(parent):
-
try:
-
self.sftp.mkdir(os.path.join(remotepath,walker[
0]))
-
except:
-
pass
-
for file
in walker[
2]:
-
self.put(os.path.join(walker[
0],file),os.path.join(remotepath,walker[
0],file))
-
-
def get(self,remotefile,localfile):
-
# Copy remotefile to localfile, overwriting or creating as needed.
-
self.sftp.get(remotefile,localfile)
-
-
def sftp_walk(self,remotepath):
-
# Kindof a stripped down version of os.walk, implemented for
-
# sftp. Tried running it flat without the yields, but it really
-
# chokes on big directories.
-
path=remotepath
-
files=[]
-
folders=[]
-
for f
in self.sftp.listdir_attr(remotepath):
-
if S_ISDIR(f.st_mode):
-
folders.append(f.filename)
-
else:
-
files.append(f.filename)
-
print (path,folders,files)
-
yield path,folders,files
-
for folder
in folders:
-
new_path=os.path.join(remotepath,folder)
-
for x
in self.sftp_walk(new_path):
-
yield x
-
-
def get_all(self,remotepath,localpath):
-
# recursively download a full directory
-
# Harder than it sounded at first, since paramiko won't walk
-
#
-
# For the record, something like this would gennerally be faster:
-
# ssh user@host 'tar -cz /source/folder' | tar -xz
-
-
self.sftp.chdir(os.path.split(remotepath)[
0])
-
parent=os.path.split(remotepath)[
1]
-
try:
-
os.mkdir(localpath)
-
except:
-
pass
-
for walker
in self.sftp_walk(parent):
-
try:
-
os.mkdir(os.path.join(localpath,walker[
0]))
-
except:
-
pass
-
for file
in walker[
2]:
-
self.get(os.path.join(walker[
0],file),os.path.join(localpath,walker[
0],file))
-
def write_command(self,text,remotefile):
-
# Writes text to remotefile, and makes remotefile executable.
-
# This is perhaps a bit niche, but I was thinking I needed it.
-
# For the record, I was incorrect.
-
self.sftp.open(remotefile,
'w').write(text)
-
self.sftp.chmod(remotefile,
755)