python运维之paramiko

转载至:https://blog.csdn.net/y2701310012/article/details/41855171

paramiko是使用Python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式远程连接服务器。

1. 安装

在安装paramiko前,首先要安装PyCrypto模块。安装脚本为:

  
  
  1. git clone https://github.com/dlitz/pycrypto.git
  2. cd pycrypto && sudo python setup.py install
  3. cd ..
  4. git clone https://github.com/paramiko/paramiko.git
  5. cd paramiko && sudo python setup.py install
  6. cd ..
  7. sudo rm -rf pycrypto paramiko


2. 使用paramiko

paramiko API 中最为基本的类是“paramiko.SSHClient". 它提供了与服务器连接和文件传输的最基本的接口。一个最简单的例子:

  
  
  1. import paramiko
  2. ssh = paramiko.SSHClient()
  3. ssh.connect( '127.0.0.1', username = 'ubuntu', password= '123')
它将创建一个新的SSHClient实例,然后调用“connect()”来连接本地的SSH服务,没有比这更简单的了不是吗?

HOST Keys

SSH认证另一种方法是采用密钥的方式。无论何时你使用ssh来连接一个远程的服务器,host key的信息将被自动存储在家目录下的".ssh/known_hosts"文件中。如果你通过ssh新连接一个host,将会看到以下信息:


  
  
  1. The authenticity of host 'localhost (::1)' can't be
  2. established.
  3. RSA key fingerprint is
  4. 22:fb: 16: 3c: 24: 7f: 60: 99: 4f:f4: 57:d6:d1: 09: 9e: 28.
  5. Are you sure you want to continue connecting
  6. (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:

  
  
  1. import paramiko
  2. ssh = paramiko.SSHClient()
  3. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  4. 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 )
Parametershostname (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.
RaisesBadHostKeyException – 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
key_filename和pkey只要一个就行。如果私钥需要密码来解锁,则要指明password参数,如:

ssh.connect('10.227.129.234',username='ubuntu', compress = True, key_filename='/home/ubuntu/.ssh/test.pem',password='123')
   
   

如果想 隐藏password,每次必须交互输入该怎么办呢?可以看看官方给的demo里面的实例中getpass模块来隐藏输入密码:

   
   
  1. import getpass
  2. key_filename = '/home/ubuntu/.ssh/test.pem'
  3. try:
  4. key = paramiko.RSAKey.from_private_key_file(key_filename)
  5. except paramiko.PasswordRequiredException:
  6. password = getpass.getpass( 'RSA key password: ')
  7. pkey = paramiko.RSAKey.from_private_key_file(path, password)
  8. ssh = paramiko.SSHClien()
  9. ssh.connect( '10.227.129.234',username= 'ubuntu', compress = True, pkey= pkey)
这里明确指明pkey是RSA加密方式,如果是DSS加密,则将RSAKey改成DSSKey即可,可通过私钥文件第一行中的说明信息中自动判断是RSA加密还是DSS加密。

   
   
  1. if isinstance(key_filename,str):
  2. key_file=open(key_filename, 'r')
  3. key_head=key_file.readline()
  4. key_file.seek( 0)
  5. if 'DSA' in key_head:
  6. keytype=paramiko.DSSKey
  7. elif 'RSA' in key_head:
  8. keytype=paramiko.RSAKey

执行命令

exec_command ( commandbufsize=-1timeout=Noneget_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.

Parameterscommand (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
Returnsthe stdin, stdout, and stderr of the executing command, as a 3-tuple
RaisesSSHException:if the server fails to execute the command
现在既然已经连接好了,那么可以执行一些命令并返回命令执行结果。与其他Unix-like应用程序一样,SSH使用input、output和error来进行输入、输出和错误输出。error将被输出到标准错误输出,output输出到标准输出,如果你要将数据传回应用程序,将数据写到标准输入即可。


  
  
  1. ...
  2. >>> ssh.connect( '127.0.0.1', username= 'jesse',password= 'lol')
  3. >>> stdin, stdout, stderr = ssh.exec_command( "uptime")
  4. >>> type(stdin)
  5. paramiko.channel.ChannelFile
  6. >>> stdout.readlines()
  7. [ '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,不用担心:


  
  
  1. ssh.connect( '127.0.0.1', username= 'jesse', password= 'lol')
  2. stdin, stdout, stderr = ssh.exec_command( "sudo fdisk -l")
  3. stdin.write( 'lol\n')
  4. stdin.flush()
  5. data = stdout.read().splitlines()
  6. for line in data:
  7. if line:
  8. print line

看到了吧?我登陆到远程机器,执行了sudo命令。这里关键一点是,当需要输入密码时,我把password写到了stdin中。也许你会感到困惑,而这样是创建你自己交互shell的简单的基本的做法。你可能想用传统的Python cmd模块来执行admin命令来管理机器,在paramiko中,这很简单。在下面的例子中例句了一个简单的方法来实现:我们封装了Paramiko 各个操作到 RunCommand方法中,允许用户随心所欲添加主机(hosts),调用connect并且执行命令。


  
  
  1. #!/usr/bin/python
  2. import paramiko
  3. import cmd
  4. class RunCommand(cmd.Cmd):
  5. """add_host
  6. Simple shell to run a command on the host """
  7. prompt = 'ssh > '
  8. def __init__(self):
  9. cmd.Cmd.__init__(self)
  10. self.hosts = []
  11. self.connections = []
  12. def do_add_host(self, args):
  13. """Add the host to the host list"""
  14. if args:
  15. self.hosts.append(args.split( ','))
  16. else:
  17. print "usage: host "
  18. def do_connect(self, args):
  19. """run
  20. Connect to all hosts in the hosts list"""
  21. for host in self.hosts:
  22. client = paramiko.SSHClient()
  23. client.set_missing_host_key_policy(
  24. paramiko.AutoAddPolicy())
  25. client.connect(host[ 0],
  26. username=host[ 1],
  27. password=host[ 2])
  28. self.connections.append(client)
  29. def do_run(self, command):
  30. """Execute this command on all hosts in the list"""
  31. if command:
  32. for host, conn in zip(self.hosts, self.connections):
  33. stdin, stdout, stderr = conn.exec_command(command)
  34. stdin.close()
  35. for line in stdout.read().splitlines():
  36. print 'host: %s: %s' % (host[ 0], line)
  37. else:
  38. print "usage: run "
  39. def do_close(self, args):
  40. for conn in self.connections:
  41. conn.close()
  42. if __name__ == '__main__':
  43. RunCommand().cmdloop()
输出:

  
  
  1. ssh > add_host 127 .0 .0 .1, jesse, lol
  2. ssh > connect
  3. ssh > run uptime
  4. host: 127 .0 .0 .1: 14 :49 up 11 days, 4 :27, 8 users,
  5. load averages: 0 .36 0 .25 0 .19
  6. ssh > close
这个例子仅仅是演示了伪交互式shell的概念,如果要使用它还有很多地方有待完善:更好的打印多行stdout输出,处理标准错误,添加一个结束方法,线程地处理返回的命令/数据等等。
和所有的shell一样,当需要可视化数据时,处理上限是是限制。如pssh、OSH、Fabric等工具,管理返回的数据方法都不尽相同,他们都有不同的方法来聚合来自不同主机的输出。

交互式

invoke_shell ( term='vt100'width=80height=24width_pixels=0height_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.

Parametersterm (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
Reuturnsa new Channel connected to the remote shell
RaisesSSHException:if the server fails to invoke a shell
以上都是通过paramiko模块来远程操作服务器,如果想通过paramiko模块直接用ssh协议登录到远端机器上怎么办?同样:

  
  
  1. import paramiko
  2. import interactive
  3. # log
  4. paramiko.util.log_to_file( '/tmp/test')
  5. #create ssh connection
  6. ssh=paramiko.SSHClient()
  7. ssh.load_system_host_keys()
  8. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  9. ssh.connect( '10.1.6.190',port= 22,username= 'root',password= 'xxxxxx',compress= True)
  10. #create interactive shell connection
  11. channel=ssh.invoke_shell()
  12. #creat interactive pip
  13. interactive.interactive_shell(channel)
  14. #close connection
  15. channel.close()
  16. ssh.close()

文件传输

Paramiko的文件操作时通过SFTP来实现,就像ssh客户端命令执行一样。首先就像先前一样,创建一个paramiko.SSHClient实例:

   
   
  1. import paramiko
  2. ssh = paramiko.SSHClient()
  3. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  4. 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"。

   
   
  1. ftp = ssh.open_sftp()
  2. ftp.get( 'remotefile.py', 'localfile.py')
  3. ftp.close()
写入一个文件到远端的主机中也采用同样的方法,只需将local和remote参数对调即可:

   
   
  1. ftp = ssh.open_sftp()
  2. ftp.put( 'localfile.py', 'remotefile.py')
  3. ftp.close()
使用paramiko提供的sftp客户端的一个优点是对诸如 stat/chmod/chown等文件属性操作的支持。你可能会很轻松地写出如"glob.glob()"这样的函数来传输某个远端文件夹下的特定文件名模式的文件,你也可以基于文件的权限、大小等搜索文件。

然而,有一点必须要注意:相比scp(secure copy)来说,sftp作为一个协议是有一些限制的。当从远端机器上下载文件时,scp可以使用Unix通配符,而sftp需要完整的文件路径,例如:
ftp.get("*.py",".")
   
   
我们原本期望的是:下载所有名称为.py结束的文件到本地机器,但是sftp却不识别此方案:

   
   
  1. >>> ftp.get( "./*.py", '.')
  2. Traceback (most recent call last):
  3. File "", line 1, in
  4. File "/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
  5. line 567, in get
  6. fr = self.file(remotepath, 'rb')
  7. File "/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
  8. line 238, in open
  9. t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
  10. File "/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
  11. line 589, in _request
  12. return self._read_response(num)
  13. File "/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
  14. line 636, in _read_response
  15. self._convert_status(msg)
  16. File "/Library/Python/2.5/site-packages/paramiko/sftp_client.py",
  17. line 662, in _convert_status
  18. raise IOError(errno.ENOENT, text)
  19. IOError: [Errno 2] No such file

需要注意的是,sftp的put和get函数都只支持单个文件,如果你需要传输整个目录的话,需要手动使用sftp.mkdir建立一个目录,然后自己遍历整个文件夹,并对每个文件使用put或get函数, pysftp则实现了上传下载文件夹。下面是来自 stackoverflow中对paramiko上传下载文件夹问题的一个回复,他用一个SSHSession类做了一个封装:

   
   
  1. import paramiko
  2. import socket
  3. import os
  4. from stat import S_ISDIR
  5. class SSHSession(object):
  6. # Usage:
  7. # Detects DSA or RSA from key_file, either as a string filename or a
  8. # file object. Password auth is possible, but I will judge you for
  9. # using it. So:
  10. # ssh=SSHSession('targetserver.com','root',key_file=open('mykey.pem','r'))
  11. # ssh=SSHSession('targetserver.com','root',key_file='/home/me/mykey.pem')
  12. # ssh=SSHSession('targetserver.com','root','mypassword')
  13. # ssh.put('filename','/remote/file/destination/path')
  14. # ssh.put_all('/path/to/local/source/dir','/path/to/remote/destination')
  15. # ssh.get_all('/path/to/remote/source/dir','/path/to/local/destination')
  16. # ssh.command('echo "Command to execute"')
  17. def __init__(self,hostname,username='root',key_file=None,password=None):
  18. #
  19. # Accepts a file-like object (anything with a readlines() function)
  20. # in either dss_key or rsa_key with a private key. Since I don't
  21. # ever intend to leave a server open to a password auth.
  22. #
  23. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  24. self.sock.connect((hostname, 22))
  25. self.t = paramiko.Transport(self.sock)
  26. self.t.start_client()
  27. keys = paramiko.util.load_host_keys(os.path.expanduser( '~/.ssh/known_hosts'))
  28. key = self.t.get_remote_server_key()
  29. # supposed to check for key in keys, but I don't much care right now to find the right notation
  30. if key_file is not None:
  31. if isinstance(key,str):
  32. key_file=open(key, 'r')
  33. key_head=key_file.readline()
  34. key_file.seek( 0)
  35. if 'DSA' in key_head:
  36. keytype=paramiko.DSSKey
  37. elif 'RSA' in key_head:
  38. keytype=paramiko.RSAKey
  39. else:
  40. raise Exception( "Can't identify key type")
  41. pkey=keytype.from_private_key(key_file)
  42. self.t.auth_publickey(username, pkey)
  43. else:
  44. if password is not None:
  45. self.t.auth_password(username,password,fallback= False)
  46. else: raise Exception( 'Must supply either key_file or password')
  47. self.sftp=paramiko.SFTPClient.from_transport(self.t)
  48. def command(self,cmd):
  49. # Breaks the command by lines, sends and receives
  50. # each line and its output separately
  51. #
  52. # Returns the server response text as a string
  53. chan = self.t.open_session()
  54. chan.get_pty()
  55. chan.invoke_shell()
  56. chan.settimeout( 20.0)
  57. ret= ''
  58. try:
  59. ret+=chan.recv( 1024)
  60. except:
  61. chan.send( '\n')
  62. ret+=chan.recv( 1024)
  63. for line in cmd.split( '\n'):
  64. chan.send(line.strip() + '\n')
  65. ret+=chan.recv( 1024)
  66. return ret
  67. def put(self,localfile,remotefile):
  68. # Copy localfile to remotefile, overwriting or creating as needed.
  69. self.sftp.put(localfile,remotefile)
  70. def put_all(self,localpath,remotepath):
  71. # recursively upload a full directory
  72. os.chdir(os.path.split(localpath)[ 0])
  73. parent=os.path.split(localpath)[ 1]
  74. for walker in os.walk(parent):
  75. try:
  76. self.sftp.mkdir(os.path.join(remotepath,walker[ 0]))
  77. except:
  78. pass
  79. for file in walker[ 2]:
  80. self.put(os.path.join(walker[ 0],file),os.path.join(remotepath,walker[ 0],file))
  81. def get(self,remotefile,localfile):
  82. # Copy remotefile to localfile, overwriting or creating as needed.
  83. self.sftp.get(remotefile,localfile)
  84. def sftp_walk(self,remotepath):
  85. # Kindof a stripped down version of os.walk, implemented for
  86. # sftp. Tried running it flat without the yields, but it really
  87. # chokes on big directories.
  88. path=remotepath
  89. files=[]
  90. folders=[]
  91. for f in self.sftp.listdir_attr(remotepath):
  92. if S_ISDIR(f.st_mode):
  93. folders.append(f.filename)
  94. else:
  95. files.append(f.filename)
  96. print (path,folders,files)
  97. yield path,folders,files
  98. for folder in folders:
  99. new_path=os.path.join(remotepath,folder)
  100. for x in self.sftp_walk(new_path):
  101. yield x
  102. def get_all(self,remotepath,localpath):
  103. # recursively download a full directory
  104. # Harder than it sounded at first, since paramiko won't walk
  105. #
  106. # For the record, something like this would gennerally be faster:
  107. # ssh user@host 'tar -cz /source/folder' | tar -xz
  108. self.sftp.chdir(os.path.split(remotepath)[ 0])
  109. parent=os.path.split(remotepath)[ 1]
  110. try:
  111. os.mkdir(localpath)
  112. except:
  113. pass
  114. for walker in self.sftp_walk(parent):
  115. try:
  116. os.mkdir(os.path.join(localpath,walker[ 0]))
  117. except:
  118. pass
  119. for file in walker[ 2]:
  120. self.get(os.path.join(walker[ 0],file),os.path.join(localpath,walker[ 0],file))
  121. def write_command(self,text,remotefile):
  122. # Writes text to remotefile, and makes remotefile executable.
  123. # This is perhaps a bit niche, but I was thinking I needed it.
  124. # For the record, I was incorrect.
  125. self.sftp.open(remotefile, 'w').write(text)
  126. self.sftp.chmod(remotefile, 755)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值