Django实现WebSSH操作物理机或虚拟机的方法
我想用它替换掉xshell、crt之类的工具
WebSSH操作物理机或虚拟机
上篇文章给大家介绍详解基于django实现的webssh简单例子,有小伙伴说咖啡哥,我们现在还没有用上Kubernetes,但我想通过浏览器连接我们的物理机和虚拟机该怎么办?
这就比较简单了,既然我们已经实现了浏览器操作Kubernetes的Pod,那么想想操作Pod和物理机虚拟机有什么区别呢?
整个数据流是一点没变:用户打开浏览器--》浏览器发送websocket请求给Django建立长连接--》Django与要操作的服务器建立SSH通道,实时的将收到的用户数据发送给SSH后的主机,并将主机执行的结果数据返回给浏览器
唯一不一样的地方就是Django与要操作的服务器建立SSH通道的方式,在Kubernetes中是通过Kubernetes提供的API建立的Stream流,而操作物理机或者虚拟机的时候我们可以使用Paramiko模块来建立SSH长连接隧道,Paramiko模块建立SSH长连接通道的方法如下:
#实例化SSHClient ssh=paramiko.SSHClient() #当远程服务器没有本地主机的密钥时自动添加到本地,这样不用在建立连接的时候输入yes或no进行确认 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #连接SSH服务器,这里以账号密码的方式进行认证,也可以用key ssh.connect(hostname=host,port=port,username=username,password=password,timeout=8) #打开ssh通道,建立长连接 transport=ssh.get_transport() self.ssh_channel=transport.open_session() #获取ssh通道,并设置term和终端大小 self.ssh_channel.get_pty(term=term,width=cols,height=rows) #激活终端,这样就可以正常登陆了 self.ssh_channel.invoke_shell()
连接建立,可以通过如下方法给SSH通道发送数据
self.ssh_channel.send(data)
当然SSH返回的数据也可以通过如下方法持续的输出给Websocket
whilenotself.ssh_channel.exit_status_ready(): #SSH返回的数据需要转码为utf-8,否则json序列化会失败 data=self.ssh_channel.recv(1024).decode('utf-8','ignore') iflen(data)!=0: message={'flag':'success','message':data} self.websocket.send(json.dumps(message)) else: break
有了这些信息,结合详解基于django实现的webssh简单例子的文章,实现WebSSH浏览器操作物理机或者虚拟机就不算困难了,完整的Consumer代码如下:
importio importjson importparamiko fromthreadingimportThread fromchannels.generic.websocketimportWebsocketConsumer fromcmdb.backends.sshargsimportargs classSSHBridge(object): def__init__(self,websocket): self.websocket=websocket defconnect(self,host,port,username,authtype,password=None,pkey=None,term='xterm',cols=80,rows=24): ssh=paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ifauthtype==2: pkey=paramiko.RSAKey.from_private_key(io.StringIO(pkey)) ssh.connect(username=username,hostname=host,port=port,pkey=pkey,timeout=8) else: ssh.connect(hostname=host,port=port,username=username,password=password,timeout=8) exceptExceptionase: message=json.dumps({'flag':'error','message':str(e)}) self.websocket.send(message) returnFalse #打开一个ssh通道并建立连接 transport=ssh.get_transport() self.ssh_channel=transport.open_session() self.ssh_channel.get_pty(term=term,width=cols,height=rows) self.ssh_channel.invoke_shell() #连接建立一次,之后交互数据不会再进入该方法 foriinrange(2): recv=self.ssh_channel.recv(1024).decode('utf-8','ignore') message=json.dumps({'flag':'success','message':recv}) self.websocket.send(message) defclose(self): try: self.websocket.close() self.ssh_channel.close() exceptBaseExceptionase: pass def_ws_to_ssh(self,data): try: self.ssh_channel.send(data) exceptOSErrorase: self.close() def_ssh_to_ws(self): try: whilenotself.ssh_channel.exit_status_ready(): data=self.ssh_channel.recv(1024).decode('utf-8','ignore') iflen(data)!=0: message={'flag':'success','message':data} self.websocket.send(json.dumps(message)) else: break exceptExceptionase: message={'flag':'error','message':str(e)} self.websocket.send(json.dumps(message)) self.close() defshell(self,data): Thread(target=self._ws_to_ssh,args=(data,)).start() Thread(target=self._ssh_to_ws).start() classSSHConsumer(WebsocketConsumer): defconnect(self): self.pk=self.scope['url_route']['kwargs'].get('id') self.query=self.scope.get('query_string') self.user=self.scope['user'] self.accept() #ssh_connect_args为SSH连接需要的参数 ssh_connect_args=args(self.pk,self.user,self.query) self.ssh=SSHBridge(websocket=self) self.ssh.connect(**ssh_connect_args) defdisconnect(self,close_code): self.ssh.close() defreceive(self,text_data=None): text_data=json.loads(text_data) self.ssh.shell(data=text_data.get('data',''))
动态调整终端窗口大小
看了详解基于django实现的webssh简单例子文章,小伙伴又说了,你这只能在连接建立时确定终端窗口的大小,如果我中途调整了浏览器的大小,显示就乱了,这该怎么办?
不要着急,接下来就让我们看看怎么让终端窗口随着浏览器大小的调整而改变,上边的文章中已经说过,终端窗口的大小需要浏览器和后端返回的Terminal大小保持一致,单单调整页面窗口大小或者后端返回的Terminal窗口大小都是不行的,那么从这两个方向来说明该如何动态调整窗口的大小
首先Paramiko模块建立的SSH通道可以通过resize_pty来动态改变返回Terminal窗口的大小,使用方法如下:
defresize_pty(self,cols,rows): self.ssh_channel.resize_pty(width=cols,height=rows)
然后Django的Channels每次接收到前端发过来的数据时,判断一下窗口是否有变化,如果有变化则调用上边的方法动态改变Terminal输出窗口的大小
我在实现时会给传过来的数据加个flag,如果flag是resize,则调用resize_pty的方法动态调整窗口大小,否则就正常调用执行命令的方法,代码如下:
defreceive(self,text_data=None): text_data=json.loads(text_data) iftext_data.get('flag')=='resize': self.ssh.resize_pty(cols=text_data['cols'],rows=text_data['rows']) else: self.ssh.shell(data=text_data.get('data',''))
后端都搞定了,那么来看看前端如何处理吧
首先有一个terminal_size的方法根据浏览器窗口大小除以每个字符所占用的大小计算出cols和rows的值,无论是xterm.js还是Paramiko都是根据这两个值来调整窗口大小的
functionterminal_size(){ return{ cols:Math.floor($('#terminal').width()/9), rows:Math.floor($(window).height()/17), } }
然后通过$(window).resize()来检测浏览器窗口的变化,一旦发生变化,则发送一个带resize标记的数据给Django,同时传递的数据还有新的cols和rows
//terminalresize $(window).resize(function(){ letcols=terminal_size().cols; letrows=terminal_size().rows; send_data=JSON.stringify({ 'flag':'resize', 'cols':cols, 'rows':rows }); socket.send(send_data); term.resize(cols,rows) })
最后通过term.resize来调整xterm渲染的窗口的大小
这样一个完整的动态调整窗口大小的方案就完成了
演示与源码
我写了个简单的Demo来实现上边的功能,Demo写完发现还挺好用,我就扩展了一下添加了内网的物理机和虚拟机,历史原因,有些是账号密码认证,有些是密钥认证,我都给兼容了一下,最终实现的效果如上图所示
项目里边要记录主机的密码,为了安全这个密码是通过RSA加密存放在数据库的,每次使用的时候进行解密,加解密的实现,可参考这篇文章django开发忘记密码通过邮箱找回功能示例
总结
以上所述是小编给大家介绍的django操作虚拟机和物理机的相关知识,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。