Django实现WebSSH操作物理机或虚拟机的方法


Posted in Python onNovember 06, 2019

我想用它替换掉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

while not self.ssh_channel.exit_status_ready():
 # SSH返回的数据需要转码为utf-8,否则json序列化会失败
 data = self.ssh_channel.recv(1024).decode('utf-8','ignore')
 if len(data) != 0:
 message = {'flag': 'success', 'message': data}
 self.websocket.send(json.dumps(message))
 else:
 break

有了这些信息,结合详解基于django实现的webssh简单例子的文章,实现WebSSH浏览器操作物理机或者虚拟机就不算困难了,完整的Consumer代码如下:

import io
import json
import paramiko
from threading import Thread
from channels.generic.websocket import WebsocketConsumer
from cmdb.backends.sshargs import args
class SSHBridge(object):
 def __init__(self, websocket):
 self.websocket = websocket
 def connect(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:
 if authtype == 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)
 except Exception as e:
 message = json.dumps({'flag': 'error', 'message': str(e)})
 self.websocket.send(message)
 return False
 # 打开一个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()
 # 连接建立一次,之后交互数据不会再进入该方法
 for i in range(2):
 recv = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
 message = json.dumps({'flag': 'success', 'message': recv})
 self.websocket.send(message)
 def close(self):
 try:
 self.websocket.close()
 self.ssh_channel.close()
 except BaseException as e:
 pass
 def _ws_to_ssh(self, data):
 try:
 self.ssh_channel.send(data)
 except OSError as e:
 self.close()
 def _ssh_to_ws(self):
 try:
 while not self.ssh_channel.exit_status_ready():
 data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
 if len(data) != 0:
 message = {'flag': 'success', 'message': data}
 self.websocket.send(json.dumps(message))
 else:
 break
 except Exception as e:
 message = {'flag': 'error', 'message': str(e)}
 self.websocket.send(json.dumps(message))
 self.close()
 def shell(self, data):
 Thread(target=self._ws_to_ssh, args=(data,)).start()
 Thread(target=self._ssh_to_ws).start()
class SSHConsumer(WebsocketConsumer):
 def connect(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)
 def disconnect(self, close_code):
 self.ssh.close()
 def receive(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窗口的大小,使用方法如下:

def resize_pty(self, cols, rows):
 self.ssh_channel.resize_pty(width=cols, height=rows)

然后Django的Channels每次接收到前端发过来的数据时,判断一下窗口是否有变化,如果有变化则调用上边的方法动态改变Terminal输出窗口的大小

我在实现时会给传过来的数据加个flag,如果flag是resize,则调用resize_pty的方法动态调整窗口大小,否则就正常调用执行命令的方法,代码如下:

def receive(self, text_data=None):
 text_data = json.loads(text_data)
 if text_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都是根据这两个值来调整窗口大小的

function terminal_size() {
 return {
 cols: Math.floor($('#terminal').width() / 9),
 rows: Math.floor($(window).height() / 17),
 }
}

然后通过$(window).resize()来检测浏览器窗口的变化,一旦发生变化,则发送一个带resize标记的数据给Django,同时传递的数据还有新的cols和rows

// terminal resize
$(window).resize(function () {
 let cols = terminal_size().cols;
 let rows = terminal_size().rows;
 send_data = JSON.stringify({
 'flag': 'resize',
 'cols': cols,
 'rows': rows
 });
 socket.send(send_data);
 term.resize(cols, rows)
})

最后通过term.resize来调整xterm渲染的窗口的大小

这样一个完整的动态调整窗口大小的方案就完成了

演示与源码

Django实现WebSSH操作物理机或虚拟机的方法

我写了个简单的Demo来实现上边的功能,Demo写完发现还挺好用,我就扩展了一下添加了内网的物理机和虚拟机,历史原因,有些是账号密码认证,有些是密钥认证,我都给兼容了一下,最终实现的效果如上图所示

项目里边要记录主机的密码,为了安全这个密码是通过RSA加密存放在数据库的,每次使用的时候进行解密,加解密的实现,可参考这篇文章django 开发忘记密码通过邮箱找回功能示例

总结

以上所述是小编给大家介绍的django操作虚拟机和物理机的相关知识,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python 网络编程起步(Socket发送消息)
Sep 06 Python
Python列表list数组array用法实例解析
Oct 28 Python
python登录豆瓣并发帖的方法
Jul 08 Python
Python实现霍夫圆和椭圆变换代码详解
Jan 12 Python
Python之pandas读写文件乱码的解决方法
Apr 20 Python
浅谈tensorflow中几个随机函数的用法
Jul 27 Python
Python3中在Anaconda环境下安装basemap包
Oct 21 Python
Keras - GPU ID 和显存占用设定步骤
Jun 22 Python
基于Python中Remove函数的用法讨论
Dec 11 Python
使用tkinter实现三子棋游戏
Feb 25 Python
python实现Thrift服务端的方法
Apr 20 Python
Python中time与datetime模块使用方法详解
Mar 31 Python
django 简单实现登录验证给你
Nov 06 #Python
Python数据可视化:箱线图多种库画法
Nov 06 #Python
使用Python完成15位18位身份证的互转功能
Nov 06 #Python
python3.8 微信发送服务器监控报警消息代码实现
Nov 05 #Python
python SVD压缩图像的实现代码
Nov 05 #Python
Django REST框架创建一个简单的Api实例讲解
Nov 05 #Python
python中for循环变量作用域及用法详解
Nov 05 #Python
You might like
PHP 面向对象 PHP5 中的常量
2010/05/05 PHP
php setcookie(name, value, expires, path, domain, secure) 参数详解
2013/06/28 PHP
JavaScript的常见兼容问题及相关解决方法(chrome/IE/firefox)
2013/12/31 Javascript
简单谈谈jQuery(function(){})与(function(){})(jQuery)
2014/12/19 Javascript
JavaScript实现非常简单实用的下拉菜单效果
2015/08/27 Javascript
JavaScript实现搜索框的自动完成功能(一)
2016/02/25 Javascript
JS使用正则表达式过滤多个词语并替换为相同长度星号的方法
2016/08/03 Javascript
JS 对java返回的json格式的数据处理方法
2016/12/05 Javascript
微信小程序canvas写字板效果及实例
2017/06/15 Javascript
nodeJS(express4.x)+vue(vue-cli)构建前后端分离实例(带跨域)
2017/07/05 NodeJs
原生js jquery ajax请求以及jsonp的调用方法
2017/08/04 jQuery
基于Vue实现支持按周切换的日历
2020/09/24 Javascript
JS实现快速比较两个字符串中包含有相同数字的方法
2017/09/11 Javascript
vue配置多页面的实现方法
2018/05/22 Javascript
微信运维交互机器人的示例代码
2018/11/12 Javascript
iview tabs 顶部导航栏和模块切换栏的示例代码
2019/03/04 Javascript
jQuery实现购物车全功能
2021/01/11 jQuery
[03:01]完美世界DOTA2联赛PWL S2 集锦第二期
2020/12/03 DOTA
Python引用(import)文件夹下的py文件的方法
2014/08/26 Python
django模型层(model)进行建表、查询与删除的基础教程
2017/11/21 Python
谈谈python中GUI的选择
2018/03/01 Python
python增加图像对比度的方法
2019/07/12 Python
在Python中利用pickle保存变量的实例
2019/12/30 Python
新手学python应该下哪个版本
2020/06/11 Python
使用 prometheus python 库编写自定义指标的方法(完整代码)
2020/06/29 Python
Python爬虫之Selenium鼠标事件的实现
2020/12/04 Python
如何撰写一封出色的求职信
2014/04/27 职场文书
党员承诺书格式
2014/05/21 职场文书
反腐倡廉演讲稿
2014/05/22 职场文书
2014年教师节国旗下讲话稿
2014/09/10 职场文书
地震慰问信
2015/02/14 职场文书
收银员岗位职责范本
2015/04/07 职场文书
黄埔军校观后感
2015/06/10 职场文书
2019员工保密协议书(3篇)
2019/09/23 职场文书
《围炉夜话》110句人生箴言,精辟有内涵,引人深思
2019/10/23 职场文书
一条慢SQL语句引发的改造之路
2022/03/16 MySQL