温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Django怎么实现WebSSH操作物理机或虚拟机

发布时间:2021-06-05 15:23:24 来源:亿速云 阅读:498 作者:小新 栏目:开发技术

小编给大家分享一下Django怎么实现WebSSH操作物理机或虚拟机,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

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怎么实现WebSSH操作物理机或虚拟机”有了一定的了解,如果想了解更多相关知识,欢迎关注亿速云行业资讯频道,感谢各位的阅读!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI