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 相关文章推荐
本地文件上传到七牛云服务器示例(七牛云存储)
Jan 11 Python
python记录程序运行时间的三种方法
Jul 14 Python
python处理“
Jun 10 Python
如何通过python画loss曲线的方法
Jun 26 Python
详解pandas使用drop_duplicates去除DataFrame重复项参数
Aug 01 Python
pygame实现俄罗斯方块游戏(基础篇3)
Oct 29 Python
python 实现生成均匀分布的点
Dec 05 Python
Python GUI库PyQt5图形和特效样式QSS介绍
Feb 25 Python
使用keras内置的模型进行图片预测实例
Jun 17 Python
基于Tensorflow读取MNIST数据集时网络超时的解决方式
Jun 22 Python
Python爬虫模拟登陆哔哩哔哩(bilibili)并突破点选验证码功能
Dec 21 Python
Python多个MP4合成视频的实现方法
Jul 16 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
咖啡的传说和历史
2021/03/03 新手入门
如何使用PHP中的字符串函数
2006/11/24 PHP
php lcg_value与mt_rand生成0~1随机小数的效果对比分析
2017/04/05 PHP
PHP实现按之字形顺序打印二叉树的方法
2018/01/16 PHP
Thinkphp5+uploadify实现的文件上传功能示例
2018/05/26 PHP
PHP实现打包zip并下载功能
2018/06/12 PHP
PHP常见加密函数用法示例【crypt与md5】
2019/01/27 PHP
关于 byval 与 byref 的区别分析总结
2007/10/08 Javascript
js 数组的for循环到底应该怎么写?
2010/05/31 Javascript
jsp网页搜索结果中实现选中一行使其高亮
2014/02/17 Javascript
深入理解JavaScript中的for循环
2017/02/07 Javascript
VueJS如何引入css或者less文件的一些坑
2017/04/25 Javascript
vue-next/runtime-core 源码阅读指南详解
2019/10/25 Javascript
vue2.0 获取从http接口中获取数据,组件开发,路由配置方式
2019/11/04 Javascript
用Golang运行JavaScript的实现示例
2019/11/25 Javascript
H5 js点击按钮复制文本到粘贴板
2020/11/19 Javascript
[43:26]完美世界DOTA2联赛PWL S2 Forest vs Rebirth 第二场 11.20
2020/11/23 DOTA
python在多玩图片上下载妹子图的实现代码
2013/08/13 Python
Python2和Python3的共存和切换使用
2019/04/12 Python
Django如何自定义model创建数据库索引的顺序
2019/06/20 Python
Python中的 is 和 == 以及字符串驻留机制详解
2019/06/28 Python
Pycharm+django2.2+python3.6+MySQL实现简单的考试报名系统
2019/09/05 Python
python-OpenCV 实现将数组转换成灰度图和彩图
2020/01/09 Python
python统计函数库scipy.stats的用法解析
2020/02/25 Python
利用 PyCharm 实现本地代码和远端的实时同步功能
2020/03/23 Python
const char*, char const*, char*const的区别是什么
2014/07/09 面试题
教育学专业实习生的自我鉴定
2013/11/26 职场文书
医院总经理职责
2013/12/26 职场文书
搞笑创意广告语
2014/03/17 职场文书
节水口号标语
2014/06/19 职场文书
关于青春的演讲稿三分钟
2014/08/22 职场文书
学校体育节班级口号
2015/12/25 职场文书
幽默导游词应该怎么写?
2019/08/26 职场文书
Springboot使用Spring Data JPA实现数据库操作
2021/06/30 Java/Android
解决Swagger2返回map复杂结构不能解析的问题
2021/07/02 Java/Android
nginx 添加http_stub_status_module模块
2022/05/25 Servers