标签:pen 水平 icons 开源框架 帐号管理 关系 交互式 start img
WebSSH有很多,基于Django的Web服务也有很多,使用Paramiko在Python中进行SSH访问的就更多了。但是通过gevent将三者结合起来,实现通过浏览器访问的堡垒机就很少见了。本文将简要介绍下我开发的IronFort堡垒机,其详细内容在我的官方网站liujiangblog.com的视频教程中。
百度百科:堡垒机,在一个特定的网络环境下,为了保障网络和数据不受来自外部和内部用户的入侵和破坏,而运用各种技术手段实时收集和监控网络环境中每一个组成部分的系统状态、安全事件、网络活动,以便集中报警、及时处理及审计定责。
对于一个中型以上的公司,当用户和职员人数较多,公司所属服务器也数量较大的情况下,其服务器上的帐号管理难度将急剧增加,参考下面的图片:
这其中必然存在很多问题,例如:
在运行初期,公司可能采取Excel表格等工具,使用人工管理的方式,靠‘人治’和道德水平约束,但当公司体量逐渐变大的时候,这种方式必然遭到淘汰,于是就出现了堡垒机的概念,如下图所示:
这种架构带来如下的好处:
堡垒机的核心概念是用户不再掌握帐号密码,用户的行为被记录用于审计。堡垒机主要针对的是内部网络和内部人员,对于人员流动性较强、体量大、行业风险高的企业需求特别强烈,比如金融行业。
堡垒机已经拥有商业产品,多数以硬件服务器为载体进行销售,价格几十万不等。也有开源的解决方案,但这些方案有的不是基于浏览器,界面不够友好,日志记录困难;有的基于Tornado,并且只能进行简单的命令执行功能,而公司使用的是Django;更多的情况是与公司需求不一致,需要二次开发,维护和升级困难,等等不一而足。
‘授人以鱼不如授人以渔’,自己掌握了开发堡垒机的核心技能,就可以快速、方便、灵活的针对公司具体需求进行定制开发,既为公司节省了购置硬件经费,又利于维护升级。
IronFort堡垒机的体系架构如下图所示:
一个完整的通信过程如下:
核心机制就是这样,下面我们来看下开发过程。
堡垒机本身通常是布置在Linux主机上的,比ubuntu16.04,对外以HTTP的形式提供服务。
首先需要建立虚拟环境,并安装Python3.6以及Django2.0,不再赘述。
使用django-admin startproject
和python manage.py startapp app_name
分别创建项目和app。
此时,可以尝试运行Django服务,可以看到欢迎界面。
任何一个Web项目都必须在深入分析项目需求的情况下,首先设计好ORM模型,也就是数据库的表结构。
IronFort中设计了六个模型,分别是:
这里需要提醒的是:
关于模型设计,每个人有每个人的需求和想法,这其中有很多坑和需要注意的地方,限于篇幅,无法展开论述。在我的个人网站liujiangblog.com的视频教程中有详细的讲解。
模型设计好了,可以同时注册Django的admin后台。然后makemigrations、migrate和createsuperuser,重启服务器后就可以在admin中创建测试用例了,如下图所示:
url的设计并不复杂,没有太多的复杂页面,下面是项目中使用的一些url:
from django.contrib import admin
from django.urls import path, re_path
from fort import views
urlpatterns = [
path(‘admin/‘, admin.site.urls),
path(‘‘, views.login),
path(‘login/‘, views.login),
path(‘logout/‘, views.logout),
path(‘index/‘, views.index),
path(‘log/‘, views.get_log),
path(‘host/<int:user_bind_host_id>/‘, views.connect),
]
Django2.0的url语法向flask等框架靠拢了,但依然可以使用正则模式。关于2.0和之前版本的区别,可以查看我曾经写过的一篇博文Django 2.0 新特性 抢先看!。其实不是重度使用者,基本感受不出变化来,该怎么用还是怎么用。最大的区别也就在url编写,和Python2及3的支持。
为了让用户界面美观,我这里使用了基于bootstrap的开源框架AdminLTE。
AdminLTE托管在GitHub上,可以通过下面的地址下载:
https://github.com/almasaeed2010/AdminLTE/releases
AdminLTE自带JQuery和Bootstrap3,无需另外下载。
AdminLTE自带多种配色皮肤,可根据需要实时调整。
AdminLTE是移动端自适应的,无需单独考虑。
AdminLTE自带大量插件,比如datatables,可根据需要载入。
但是AdminLTE的源文件包内,缺少font-awesome-4.6.3和ionicons-2.0.1这两个图标插件,它是通过CDN的形式加载的,如果网络不太好,加载可能比较困难或者缓慢,最好用本地静态文件的形式,请自定下载并引入项目内。
我们不需要AdminLTE那么多的功能,只需要它的基本框架。在其源码包内,对index文件进行裁剪和静态文件导入处理,形成一个基本的base.html用于拓展,在它的基础上,我们可以扩展出index和log页面。
堡垒机用户登录页面不需要使用AdminLTE,最好是单独一个简单的页面,展示的内容越少越好。
而用户登录的处理视图就很简单了,直接使用Django内置的Auth认证系统。
使用Django自带的authenticate和login方法就可以完成用户验证和登录会话。
既然有了登录,必然就要有登出。为了限制未登录用户访问堡垒机系统,所有的相关视图都必须先使用装饰器进行是否登录验证。
通常而言,堡垒机不需要提供面向用户的注册页面。堡垒机用户的注册都是超级管理员掌控的,在后台进行!
也就是我们堡垒机用户登录进系统后,显示的默认页面index。这里将通过表格的形式,列出当前堡垒机用户可以使用的远程主机帐号。视图很简单:
@login_required(login_url=‘/login/‘)
def index(request):
# ...通过ORM的API查询可使用的帐号
return render(request, ‘fort/index.html‘, locals())
主机账户的前端页面index基于base.html,使用datatable插件,提供搜索、排序和分页等高级功能,其展示效果如下图:
百度百科:WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
本文不打算成为一篇websocket的科普文,有兴趣深入研究的可以查看博客园的精华博文WebSocket协议:5分钟从入门到精通
简单的说,有以下几点:
关于websocket的使用教程,可以参考阮一峰专家的博文WebSocket 教程
其具体API如下图所示:
要简单的创建并使用一个websocket,按下面的套路就可以了:
new WebSocket(url, [protocol] );
创建ws对象我们在主机帐号表格中隐藏一个主机帐号id的字段,通过js代码获取该字段的值,然后启动websocket通信,传递这个id作为参数之一,用于构造websocket通信使用的url。
在浏览器模拟Linux终端方面,我使用的是term.js插件。这是一个开源在github上的浏览器模拟Linux终端的js插件,地址为:https://github.com/chjj/term.js
。其官方文档比较简单,有兴趣的同学可以深入研读其源代码,或者使用xterm作为替代。
最终效果如下:
因为此时后端还没有完成,所以是连接不上任何主机的。
Django本身是一个同步Web框架,也不支持websocket。所以你使用它的runserver,是无法接收和处理websocket请求的。为了解决这个问题,可以使用gevent这个Python的第三方异步网络框架。
gevent基于greelet协程库,自带有WSGI服务器,并且其扩展库gevent-websocket支持websocket通信。
请先用pip install gevent gevent-websocket
安装这两个库。
在IronFort项目根目录下创建一个start_ironfort.py
脚本,以后这就是我们的服务启动脚本了。
from gevent import monkey
monkey.patch_all()
from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
from ironfort.wsgi import application
print(‘ironfort is running ......‘)
ws_server = WSGIServer(
(host, port),
application,
log=None,
handler_class=WebSocketHandler
)
try:
ws_server.serve_forever()
except KeyboardInterrupt:
print(‘服务器关闭......‘)
pass
核心要点是,使用gevent的WSGIServer服务器代替DJango的runserver,使用geventwebsocket的WebSocketHandler来处理浏览器发送过来的websocket通信请求,并将其转发到Django的application。
我们知道Django的通信入口就存在于from ironfort.wsgi import application
中的这个方法。通过gevent的帮助,我们让Django具备了接收websocket通信请求的能力。
运行python start_ironfort
可以启动新的服务器,在浏览器验证一下,都可以正常访问。
我们前面的根路由中已经写了相关的url,这里再贴出来:
path(‘host/<int:user_bind_host_id>/‘, views.connect),
这样,以ws://ip:port/host/15/
形式的url请求,将被转发到connect视图进行处理,这其中传递了‘15’这个主机帐号id的参数。具体connect视图局部代码如下:
@login_required(login_url=‘/login/‘)
def connect(request, user_bind_host_id):
# 如果当前请求不是websocket请求则退出
# ...省略
# 获取remote_user_bind_host
bridge = WSSHBridge(request.environ.get(‘wsgi.websocket‘), request.user)
try:
bridge.open(
host_ip=remote_user_bind_host.host.ip,
port=remote_user_bind_host.host.port,
username=remote_user_bind_host.remote_user.remote_user_name,
password=remote_user_bind_host.remote_user.password
)
except Exception as e:
message = ‘尝试连接{0}的过程中发生错误:\n {1}‘.format(
remote_user_bind_host.remote_user.remote_user_name, e)
print(message)
add_log(request.user, message, log_type=‘2‘)
return HttpResponse("错误!无法建立SSH连接!")
bridge.shell()
request.environ.get(‘wsgi.websocket‘).close()
print(‘用户断开连接.....‘)
return HttpResponse("200, ok")
说明:
那么这里的WSSHBridge类是什么呢?
WSSHBridge:
import gevent
from gevent.socket import wait_read, wait_write
import paramiko
import json
class WSSHBridge:
"""
桥接websocket和SSH的核心类
"""
def __init__(self, websocket, user):
self.user = user
self._websocket = websocket
self._tasks = []
#...
def open(self, host_ip, port=22, username=None, password=None):
""" 建立SSH连接 """
pass
def _forward_inbound(self, channel):
""" 正向数据转发,websocket -> ssh """
pass
def _forward_outbound(self, channel):
""" 反向数据转发,ssh -> websocket """
pass
def _bridge(self, channel):
""" 桥接websocket和ssh """
pass
def close(self):
""" 结束桥接会话 """
pass
def shell(self):
""" 启动一个shell通信界面 """
pass
首先需要pip install paramiko
安装模块。
WSSHBridge类,本质上就是桥接websocket通道和paramiko打开的ssh通道,进行数据双向转发。
open方法调用paramiko的相关API,传入主机ip、port、用户名和密码,打开ssh通道,_forward_inbound
和_forward_outbound
方法分别实现数据的正向和反向转发。
核心的关键是_bridge
方法:
self._tasks = [
gevent.spawn(self._forward_inbound, channel),
gevent.spawn(self._forward_outbound, channel),
]
gevent.joinall(self._tasks)
使用gevent的spawn方法创建了两个协同任务,然后调用joinall方法等待它们任务结束。这样就实现了数据在websocket通道和ssh通道之间的一发一收,一收一发的通信机制。
这一步完成后,重启服务器,我们就可以来展示整个通信过程了。
首先是,连接成功:
其次是类似Python这种交互式命令:
然后是top这种动态命令结果返回:
最后是vim这种编辑环境:
可以看到,我们是支持彩色输出的:
关于用户操作,在数据由websocket往ssh发送过程中,可以保存用户通过前端Linux模拟器终端所敲击的所有按键记录,并且很规整的以回车键进行分隔,非常容易判别。
我们只需要创建一个日志模型,编写一个保存日志的方法,然后在需要的位置保存日志即可。
日志展示页面非常类似主机账户的页面,同样使用datatable插件进行处理,最终效果如下图所示:
至此,基于Webssh的堡垒机核心功能就开发完毕了。限于篇幅,不可能点点滴滴、枝叶不漏的全部叙述,我这里也只是一个抛砖引玉的过程。
远程主机的创建、主机账号的管理、堡垒机用户和用户组的管理,这一系列的工作,目前我还是放在admin后台中进行。后期,大家可以将它迁移到堡垒机页面中一起管理。如果将IronFort用于生产环境,添加批量命令执行、文件分发功能,进行系统部署上线、结合Linux运维等等,必然需要大量的额外工作和安全机制,这些就留给大家自己去研究了。
标签:pen 水平 icons 开源框架 帐号管理 关系 交互式 start img
原文地址:https://www.cnblogs.com/django-dev/p/13776515.html