WebSSH有不少,基於Django的Web服務也有不少,使用Paramiko在Python中進行SSH訪問的就更多了。可是經過gevent將三者結合起來,實現經過瀏覽器訪問的堡壘機就不多見了。本文將簡要介紹下我開發的IronFort堡壘機,其詳細內容在http://study.163.com/course/courseMain.htm?courseId=1005453031&share=2&shareId=400000000495004視頻教程中。html
百度百科:堡壘機,在一個特定的網絡環境下,爲了保障網絡和數據不受來自外部和內部用戶的入侵和破壞,而運用各類技術手段實時收集和監控網絡環境中每個組成部分的系統狀態、安全事件、網絡活動,以便集中報警、及時處理及審計定責。前端
對於一箇中型以上的公司,當用戶和職員人數較多,公司所屬服務器也數量較大的狀況下,其服務器上的賬號管理難度將急劇增長,參考下面的圖片:python
這其中必然存在不少問題,例如:git
在運行初期,公司可能採起Excel表格等工具,使用人工管理的方式,靠‘人治’和道德水平約束,但當公司體量逐漸變大的時候,這種方式必然遭到淘汰,因而就出現了堡壘機的概念,以下圖所示:github
這種架構帶來以下的好處:web
堡壘機的核心概念是用戶再也不掌握賬號密碼,用戶的行爲被記錄用於審計。堡壘機主要針對的是內部網絡和內部人員,對於人員流動性較強、體量大、行業風險高的企業需求特別強烈,好比金融行業。shell
堡壘機已經擁有商業產品,多數以硬件服務器爲載體進行銷售,價格幾十萬不等。也有開源的解決方案,但這些方案有的不是基於瀏覽器,界面不夠友好,日誌記錄困難;有的基於Tornado,而且只能進行簡單的命令執行功能,而公司使用的是Django;更多的狀況是與公司需求不一致,須要二次開發,維護和升級困難,等等不一而足。數據庫
‘授人以魚不如授人以漁’,本身掌握了開發堡壘機的核心技能,就能夠快速、方便、靈活的針對公司具體需求進行定製開發,既爲公司節省了購置硬件經費,又利於維護升級。django
IronFort堡壘機的體系架構以下圖所示:json
一個完整的通訊過程以下:
核心機制就是這樣,下面咱們來看下開發過程。
堡壘機自己一般是佈置在Linux主機上的,比ubuntu16.04,對外以HTTP的形式提供服務。
首先須要創建虛擬環境,並安裝Python3.6以及Django2.0,再也不贅述。
使用django-admin startproject
和python manage.py startapp app_name
分別建立項目和app。
此時,能夠嘗試運行Django服務,若是看到下面的頁面,表示Ok。
Django2.0的歡迎界面比之前漂亮了一點,還帶連接,與時俱進呀。
任何一個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運維等等,必然須要大量的額外工做和安全機制,這些就留給你們本身去研究了。
另外,我在官方主頁劉江的博客和教程中,還有一個關於CMDB主機管理系統的教程,將它結合到堡壘機項目中來,一個運維管理平臺的核心底層功能就基本具有了,在此基礎上進行擴展,大有可爲!
最後,我的技術和能力有限,必然存在不足之處,請你們輕噴,多提寶貴意見,謝謝!