Django完成WebSSH操纵物理机或虚拟机
2019-11-18杂谈搜奇网55°c
A+ A-我想用它替换掉xshell、crt之类的东西
WebSSH操纵物理机或虚拟机
Django完成WebSSH操纵Kubernetes Pod文章宣布后,有小伙伴说咖啡哥,我们如今还没有用上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操纵Kubernetes Pod的文章,完成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', ''))
动态调解终端窗口大小
看了Kubernetes WebSSH终端窗口自适应Resize文章,小伙伴又说了,你这只能在衔接竖立时肯定终端窗口的大小,假如我半途调解了浏览器的大小,显现就乱了,这该怎样办?
不要焦急,接下来就让我们看看怎样让终端窗口跟着浏览器大小的调解而转变,上边的文章中已说过,终端窗口的大小须要浏览器和后端返回的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衬着的窗口的大小
如许一个完全的动态调解窗口大小的计划就完成了
演示与源码
我写了个简朴的Demo来完成上边的功用,Demo写完发明还挺好用,我就扩大了一下添加了内网的物理机和虚拟机,汗青缘由,有些是账号暗码认证,有些是密钥认证,我都给兼容了一下,终究完成的结果如上图所示
项目里边要纪录主机的暗码,为了平安这个暗码是经由过程RSA加密存放在数据库的,每次运用的时刻举行解密,加解密的完成,可参考这篇文章 Django开辟暗码治理表实例【附源码】
末了,假如你对这个简朴的小玩意感兴趣,想要本身完成,却遇到了一些问题,能够经由过程民众号背景加我微信猎取源码
相干文章引荐浏览:
- Django完成WebSSH操纵Kubernetes Pod
- Kubernetes WebSSH终端窗口自适应Resize