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测试驱动开发实例
Oct 08 Python
详解字典树Trie结构及其Python代码实现
Jun 03 Python
Python实现ssh批量登录并执行命令
Oct 25 Python
Python模块结构与布局操作方法实例分析
Jul 24 Python
Python实现钉钉发送报警消息的方法
Feb 20 Python
详解python:time模块用法
Mar 25 Python
python实现监控阿里云账户余额功能
Dec 16 Python
python、PyTorch图像读取与numpy转换实例
Jan 13 Python
python3用urllib抓取贴吧邮箱和QQ实例
Mar 10 Python
python 爬取免费简历模板网站的示例
Sep 27 Python
Python - 10行代码集2000张美女图
May 23 Python
手把手教你实现PyTorch的MNIST数据集
Jun 28 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
全国FM电台频率大全 - 23 四川省
2020/03/11 无线电
php url地址栏传中文乱码解决方法集合
2010/06/25 PHP
php实现可以设置中奖概率的抽奖程序代码分享
2014/01/19 PHP
php+html5使用FormData对象提交表单及上传图片的方法
2015/02/11 PHP
Extjs中使用extend(js继承) 的代码
2012/03/15 Javascript
点击弹出层效果&弹出窗口后网页背景变暗效果的实现代码
2014/02/10 Javascript
JavaScript中的包装对象介绍
2015/01/27 Javascript
JS实现兼容性较好的随屏滚动效果
2015/11/09 Javascript
JavaScript实现点击按钮直接打印
2016/01/06 Javascript
微信小程序Redux绑定实例详解
2017/06/07 Javascript
JS鼠标滚动分页效果示例
2017/07/05 Javascript
关于vue的语法规则检测报错问题的解决
2018/05/21 Javascript
Vue实现textarea固定输入行数与添加下划线样式的思路详解
2018/06/28 Javascript
JavaScript实现正则去除a标签并保留内容的方法【测试可用】
2018/07/18 Javascript
node.js的http.createServer过程深入解析
2019/06/06 Javascript
解决jquery validate 验证不通过后验证正确的信息仍残留在label上的方法
2019/08/27 jQuery
Python函数学习笔记
2008/10/07 Python
python创建临时文件夹的方法
2015/07/06 Python
对于Python中RawString的理解介绍
2016/07/07 Python
HTML中使用python屏蔽一些基本功能的方法
2017/07/07 Python
django中的HTML控件及参数传递方法
2018/03/20 Python
pandas使用apply多列生成一列数据的实例
2018/11/28 Python
对python实现合并两个排序链表的方法详解
2019/01/23 Python
Opencv实现抠图背景图替换功能
2019/05/21 Python
python如何使用Redis构建分布式锁
2020/01/16 Python
Python实现定时监测网站运行状态的示例代码
2020/09/30 Python
python 删除系统中的文件(按时间,大小,扩展名)
2020/11/19 Python
Myprotein比利时官方网站:欧洲第一运动营养品牌
2020/10/04 全球购物
数字天堂软件测试面试题
2012/12/23 面试题
出纳的岗位职责
2013/11/09 职场文书
总经理秘书岗位职责
2014/03/17 职场文书
2014年教师学期工作总结
2014/11/08 职场文书
2015年农村党员公开承诺事项
2015/04/28 职场文书
php字符串倒叙
2021/04/01 PHP
Python基础知识学习之类的继承
2021/05/31 Python
MySQL 数据库 增删查改、克隆、外键 等操作
2022/05/11 MySQL