jumpserver是个很受欢迎的开源堡垒机系统,功能丰富,开箱即用。最近公司有需求想要二次开发,调研过程中发现改造还是比较困难的,主要有以下几个问题:
- 部署麻烦,有core、koko、lion、luna、lina等服务
- 分别用了python、golang、nodejs语言开发
- core服务使用了django-rest-framework库,以配置为主,修改逻辑比较困难
- 部分前后端未分离
- 核心的lion未开源
- celery处理异步任务,很多任务都不是必要的
- ansbile推送系统用户等,需要服务器和网络支持
- 控制台权限管理不清晰
综上原因,改造比较困难,因此想自己开发一个。
虽然后来发现了更简单轻量的next-terminal,但本着学习的目的,还是自己造了轮子。
堡垒机系统的关键在于通过网页上连接服务器,因此在此记录下方案。
开始想用python实现后端,用过flask + paramiko + flask-socketio方案,尝试下来发现性能不行,ssh延迟比较大。
后来改成了fastapi + asyncssh,效果还行。
因为公司内用golang多些,最终用Gin框架来实现后端。
rdp协议连接用了Guacamole。
前端用了vue + xterm + socketio
paramiko连接ssh部分代码:
import paramiko
import threading
import io
class SshClient:
def __init__(self, ip, port, username, psd=None, pkey=None):
self.ip = ip
self.port = port
self.username = username # 用户
self.psd = psd #密码
self.pkey = pkey # 私钥内容
self.channel = None
def send(self, msg):
'''
websocket接收到网页发来的消息后,调用此函数发给ssh通道
'''
if self.channel:
return self.channel.send(msg)
def listening(self):
while True:
ret = self.channel.recv(10240)
print('>>', ret)
# 收到ssh通道返回的消息,通过websocket返回给网页
def start(self):
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 私钥登录
if self.pkey:
pkey = paramiko.RSAKey.from_private_key(io.StringIO(self.pkey))
client.connect(hostname=self.ip, port=self.port, username=self.username, pkey=pkey)
self.channel = client.invoke_shell()
# 密码登录
else:
client.connect(hostname=self.ip, port=self.port, username=self.username, password=self.psd)
self.channel = client.invoke_shell()
threading._start_new_thread(self.listening, ())
asyncssh连接ssh部分代码:
import asyncio, asyncssh, sys
from fastapi import WebSocket
class MySSHClientSession(asyncssh.SSHClientSession):
def __init__(self, consumer):
self.consumer = consumer
def data_received(self, data, datatype):
asyncio.ensure_future(self.consumer.send(data))
def connection_lost(self, exc):
if exc:
print('SSH session error: ' + str(exc), file=sys.stderr)
class MySSHClient(asyncssh.SSHClient):
def connection_made(self, conn):
print('Connection made to %s.' % conn.get_extra_info('peername')[0])
def auth_completed(self):
print('Authentication successful.')
class WsClient():
def __init__(self, websocket: WebSocket, client_id=None):
self.websocket = websocket
self.client_id = client_id
self.chan = None
self.conn = None
async def close(self):
if self.conn:
self.conn.close()
if self.chan:
self.chan.close()
self.conn = None
self.chan = None
self.websocket = None
async def send(self, text):
await self.websocket.send_text(text)
def write(self, text):
if self.chan and self.chan._send_state == 'open':
self.chan.write(text)
else:
print("no channel")
async def run_client(self):
self.conn, client = await asyncssh.create_connection(MySSHClient, '192.168.64.8', port=22, username='root', password=' ', known_hosts=None)
self.chan, _ = await self.conn.create_session(lambda:MySSHClientSession(self), term_type='xterm')
前端部分截图
使用docker运行体验
docker run -itd -p 8080:80 qf0129/jumpman:latest
github地址
https://github.com/qf0129/jumpman