Django使用Channels实现WebSSH网页终端,实现SSH堡垒机雏形
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django使用Channels实现WebSSH网页终端,实现SSH堡垒机雏形相关的知识,希望对你有一定的参考价值。
参考技术A更多内容请点击 我的博客 查看,欢迎来访。
本教程基于《Django使用Channels实现WebSocket消息通知功能》
xterm.js : 前端模拟 shell 终端的一个库,当用户每输入一个键,就向后端发送该数据
paramiko : Python 下对 ssh2 封装的一个库,可以使用他来远程连接主机
创建app。名为 webssh
将应用添加到 settings.py
修改应用下的 apps.py
修改应用下的 __init__.py
访问 http://127.0.0.1/webssh/ 可以连接到主页
apps/webssh/consumers.py
apps/webssh/routing.py
合并多个应用的url
遇到的问题:用协作连接时,用户加入到一个通道组,往这个通道组发送命令,这个通道所有用户都能收到,来实现协作的功能,但是从 self.ssh_channel 接收返回的数据,可能会存在和预想的不同,特别是 top 命令。示例如下,不知道怎么解决了!!!
参考链接: https://github.com/huyuan1999/django-webssh
https://www.cnblogs.com/52op/articles/9327733.html 【gevent库】
代码发布项目——django实现websocket(使用channels)基于channels实现群聊功能gojs插件paramiko模块
一、django实现websocket
django默认是不支持websocket,只支持http协议
在django中如果想要基于websocket开发项目 你需要安装模块:channles pip3 install channels==2.3 版本不要使用最新的,如果安装最新的可能会自动把你的django版本升级到最新版 对应的解释器环境建议使用3.6(官网的说法:3.5可能有问题,3.7可能也有问题...具体原因没有给解释) channels模块内部已经帮我们封装好了 握手/加密/解密 面试点: 注意:不是所有的服务端都支持websocket django -默认不支持 -但是有第三方的模块:channles来实现 flask -默认不支持 -但是也有第三方的模块:geventwebsocket tronado -默认支持
创建django项目测试channels模块
1.需要在配置文件中注册channels应用
INSTALLED_APPS = [ # 1.需要先注册channels ‘channels‘ ]
2.还需要在配置文件配置以下参数
# ‘与项目名同名的文件夹名.routing文件名.文件内的变量名application‘
ASGI_APPLICATION = ‘channels_demo.routing.application‘
3.创建routing.py文件(和settings.py同级),文件内容如下
from channels.routing import ProtocolTypeRouter,URLRouter application = ProtocolTypeRouter({ ‘websocket‘:URLRouter([ # 路由与视图函数对应关系 ]) })
注意配置完成后,django就会即支持http协议也支持websocket协议
强调
正常的http协议还是按照之前的写法 在urls中写路由与视图函数对应关系,正常的访问页面还是需要在urls中设置路径
而针对websocket协议则在当前文件内书写路由与视图函数对应关系,这个设置的路径在前端验证是否支持websocket使用
在routing文件中书写路由与视图函数对应关系
application = ProtocolTypeRouter({ ‘websocket‘:URLRouter([ url(r‘^chat/‘,consumers.ChatConsumer) ]) })
在项目根目录创建consumer.py文件用来专门处理websocket请求的视图函数
from channels.generic.websocket import WebsocketConsumer class ChatConsumer(WebsocketConsumer): def websocket_connect(self, message): """ 客户端发来链接请求之后就会自动触发 """ def websocket_receive(self, message): """ 客户端向服务端发送消息就会自动触发
message包括前端发送的消息 """ def websocket_disconnect(self, message): """ 客户端主动断开链接之后自动触发 """
二、基于channels实现多人聊天室
http协议
index index函数
浏览器发送请求即可访问,设置url路径访问
websocket协议
chat ChatConsumer类 内部有三个方法
通过前端创建new WebScoket对象才能访问
后端
# 该文件内是专门用来写处理websocket请求的视图函数 from channels.generic.websocket import WebsocketConsumer from channels.exceptions import StopConsumer consumer_object_list = [] class ChatConsumer(WebsocketConsumer): def websocket_connect(self, message): """ 客户端发来链接请求之后就会自动触发 """ self.accept() # 向服务端发送加密字符串,在前一章中讲的shankhands中的加密 # self就是每一个客户端对象 # 链接成功 我就将当前对象放入全局的列表中 consumer_object_list.append(self) def websocket_receive(self, message): """ 客户端向服务端发送消息就会自动触发 """ print(message) #接收客户端传递的信息 # 给列表中所有的对象都发送消息,给客户端返回信息用send,传递信息用text_data for obj in consumer_object_list: obj.send(text_data=message.get(‘text‘)) def websocket_disconnect(self, message): """ 客户端主动断开链接之后自动触发 """ print(‘断开链接了‘) # 服务端断开链接 就去列表中删除对应的客户端对象 consumer_object_list.remove(self) raise StopConsumer #停止消费
前端
<script> // 验证服务端是否支持websocket var ws = new WebSocket(‘ws://127.0.0.1:8000/chat/‘); //1.握手环节,连接成功自动触发,访问页面成功就会触发 ws.onopen=function(){ alert(‘验证成功‘) }; // 2.客户端给服务端发送消息 function sendMsg() { ws.send($(‘#d1‘).val()) // 将用户输入的内容发送给后端 } // 3.接收服务端消息 会自动触发 ws.onmessage = function (event) { // event是数据对象 真正的数据在data属性内 {#alert(event.data) // 服务端返回的真实数据#} // 将消息渲染到html页面上 var pEle = $(‘<p>‘); pEle.text(event.data); $(‘#content‘).append(pEle) }; // 4.断开链接 function closeLink() { ws.close() } </script>
总结:
后端三个方法
websocket_connect
websocket_receive
websocket_disconnect
前端四个方法
var ws = new WebSocket(‘ws://127.0.0.1:8000/chat/‘); ws.onopen ws.send() ws.onmessage ws.close()
要想实现完美的群聊功能需要借助于channel-layers
三、gojs插件
是一个前端组件,可以动态的创建各种流程图、图表...
基本使用
1.先用div占据页面一块内容,然后在该div内部操作图表
<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div> <script src="go.js"></script> <script> var $ = go.GraphObject.make; // 第一步:创建图表 var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图 // 第二步:创建一个节点,内容为jason var node = $(go.Node, $(go.TextBlock, {text: "jason"})); // 第三步:将节点添加到图表中 myDiagram.add(node) </script>
-
-
Shap 图形
-
Node 节点(将文本与图形结合)
-
Link 箭头
TextBlock
<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div> <script src="go.js"></script> <script> var $ = go.GraphObject.make; // 第一步:创建图表 var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图 var node1 = $(go.Node, $(go.TextBlock, {text: "jason"})); myDiagram.add(node1); var node2 = $(go.Node, $(go.TextBlock, {text: "jason", stroke: ‘red‘})); myDiagram.add(node2); var node3 = $(go.Node, $(go.TextBlock, {text: "jason", background: ‘lightblue‘})); myDiagram.add(node3); </script>
Shap
gojs只引入go.js默认展示的图形比较少,你如果想展示其他图形,需要再引入Fugures.js
<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div> <script src="go.js"></script> <script src="Figures.js"></script> <script> var $ = go.GraphObject.make; // 第一步:创建图表 var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图 var node1 = $(go.Node, $(go.Shape, {figure: "Ellipse", width: 40, height: 40}) ); myDiagram.add(node1); var node2 = $(go.Node, $(go.Shape, {figure: "RoundedRectangle", width: 40, height: 40, fill: ‘green‘,stroke:‘red‘}) ); myDiagram.add(node2); var node3 = $(go.Node, $(go.Shape, {figure: "Rectangle", width: 40, height: 40, fill: null}) ); myDiagram.add(node3); var node5 = $(go.Node, $(go.Shape, {figure: "Club", width: 40, height: 40, fill: ‘red‘}) ); myDiagram.add(node5); </script>
Node 节点
<div id="myDiagramDiv" style="width:500px; height:350px; background-color: #DAE4E4;"></div> <script src="js/go.js"></script> <script src="js/Figures.js"></script> <script> var $ = go.GraphObject.make; // 第一步:创建图表 var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图 var node1 = $(go.Node, "Vertical", // 垂直方向 { background: ‘yellow‘, padding: 8 }, $(go.Shape, {figure: "Ellipse", width: 40, height: 40,fill:null}), $(go.TextBlock, {text: "jason"}) ); myDiagram.add(node1); var node2 = $(go.Node, "Horizontal", // 水平方向 { background: ‘white‘, padding: 5 }, $(go.Shape, {figure: "RoundedRectangle", width: 40, height: 40}), $(go.TextBlock, {text: "jason"}) ); myDiagram.add(node2); var node3 = $(go.Node, "Auto", // 居中 $(go.Shape, {figure: "Ellipse", width: 80, height: 80, background: ‘green‘, fill: ‘red‘}), $(go.TextBlock, {text: "jason"}) ); myDiagram.add(node3); </script>
流程图必备
<div id="myDiagramDiv" style="width:500px; min-height:450px; background-color: #DAE4E4;"></div> <script src="js/go-debug.js"></script> <script> var $ = go.GraphObject.make; var myDiagram = $(go.Diagram, "myDiagramDiv", {layout: $(go.TreeLayout, {angle: 0})} ); // 创建图表,用于在页面上画图 var startNode = $(go.Node, "Auto", $(go.Shape, {figure: "Ellipse", width: 40, height: 40, fill: ‘#79C900‘, stroke: ‘#79C900‘}), $(go.TextBlock, {text: ‘开始‘, stroke: ‘white‘}) ); myDiagram.add(startNode); var downloadNode = $(go.Node, "Auto", $(go.Shape, {figure: "RoundedRectangle", height: 40, fill: ‘red‘, stroke: ‘#79C900‘}), $(go.TextBlock, {text: ‘下载代码‘, stroke: ‘white‘}) ); myDiagram.add(downloadNode); var startToDownloadLink = $(go.Link, {fromNode: startNode, toNode: downloadNode}, $(go.Shape, {strokeWidth: 1}), $(go.Shape, {toArrow: "OpenTriangle", fill: null, strokeWidth: 1}) ); myDiagram.add(startToDownloadLink); </script>
拷贝使用即可
<div id="diagramDiv" style="width:100%; min-height:450px; background-color: #DAE4E4;"></div> <script src="js/go.js"></script> <script> var $ = go.GraphObject.make; var diagram = $(go.Diagram, "diagramDiv",{ layout: $(go.TreeLayout, { angle: 0, nodeSpacing: 20, layerSpacing: 70 }) }); diagram.nodeTemplate = $(go.Node, "Auto", $(go.Shape, { figure: "RoundedRectangle", fill: ‘yellow‘, stroke: ‘yellow‘ }, new go.Binding("figure", "figure"), new go.Binding("fill", "color"), new go.Binding("stroke", "color")), $(go.TextBlock, {margin: 8}, new go.Binding("text", "text")) ); diagram.linkTemplate = $(go.Link, {routing: go.Link.Orthogonal}, $(go.Shape, {stroke: ‘yellow‘}, new go.Binding(‘stroke‘, ‘link_color‘)), $(go.Shape, {toArrow: "OpenTriangle", stroke: ‘yellow‘}, new go.Binding(‘stroke‘, ‘link_color‘)) ); var nodeDataArray = [ {key: "start", text: ‘开始‘, figure: ‘Ellipse‘, color: "lightgreen"}, {key: "download", parent: ‘start‘, text: ‘下载代码‘, color: "lightgreen", link_text: ‘执行中...‘}, {key: "compile", parent: ‘download‘, text: ‘本地编译‘, color: "lightgreen"}, {key: "zip", parent: ‘compile‘, text: ‘打包‘, color: "red", link_color: ‘red‘}, {key: "c1", text: ‘服务器1‘, parent: "zip"}, {key: "c11", text: ‘服务重启‘, parent: "c1"}, {key: "c2", text: ‘服务器2‘, parent: "zip"}, {key: "c21", text: ‘服务重启‘, parent: "c2"}, {key: "c3", text: ‘服务器3‘, parent: "zip"}, {key: "c31", text: ‘服务重启‘, parent: "c3"} ]; diagram.model = new go.TreeModel(nodeDataArray); // 动态控制节点颜色变化 /* var node = diagram.model.findNodeDataForKey("zip"); diagram.model.setDataProperty(node, "color", "lightgreen"); */ </script>
修改go.js文件中的源码,查找一个字符串7eba17a4ca3b1a8346
先注释
/*a.Cr=b.Z[Wa("7eba17a4ca3b1a8346")][Wa("78a118b7")](b.Z,Zk,4,4);*/
添加新内容
a.kr=function(){return true};
四、paramiko模块
通过ssh远程连接服务器并执行想要命令类似于Xshell
安装
pip install paramiko
使用
paramiko模块支持上面两种连接服务器的方式:
1.用户名和密码连接服务器
2.公钥私钥连接服务器
支持输入命令行
# 用户名和密码的方式 import paramiko # 创建ssh对象 ssh = paramiko.SSHClient() # 允许链接不在know_hosts文件中主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 链接服务器 ssh.connect(hostname=‘172.16.219.168‘,port=22,username=‘root‘,password=‘jason123‘) # 执行命令 stdin, stdout, stderr = ssh.exec_command(‘ip a‘) # 获取结果 res = stdout.read() # 基于网络传输 该结果是一个bytes类型 print(res.decode(‘utf-8‘)) # 断开链接 ssh.close() # 公钥私钥方式 # 首先你要产生你自己的公钥和私钥 然后将你的公钥上传到服务器保存 之后就可以通过私钥来链接 """ mac本 ssh-keygen -t rsa ssh-copy-id -i ~/.ssh/id_rsa.pub username@hostname cat ~/.ssh/id_rsa """ # # 公钥和私钥(先讲公钥保存到服务器上) # import paramiko # # # 读取本地私钥 # private_key = paramiko.RSAKey.from_private_key_file(‘a.txt‘) # # # 创建SSH对象 # ssh = paramiko.SSHClient() # # 允许连接不在know_hosts文件中的主机 # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # # 连接服务器 # ssh.connect(hostname=‘172.16.219.168‘, port=22, username=‘root‘, pkey=private_key) # # # 执行命令 # stdin, stdout, stderr = ssh.exec_command(‘ls /‘) # # 获取命令结果 # result = stdout.read() # print(result.decode(‘utf-8‘)) # # 关闭连接 # ssh.close()
进行上传下载文件
# 用户名密码方式 # import paramiko # # # 用户名和密码 # transport = paramiko.Transport((‘172.16.219.168‘, 22)) # transport.connect(username=‘root‘, password=‘jason123‘) # # sftp = paramiko.SFTPClient.from_transport(transport) # # # 上传文件 # # sftp.put("a.txt", ‘/data/tmp.txt‘) # 注意上传文件到远程某个文件下 文件必须存在 # # # 下载文件 # sftp.get(‘/data/tmp.txt‘, ‘hahahha.txt‘) # 将远程文件下载到本地并重新命令 # transport.close() # 公钥私钥方式 # # import paramiko # private_key = paramiko.RSAKey.from_private_key_file(‘a.txt‘) # transport = paramiko.Transport((‘172.16.219.168‘, 22)) # transport.connect(username=‘root‘, pkey=private_key) # sftp = paramiko.SFTPClient.from_transport(transport) # # 将location.py 上传至服务器 /tmp/test.py # sftp.put(‘/tmp/location.py‘, ‘/tmp/test.py‘) # # # 将remove_path 下载到本地 local_path # sftp.get(‘remove_path‘, ‘local_path‘) # transport.close()
我们要实现在一个链接上即可以执行命令又可以上传下载文件
我们封装成一个类,类的内部有一系列的方法,并且这些方法都可以在同一个链接下执行多次
import paramiko class SSHProxy(object): def __init__(self, hostname, port, username, password): self.hostname = hostname self.port = port self.username = username self.password = password self.transport = None def open(self): # 给对象赋值一个上传下载文件对象连接 self.transport = paramiko.Transport((self.hostname, self.port)) self.transport.connect(username=self.username, password=self.password) def command(self, cmd): # 正常执行命令的连接 至此对象内容就既有执行命令的连接又有上传下载链接 ssh = paramiko.SSHClient() ssh._transport = self.transport stdin, stdout, stderr = ssh.exec_command(cmd) result = stdout.read() return result def upload(self, local_path, remote_path): sftp = paramiko.SFTPClient.from_transport(self.transport) sftp.put(local_path, remote_path) sftp.close() def close(self): self.transport.close() def __enter__(self): print(‘with开始‘) self.open() return self # 该方法返回什么 with ... as 后面就拿到什么 def __exit__(self, exc_type, exc_val, exc_tb): print(‘with结束‘) self.close() if __name__ == ‘__main__‘: # obj = SSHProxy(hostname=‘172.16.219.168‘,port=22,username=‘root‘,password=‘jason123‘) # # 生成对象之后必须要先执行open方法 # obj.open() # # 你就可以无限制的执行命令或者上传下载文件 # obj.command(‘ls /‘) # obj.command(‘df‘) # obj.upload(‘a.txt‘,‘/data/tmp.txt‘) # # 断开链接 # obj.close() """ 文件操作 f = open() ... f.close() with上下文管理 """ obj = SSHProxy(hostname=‘172.16.219.168‘,port=22,username=‘root‘,password=‘jason123‘) with obj as ssh: # 默认不支持 对象执行with会自动触发内部的__enter__方法 print(‘with内部代码‘) # with上下文执行完毕之后 会自动触发__exit__方法
以上是关于Django使用Channels实现WebSSH网页终端,实现SSH堡垒机雏形的主要内容,如果未能解决你的问题,请参考以下文章
Django + Nginx + Daphne实现webssh功能
websockets django nginx webssh
Python Django撸个WebSSH操作Kubernetes Pod(下)- 终端窗口自适应Resize