WebSSH有很多,基于Django的Web服务也有很多,使用Paramiko在Python中进行SSH访问的就更多了。但是通过gevent将三者结合起来,实现通过浏览器访问的堡垒机就很少见了。本文将简要介绍下我开发的IronFort堡垒机,其详细内容在我的官方网站liujiangblog.com的视频教程中。

一、堡垒机概述

百度百科:堡垒机,在一个特定的网络环境下,为了保障网络和数据不受来自外部和内部用户的入侵和破坏,而运用各种技术手段实时收集和监控网络环境中每一个组成部分的系统状态、安全事件、网络活动,以便集中报警、及时处理及审计定责。

对于一个中型以上的公司,当用户和职员人数较多,公司所属服务器也数量较大的情况下,其服务器上的帐号管理难度将急剧增加,参考下面的图片:

这其中必然存在很多问题,例如:

  • 用户、主机、账号数量太多,工作量大,管理混乱;
  • 每个人员的权限和可使用账号没有系统管理,等级区分不明;
  • 用户直接掌握主机的帐号密码;
  • 密码可能交叉使用;
  • 离职人员可能还可以使用公司的帐号;
  • 内部人员可以跳过防火墙,直接使用帐号在机房内访问;
  • 内部人员离职前设下木马或暗门,一段时间后再爆发;
  • 对人员的访问记录、过往操作没有日志和审计,缺乏事后追踪手段;
  • 其它风险

在运行初期,公司可能采取Excel表格等工具,使用人工管理的方式,靠‘人治’和道德水平约束,但当公司体量逐渐变大的时候,这种方式必然遭到淘汰,于是就出现了堡垒机的概念,如下图所示:

这种架构带来如下的好处:

  • 用户不能直接访问远程主机,而是需要通过堡垒机跳转;
  • 用户不再掌握远程主机的帐号密码,只有访问堡垒机的帐号;
  • 限制用户登录远程主机后的修改密码能力,不允许修改;
  • 堡垒机的用户、远程主机的用户、用户密码、用户权限等等都被统一集中管理,大量节省人工成本;
  • 用户在登录堡垒机后所进行的一切操作将被记录下来,用于后期的行为审计;
  • 由于没有远程主机帐号密码,即使进入机房也无法直连主机;
  • 还可以实现批量命令执行、文件分发等附带功能;
  • 其它收益。

堡垒机的核心概念是用户不再掌握帐号密码,用户的行为被记录用于审计。堡垒机主要针对的是内部网络和内部人员,对于人员流动性较强、体量大、行业风险高的企业需求特别强烈,比如金融行业。

堡垒机已经拥有商业产品,多数以硬件服务器为载体进行销售,价格几十万不等。也有开源的解决方案,但这些方案有的不是基于浏览器,界面不够友好,日志记录困难;有的基于Tornado,并且只能进行简单的命令执行功能,而公司使用的是Django;更多的情况是与公司需求不一致,需要二次开发,维护和升级困难,等等不一而足。

‘授人以鱼不如授人以渔’,自己掌握了开发堡垒机的核心技能,就可以快速、方便、灵活的针对公司具体需求进行定制开发,既为公司节省了购置硬件经费,又利于维护升级。

二、 IronFort堡垒机体系架构

IronFort堡垒机的体系架构如下图所示:

一个完整的通信过程如下:

  1. 用户通过使用支持HTML5的浏览器,在HTTP的基础上,向堡垒机发送websocket请求;
  2. 堡垒机上使用gevent接收websocket请求并转发给Django;
  3. Django接收请求后,调用paramiko建立与远程主机的ssh通道;
  4. 远程主机执行用户的命令后,通过ssh返回数据给Django;
  5. Django通过gevent以websocket的形式返回给用户浏览器;
  6. 用户浏览器使用term.js插件模拟Linux终端,显示远程主机返回的结果。

核心机制就是这样,下面我们来看下开发过程。

三、开发简介

1. 项目创建

堡垒机本身通常是布置在Linux主机上的,比ubuntu16.04,对外以HTTP的形式提供服务。

首先需要建立虚拟环境,并安装Python3.6以及Django2.0,不再赘述。

使用django-admin startprojectpython manage.py startapp app_name分别创建项目和app。

此时,可以尝试运行Django服务,可以看到欢迎界面。

2. ORM模型

任何一个Web项目都必须在深入分析项目需求的情况下,首先设计好ORM模型,也就是数据库的表结构。

IronFort中设计了六个模型,分别是:

  • 远程主机
  • 远程主机用户
  • 远程主机绑定的用户
  • 堡垒机用户
  • 堡垒机用户组
  • 日志

这里需要提醒的是:

  • 每个远程主机账户可以绑定多个远程主机,两者实际是多对多的关系;
  • 堡垒机用户不能直接绑定远程主机;
  • 堡垒机用户绑定的实际是一个主机+主机账户的对象;
  • 考虑账户是否激活或者被经用的enabled属性;
  • 考虑某些字段的unique_together属性;

关于模型设计,每个人有每个人的需求和想法,这其中有很多坑和需要注意的地方,限于篇幅,无法展开论述。在我的个人网站liujiangblog.com的视频教程中有详细的讲解。

模型设计好了,可以同时注册Django的admin后台。然后makemigrations、migrate和createsuperuser,重启服务器后就可以在admin中创建测试用例了,如下图所示:

3. url和路由

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的支持。

4. 前端框架AdminLTE

为了让用户界面美观,我这里使用了基于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页面。

5. 堡垒机用户登录页面

堡垒机用户登录页面不需要使用AdminLTE,最好是单独一个简单的页面,展示的内容越少越好。

而用户登录的处理视图就很简单了,直接使用Django内置的Auth认证系统。

使用Django自带的authenticate和login方法就可以完成用户验证和登录会话。

既然有了登录,必然就要有登出。为了限制未登录用户访问堡垒机系统,所有的相关视图都必须先使用装饰器进行是否登录验证。

通常而言,堡垒机不需要提供面向用户的注册页面。堡垒机用户的注册都是超级管理员掌控的,在后台进行!

6. 主机帐号页面

也就是我们堡垒机用户登录进系统后,显示的默认页面index。这里将通过表格的形式,列出当前堡垒机用户可以使用的远程主机帐号。视图很简单:

@login_required(login_url='/login/')
def index(request):
# ...通过ORM的API查询可使用的帐号
return render(request, 'fort/index.html', locals())

主机账户的前端页面index基于base.html,使用datatable插件,提供搜索、排序和分页等高级功能,其展示效果如下图:

7. 在浏览器中打开websocket通道

百度百科:WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

本文不打算成为一篇websocket的科普文,有兴趣深入研究的可以查看博客园的精华博文WebSocket协议:5分钟从入门到精通

简单的说,有以下几点:

  • HTTP本身是无状态连接,不支持实时通信;
  • websocket基于HTML5,需要浏览器支持;
  • 通过在http报头中添加upgrade属性,申请通信协议升级为websocket;
  • 升级成为websocket通信后,可以实现浏览器和远程服务器之间的全双工实时通信。

关于websocket的使用教程,可以参考阮一峰专家的博文WebSocket 教程

其具体API如下图所示:

要简单的创建并使用一个websocket,按下面的套路就可以了:

  • 使用new WebSocket(url, [protocol] );创建ws对象
  • 使用ws,调用onopen、onmessage、onerror和onclose方法处理通信过程中的数据
  • 使用ws,调用send方法发送数据给后端服务器
  • 使用ws,调用close方法,关闭websocket连接。

我们在主机帐号表格中隐藏一个主机帐号id的字段,通过js代码获取该字段的值,然后启动websocket通信,传递这个id作为参数之一,用于构造websocket通信使用的url。

在浏览器模拟Linux终端方面,我使用的是term.js插件。这是一个开源在github上的浏览器模拟Linux终端的js插件,地址为:https://github.com/chjj/term.js。其官方文档比较简单,有兴趣的同学可以深入研读其源代码,或者使用xterm作为替代。

最终效果如下:

因为此时后端还没有完成,所以是连接不上任何主机的。

8. 创建websocket服务器

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可以启动新的服务器,在浏览器验证一下,都可以正常访问。

9. 在Django中创建视图处理websocket请求

我们前面的根路由中已经写了相关的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")

说明:

  • 获取id对应的远程帐号;
  • 调用WSSHBridge()方法,传入websocket对象和当前用户,创建一个websocket和ssh通信的桥接类,这个类一会我们会介绍。
  • 调用open方法启动ssh通信;
  • 调用shell方法启动终端环境;
  • 通信结束后调用close方法,关闭通道。

那么这里的WSSHBridge类是什么呢?

10. 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这种编辑环境:

可以看到,我们是支持彩色输出的:

11. 日志记录和行为审计

关于用户操作,在数据由websocket往ssh发送过程中,可以保存用户通过前端Linux模拟器终端所敲击的所有按键记录,并且很规整的以回车键进行分隔,非常容易判别。

我们只需要创建一个日志模型,编写一个保存日志的方法,然后在需要的位置保存日志即可。

日志展示页面非常类似主机账户的页面,同样使用datatable插件进行处理,最终效果如下图所示:

至此,基于Webssh的堡垒机核心功能就开发完毕了。限于篇幅,不可能点点滴滴、枝叶不漏的全部叙述,我这里也只是一个抛砖引玉的过程。

四、总结

远程主机的创建、主机账号的管理、堡垒机用户和用户组的管理,这一系列的工作,目前我还是放在admin后台中进行。后期,大家可以将它迁移到堡垒机页面中一起管理。如果将IronFort用于生产环境,添加批量命令执行、文件分发功能,进行系统部署上线、结合Linux运维等等,必然需要大量的额外工作和安全机制,这些就留给大家自己去研究了。

开发基于Django和Websocket的堡垒机的更多相关文章

  1. IronFort---基于Django和Websocket的堡垒机

    WebSSH有很多,基于Django的Web服务也有很多,使用Paramiko在Python中进行SSH访问的就更多了.但是通过gevent将三者结合起来,实现通过浏览器访问的堡垒机就很少见了.本文将 ...

  2. Python开发【第九章】:堡垒机实例

    一.堡垒机前戏 开发堡垒机之前,先来学习Python的paramiko模块,该模块基于SSH用于连接远程服务器并执行相关操作 模块安装 C:\Program Files\Python 3.5\Scri ...

  3. Python_基于Python同Linux进行交互式操作实现通过堡垒机访问目标机

    基于Python同Linux进行交互式操作实现通过堡垒机访问目标机   by:授客 QQ:1033553122 欢迎加入全国软件测试交流群:7156436 实现功能 1 测试环境 1 代码实践 2 注 ...

  4. Django项目:堡垒机(Linux服务器主机管理系统)--03--03堡垒机在Linux系统里记录会话日志02/02

    #main.py #本文件写所有的连接交互动作程序 # ————————————————03堡垒机在Linux系统里记录会话日志 开始———————————————— from Fortress im ...

  5. Django项目:堡垒机(Linux服务器主机管理系统)--02--02堡垒机设计后台交互程序

    #main.py #本文件写所有的连接交互动作程序 #————————————————02堡垒机设计后台交互程序 开始———————————————— from django.contrib.auth ...

  6. Django项目:堡垒机(Linux服务器主机管理系统)--01--01堡垒机重写DJANGO账户表

    python相关软件安装流程图解————————python安装——————python-3.7.1-amd64 https://www.cnblogs.com/ujq3/p/10098166.htm ...

  7. day13--开发堡垒机

    本节内容 项目实战:运维堡垒机开发      商业:<齐治--堡垒机>     前景介绍        https://www.cnblogs.com/alex3714/articles/ ...

  8. Python之路——堡垒机原理及其简单实现

    1 堡垒机基本概述 其从功能上讲,它综合了核心系统运维和安全审计管控两大主干功能,从技术实现上讲,通过切断终端计算机对网络和服务器资源的直接访问,而采用协议代理的方式,接管了终端计算机对网络和服务器的 ...

  9. Pyhton开发堡垒机之paramiko模块

    堡垒机前戏 开发堡垒机之前,先来学习Python的paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作 SSHClient 用于连接远程服务器并执行基本命令 基于用户名密码连接: + ...

随机推荐

  1. Android开发之华为手机无法看log日志解决方法(亲测可用华为荣耀6)

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, 转载请说明出处. 在上家公司上班的时候,公司配了华为荣耀6的测试机,发现在eclipse下 无法查看 ...

  2. AndroidStudio修改程序的包名,可以修改com.example.xxx之类的详解

    转载请说明出处.原创作品. 首先说明一下,当时公司需要修改androidStudio 项目的包名 于是上网查了一下,只看到了修改后面的包名,而不可以修改 前缀的com.example.xxx.所以很无 ...

  3. CTF常见源码泄漏总结

    .hg源码泄漏 漏洞成因: hg init的时候会生成.hge.g.http://www.am0s.com/.hg/ 漏洞利用:工具:dvcs-ripperrip-hg.pl -v -u http:/ ...

  4. Jmeter4.0安装教程

    1. 检查安装环境 1.1 Jdk要求 JDK版本:1.6+ 1.2 检查是否安装JDK win  +  R  快捷键打开运行,输入cmd 打开面板,在面板中输入  java -version,出现如 ...

  5. Zabbix 5.0 LTS版本的安装小结

    Zabbix 5.0 LTS版本的安装小结   1:准备Zabbix的服务器. 这里可能需要一台或多台服务器,视需求和资源而定.也可以将Zabbix_Server.MySQL.Zabbix Web等安 ...

  6. 微信小程序(1)

    微信小程序 什么是微信小程序? 微信小程序优点 跨平台 打开速度比h5快 不用下载 开发目录结构介绍 1. 小程序Pages目录说明 2. 工具文件夹 3. 全局文件 用法1 全局APP.json文件 ...

  7. Spring Boot 整合Redis 实现缓存

      本文提纲 一.缓存的应用场景 二.更新缓存的策略 三.运行 springboot-mybatis-redis 工程案例 四.springboot-mybatis-redis 工程代码配置详解   ...

  8. W5300中文手册

    如果链接没了就Q我吧1178875532 链接:https://pan.baidu.com/s/1HcNJN_T6QJCvPWymU1sFDQ 提取码:suBB

  9. [程序员代码面试指南]递归和动态规划-机器人达到指定位置方法数(一维DP待做)(DP)

    题目描述 一行N个位置1到N,机器人初始位置M,机器人可以往左/右走(只能在位置范围内),规定机器人必须走K步,最终到位置P.输入这四个参数,输出机器人可以走的方法数. 解题思路 DP 方法一:时间复 ...

  10. 记录openssl和openssh升级中遇到的问题以及解决方法

    本文档讲述的升级操作是基于操作系统centos6.5,使用的openssl版本是openssl-1.0.2l.tar.gz,openssh版本是openssh-7.6p1.tar.gz. 1. 依赖检 ...