Django + Nginx + Daphne实现webssh功能
Posted 假装看起来很努力
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django + Nginx + Daphne实现webssh功能相关的知识,希望对你有一定的参考价值。
pip install channels==2.0.0 pip install Django==2.1 pip install uWSGI==2.0.19.1 pip install paramiko==2.4.1 pip install daphne==2.2.5
2.打开django项目的setting.py文件,添加以下内容
INSTALLED_APPS = [ \'channels\', ] ASGI_APPLICATION = \'my_project_name.routing.application\'
3.在setting.py同级目录下添加routing.py文件,routing.py文件就相当于urls.py意思
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from assets.tools.channel import routing application = ProtocolTypeRouter({ \'websocket\': AuthMiddlewareStack( URLRouter( routing.websocket_urlpatterns ) ), })
4.在你的新建一个app应用下面添加一下目录文件
tools目录 __init__.py ssh.py tools.py channel目录 __init__.py routing.py websocket.py
routing.py文件
from django.urls import path from assets.tools.channel import websocket websocket_urlpatterns = [ path(\'webssh/\', websocket.WebSSH) 开头是webssh请求交给websocket.WebSSH处理 ]
websocket.py文件
from channels.generic.websocket import WebsocketConsumer from assets.tools.ssh import SSH from django.http.request import QueryDict from django.utils.six import StringIO from test_devops.settings import TMP_DIR import os import json import base64 class WebSSH(WebsocketConsumer): message = {\'status\': 0, \'message\': None} """ status: 0: ssh 连接正常, websocket 正常 1: 发生未知错误, 关闭 ssh 和 websocket 连接 message: status 为 1 时, message 为具体的错误信息 status 为 0 时, message 为 ssh 返回的数据, 前端页面将获取 ssh 返回的数据并写入终端页面 """ def connect(self): """ 打开 websocket 连接, 通过前端传入的参数尝试连接 ssh 主机 :return: """ self.accept() query_string = self.scope.get(\'query_string\') ssh_args = QueryDict(query_string=query_string, encoding=\'utf-8\') width = ssh_args.get(\'width\') height = ssh_args.get(\'height\') port = ssh_args.get(\'port\') width = int(width) height = int(height) port = int(port) auth = ssh_args.get(\'auth\') ssh_key_name = ssh_args.get(\'ssh_key\') passwd = ssh_args.get(\'password\') host = ssh_args.get(\'host\') user = ssh_args.get(\'user\') if passwd: passwd = base64.b64decode(passwd).decode(\'utf-8\') else: passwd = None self.ssh = SSH(websocker=self, message=self.message) ssh_connect_dict = { \'host\': host, \'user\': user, \'port\': port, \'timeout\': 30, \'pty_width\': width, \'pty_height\': height, \'password\': passwd } if auth == \'key\': ssh_key_file = os.path.join(TMP_DIR, ssh_key_name) with open(ssh_key_file, \'r\') as f: ssh_key = f.read() string_io = StringIO() string_io.write(ssh_key) string_io.flush() string_io.seek(0) ssh_connect_dict[\'ssh_key\'] = string_io os.remove(ssh_key_file) self.ssh.connect(**ssh_connect_dict) def disconnect(self, close_code): try: self.ssh.close() except: pass def receive(self, text_data=None, bytes_data=None): data = json.loads(text_data) if type(data) == dict: status = data[\'status\'] if status == 0: data = data[\'data\'] self.ssh.shell(data) else: cols = data[\'cols\'] rows = data[\'rows\'] self.ssh.resize_pty(cols=cols, rows=rows)
ssh.py文件
import paramiko from threading import Thread from assets.tools.tools import get_key_obj import socket import json class SSH: def __init__(self, websocker, message): self.websocker = websocker self.message = message def connect(self, host, user, password=None, ssh_key=None, port=22, timeout=30, term=\'xterm\', pty_width=80, pty_height=24): try: # 实例化SSHClient ssh_client = paramiko.SSHClient() # 当远程服务器没有本地主机的密钥时自动添加到本地,这样不用在建立连接的时候输入yes或no进行确认 ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) if ssh_key: key = get_key_obj(paramiko.RSAKey, pkey_obj=ssh_key, password=password) or \\ get_key_obj(paramiko.DSSKey, pkey_obj=ssh_key, password=password) or \\ get_key_obj(paramiko.ECDSAKey, pkey_obj=ssh_key, password=password) or \\ get_key_obj(paramiko.Ed25519Key, pkey_obj=ssh_key, password=password) # 连接SSH服务器,这里以账号密码的方式进行认证,也可以用key ssh_client.connect(username=user, hostname=host, port=port, pkey=key, timeout=timeout) # else: # ssh_client.connect(username=user, password=password, hostname=host, port=port, timeout=timeout) # 打开ssh通道,建立长连接 transport = ssh_client.get_transport() self.channel = transport.open_session() # 获取ssh通道,并设置term和终端大小 self.channel.get_pty(term=term, width=pty_width, height=pty_height) # 激活终端,这样就可以正常登陆了 self.channel.invoke_shell() # 连接建立一次,之后交互数据不会再进入该方法 for i in range(2): # SSH返回的数据需要转码为utf-8,否则json序列化会失败 recv = self.channel.recv(1024).decode(\'utf-8\') self.message[\'status\'] = 0 self.message[\'message\'] = recv message = json.dumps(self.message) self.websocker.send(message) except socket.timeout: self.message[\'status\'] = 1 self.message[\'message\'] = \'ssh 连接超时\' message = json.dumps(self.message) self.websocker.send(message) self.close() except Exception as e: self.close(e) # 动态调整终端窗口大小 def resize_pty(self, cols, rows): self.channel.resize_pty(width=cols, height=rows) def django_to_ssh(self, data): try: self.channel.send(data) except Exception as e: self.close(e) def websocket_to_django(self): try: while True: data = self.channel.recv(1024).decode(\'utf-8\') if not len(data): return self.message[\'status\'] = 0 self.message[\'message\'] = data message = json.dumps(self.message) self.websocker.send(message) except Exception as e: self.close(e) def close(self,error=None): self.message[\'status\'] = 1 self.message[\'message\'] = f\'{error}\' message = json.dumps(self.message) self.websocker.send(message) try: self.websocker.close() self.channel.close() except Exception as e: pass def shell(self, data): Thread(target=self.django_to_ssh, args=(data,)).start() Thread(target=self.websocket_to_django).start()
tools.py文件
import time import random import hashlib def get_key_obj(pkeyobj, pkey_file=None, pkey_obj=None, password=None): if pkey_file: with open(pkey_file) as fo: try: pkey = pkeyobj.from_private_key(fo, password=password) return pkey except: pass else: try: pkey = pkeyobj.from_private_key(pkey_obj, password=password) return pkey except: pkey_obj.seek(0) def unique(): ctime = str(time.time()) salt = str(random.random()) m = hashlib.md5(bytes(salt, encoding=\'utf-8\')) m.update(bytes(ctime, encoding=\'utf-8\')) return m.hexdigest()
三、前端页面代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webssh</title> <link rel="stylesheet" href="/static/css/ssh/xterm/xterm.css"/> <link rel="stylesheet" href="/static/css/ssh/xterm/style.css"/> <link rel="stylesheet" href="/static/css/toastr/toastr.min.css"> <link rel="stylesheet" href="/static/css/bootstrap.min.css"/> </head> <body> <div id="django-webssh-terminal"> <div id="terminal"></div> </div> <script src="/static/js/plugin/jquery.min.js"></script> <script src="/static/js/plugin/ssh/xterm/xterm.js"></script> <script src="/static/js/plugin/toastr/toastr.min.js"></script> <script> host_data = {{ data | safe }} var port = host_data.port; var intrant_ip = host_data.intranet_ip; var user_name = host_data.login_user; var auth_type = host_data.auth_type; var user_key = host_data.ssh_key; function get_term_size() { var init_width = 9; var init_height = 17; var windows_width = $(window).width(); var windows_height = $(window).height(); return { cols: Math.floor(windows_width / init_width), rows: Math.floor(windows_height / init_height), } } var cols = get_term_size().cols; var rows = get_term_size().rows; var connect_info = \'host=\' + intrant_ip+ \'&port=\' + port + \'&user=\' + user_name + \'&auth=\' + auth_type + \'&password=\' + \'&ssh_key=\' + user_key; var term = new Terminal( { cols: cols, rows: rows, useStyle: true, cursorBlink: true } ), protocol = (location.protocol === \'https:\') ? \'wss://\' : \'ws://\', socketURL = protocol + location.hostname + ((location.port) ? (\':\' + location.port) : \'\') + \'/webssh/?\' + connect_info + \'&width=\' + cols + \'&height=\' + rows; var sock; sock = new WebSocket(socketURL); // 打开 websocket 连接, 打开 web 终端 sock.addEventListener(\'open\', function () { term.open(document.getElementById(\'terminal\')); }); // 读取服务器端发送的数据并写入 web 终端 sock.addEventListener(\'message\', function (recv) { var data = JSON.parse(recv.data); var message = data.message; var status = data.status; if (status === 0) { term.write(message) } else { toastr.error(\'连接失败,错误:\' + data.message) } }); /* * status 为 0 时, 将用户输入的数据通过 websocket 传递给后台, data 为传递的数据, 忽略 cols 和 rows 参数 * status 为 1 时, resize pty ssh 终端大小, cols 为每行显示的最大字数, rows 为每列显示的最大字数, 忽略 data 参数 */ var message = {\'status\': 0, \'data\': null, \'cols\': null, \'rows\': null}; // 向服务器端发送数据 term.on(\'data\', function (data) { message[\'status\'] = 0; message[\'data\'] = data; var send_data = JSON.stringify(message); sock.send(send_data) }); // 监听浏览器窗口, 根据浏览器窗口大小修改终端大小 $(window).resize(function () { var cols = get_term_size().cols; var rows = get_term_size().rows; message[\'status\'] = 1; message[\'cols\'] = cols; message[\'rows\'] = rows; var send_data = JSON.stringify(message); sock.send(send_data); term.resize(cols, rows) }) </script> </body> </html>
四、配置Daphne
在生产环境一般用django + nginx + uwsgi,但是uwsgi只处理http协议请求,不处理websocket请求,所以要额外添加文件启动进程,这里使用daphne,在setting.py文件同级目录下添加asgi.py文件
补充小知识:Daphne 是一个纯Python编写的应用于UNIX环境的由Django项目维护的ASGI服务器。它扮演着ASGI参考服务器的角色。
""" ASGI entrypoint. Configures Django and then runs the application defined in the ASGI_APPLICATION setting. """ import os import django from channels.routing import get_default_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project_name.settings") django.setup() application = get_default_application()
启动方式
#你应该在与 manage.py 文件相同的路径中运行这个命令。 daphne -p 8001 my_project_name.asgi:application
五、配置Nginx
upstream wsbackend { server 127.0.0.1:8001; } server { listen 80; server_name 192.168.10.133; location /webssh { proxy_pass http://wsbackend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; } }
六、效果展示
点击登录按钮,如果用户公钥在这台机器上面就可以登录
2.否则会报错,关闭窗口连接也会断开
总结:如果后台配合权限整合webssh功能,对使用者来说带来很多方便,不妨试试~
以上是关于Django + Nginx + Daphne实现webssh功能的主要内容,如果未能解决你的问题,请参考以下文章
使用 nginx 和 daphne 部署 django、channels 和 websockets
使用 nginx、django、daphne 部署到 docker
使用 SSL 使用 Daphne + NGINX 部署 Django 通道
ERR_TOO_MANY_REDIRECTS 当我去域:Nginx,Daphne,Django,DigitalOcean