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通过websocket与js客户端通信示例分析
Jun 25 Python
用Python生成器实现微线程编程的教程
Apr 13 Python
python字典get()方法用法分析
Apr 17 Python
详解JavaScript编程中的window与window.screen对象
Oct 26 Python
win10下Python3.6安装、配置以及pip安装包教程
Oct 01 Python
python3 拼接字符串的7种方法
Sep 12 Python
Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】
Mar 18 Python
python脚本执行CMD命令并返回结果的例子
Aug 14 Python
python 实现两个线程交替执行
May 02 Python
python中round函数如何使用
Jun 19 Python
Python模拟登录requests.Session应用详解
Nov 17 Python
pytorch 计算Parameter和FLOP的操作
Mar 04 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技术开发技巧分享
2010/03/23 PHP
win7+apache+php+mysql环境配置操作详解
2013/06/10 PHP
PHP has encountered a Stack overflow问题解决方法
2014/11/03 PHP
Thinkphp3.2简单解决多文件上传只上传一张的问题
2017/09/26 PHP
JavaScript创建命名空间(namespace)的最简实现
2007/12/11 Javascript
JavaScript的eval JSON object问题
2009/11/15 Javascript
javascript数字数组去重复项的实现代码
2010/12/30 Javascript
使用js实现数据格式化
2014/12/03 Javascript
JS实现5秒钟自动封锁div层的方法
2015/02/20 Javascript
JavaScript实现的经典文件树菜单效果
2015/09/08 Javascript
Javascript 计算字符串在localStorage中所占字节数
2015/10/21 Javascript
值得分享的JavaScript实现图片轮播组件
2016/11/21 Javascript
基于Bootstrap和jQuery构建前端分页工具实例代码
2016/11/23 Javascript
JQuery.dataTables表格插件添加跳转到指定页
2017/06/09 jQuery
Element-ui tree组件自定义节点使用方法代码详解
2018/09/17 Javascript
JS数组降维的实现Array.prototype.concat.apply([], arr)
2020/04/28 Javascript
JS实现炫酷轮播图
2020/11/15 Javascript
Node.js 中如何收集和解析命令行参数
2021/01/08 Javascript
[03:17]2016完美“圣”典风云人物:冷冷专访
2016/12/08 DOTA
简单分析Python中用fork()函数生成的子进程
2015/05/04 Python
python编程开发之类型转换convert实例分析
2015/11/13 Python
Python中判断输入是否为数字的实现代码
2018/05/26 Python
pip安装py_zipkin时提示的SSL问题对应
2018/12/29 Python
python如何保存文本文件
2020/06/07 Python
浅谈TensorFlow中读取图像数据的三种方式
2020/06/30 Python
Python基于xlutils修改表格内容过程解析
2020/07/28 Python
HTML5新控件之日期和时间选择输入的实现代码
2018/09/13 HTML / CSS
如何用H5实现一个触屏版的轮播器的实例
2017/01/09 HTML / CSS
出纳员岗位职责
2014/03/13 职场文书
毕业生自荐信如何写
2014/03/24 职场文书
计算机系统管理员求职信
2014/06/20 职场文书
2014年导购员工作总结
2014/11/18 职场文书
护理实习生带教计划
2015/01/16 职场文书
大学生自荐信范文
2015/03/05 职场文书
关于清明节的演讲稿2015
2015/03/18 职场文书
材料员岗位职责范本
2015/04/11 职场文书