搭建一個(gè) 802.1x 的 web 測試服務(wù)

前言

802.1x 是一種二層認(rèn)證協(xié)議扬卷,結(jié)合 EAP,它能夠?yàn)闊o線網(wǎng)絡(luò)提供安全的酸钦,無感知的認(rèn)證服務(wù)怪得。因此許多大型的園區(qū)網(wǎng)都選擇使用 802.1x 作為無線網(wǎng)絡(luò)的認(rèn)證模式。
大名鼎鼎的 eduroam 也是完全基于 802.1x 認(rèn)證的全球無線漫游網(wǎng)絡(luò)卑硫。
由于 802.1x 的特性徒恋,在進(jìn)行基于 802.1x 的無線認(rèn)證時(shí),對終端本身有一定的要求欢伏。因此 802.1x 的無線網(wǎng)絡(luò)排障就比較討厭入挣,因?yàn)槠渲杏邢喈?dāng)一部分屬于終端側(cè)的配置或兼容問題。
對于 eduroam 漫游時(shí)就更加麻煩了硝拧,管理員很難確認(rèn)到底是漫游所在地的 eduroam-sp 存在問題径筏,還是賬號所在地的 eduroam-idp 認(rèn)證存在異常。
因此障陶,最好能有一個(gè)基于 web 的 802.1x 認(rèn)證測試服務(wù)匠璧,管理員測試一下就知道是哪邊出的問題。
這樣的服務(wù)國外有學(xué)校提供咸这,例如這個(gè) Masarykova univerzita
現(xiàn)在我們來自己實(shí)現(xiàn)一個(gè)

輪子

要做這件事夷恍,核心的功能就是要構(gòu)造模擬的認(rèn)證報(bào)文,并且需要結(jié)合
EAP媳维∧鹧考慮到普遍性,最先應(yīng)該支持的應(yīng)該是 mschapv2 這個(gè)侄刽。所以當(dāng)然是先找輪子指黎。。州丹。
https://github.com/Bonn93/radius-python-sanity-check
找到了輪子醋安,測試可以進(jìn)行 mschapv2 的驗(yàn)證,剩下的事情就好辦了墓毒,在輪子的基礎(chǔ)上封裝就好了

需求

先整理一下需求:

  • 我們需要一個(gè) web 表單吓揪,讓用戶輸入他的用戶名和密碼進(jìn)行測試,并返
    回結(jié)果
  • 不同的 SSID 應(yīng)該能生成對應(yīng)不同的表單頁面
  • 得加一個(gè)驗(yàn)證碼來防止壞人猜密碼
  • 最好再有個(gè) API 可供程序調(diào)用所计,這樣可以構(gòu)建自動(dòng)化的監(jiān)控
  • API 得有訪問控制

實(shí)現(xiàn)

因?yàn)檩喿邮?Python 的柠辞,所以整個(gè)服務(wù)肯定也就用 Python 來實(shí)現(xiàn)了,用 flask 來做主胧。下面做一些簡單的介紹叭首,完整代碼見 GITHUB
目錄結(jié)構(gòu)如下:

├── app
│   ├── framd.ttf  #字體文件习勤,用來生成驗(yàn)證碼的
│   ├── __init__.py
│   ├── static # 靜態(tài)文件,css,js 之類的
│   ├── templates # web 模板 
│   ├── utils.py 
│   └── views.py 
├── config.py # 配置文件
├── control # 啟動(dòng)腳本
├── gunicorn.conf # gunicorn 的配置文件
├── lib # radius 報(bào)文的封裝 lib(就是我們找的那個(gè)輪子)
│   ├── bidict.py
│   ├── client.py
│   ├── dictfile.py
│   ├── dictionary.py
│   ├── dicts
│   ├── host.py
│   ├── __init__.py
│   ├── mschap2.py
│   ├── packet.py
│   └── tools.py
├── LICENSE
├── README-CN.md
├── README.md
├── requirement.txt
├── run.py
└── var
    └── app.log

先從 config.py 說起,我們的配置都放在這兒


# BASIC APP CONFIG
BIND_ADDRESS = '0.0.0.0'
PORT = 81
SECRET_KEY = "session_secret_key"  # 建立 session 所使用的 key焙格,session 用來存驗(yàn)證碼

# SSID CONFIG
SSID_CONFIG = {
        "test1x":
            {"RADIUS_HOST":"192.168.0.210","RADIUS_SECRET":"802.1x","RADIUS_PORT":1812,"NAS_IP":"192.168.80.5"},
        "eduroam":
            {"RADIUS_HOST":"192.168.0.220","RADIUS_SECRET":"eduroam","RADIUS_PORT":1812,"NAS_IP":"192.168.80.5"},
        }
# 字典的 key 將作為 url 的路徑图毕,例如 https://test.edu.cn/test/eduroam

# API_KEY
API_KEY = "0c8d964e8fbd4cfcd040b5691d119968"

因?yàn)槲覀兊臒o線網(wǎng)絡(luò)可能有多個(gè) SSID,并且不同的 SSID 對應(yīng)的后端是不同的 Radius眷唉。因此需要根據(jù)每個(gè) SSID 配置不同的 Radius 配置吴旋。這里的 SSID ,同時(shí)也會(huì)成為最后生成的測試 URL厢破,例如 https://test.example.cn/test/eduroam

挑戰(zhàn)報(bào)文

def radius_challenge(username, password, host, secret, port, nasip, debug):
        hostname = gethostname()
        dict_path = sys.path[0] + "/lib/dicts/dictionary"
        radius = Client(server = host, secret = secret, authport = port, dict = Dictionary(dict_path))
        request = radius.CreateAuthPacket(code = packet.AccessRequest)
        if debug:
                print "[DEBUG] assembling packet attributes"
        request["User-Name"] = username
        request["NAS-IP-Address"] = nasip
        request["NAS-Identifier"] = hostname
        ……
        ……

我們把輪子里的代碼重新封裝一下荣瑟,構(gòu)造一個(gè)函數(shù)出來負(fù)責(zé)發(fā)送 Radius 挑戰(zhàn)報(bào)文。后面就用調(diào)用它來做 Radius 的測試

驗(yàn)證碼

由于我們提供的是一個(gè)開放的測試表單摩泪,因此必須要有驗(yàn)證碼笆焰,否則壞人就要暴力猜密碼叻。我們使用 PIL 庫進(jìn)行驗(yàn)證碼圖像的生成见坑,并把驗(yàn)證碼圖像對應(yīng)的數(shù)字存在 session 里面嚷掠。

@app.route('/code', methods=['GET'])
def code():
    """生成驗(yàn)證碼
    """
    from io import BytesIO

    output = BytesIO()
    code_img, code_str = create_validate_code() # 生成驗(yàn)證碼的函數(shù),在 utils.py 里
    code_img.save(output, 'jpeg')
    img_data=output.getvalue()
    output.close()
    response = make_response(img_data)
    response.headers['Content-Type'] = 'image/jpg'
    session['code_text'] = code_str # 把正確的驗(yàn)證碼數(shù)字放進(jìn) session 里待校驗(yàn)
    return response

表單

@app.route('/test/<string:ssid>', methods=['GET','POST'])
def radius_test(ssid):
        ssid_config = app.config['SSID_CONFIG']
        if ssid not in ssid_config:
                return "404 page not found",404
        if request.method == 'GET':
                return render_template('radius1x.html',ssid=ssid)
        ……
        ……

根據(jù)配置不同的 SSID 生成不同的表單 URL荞驴,通過校驗(yàn) session 中的驗(yàn)證碼來判斷驗(yàn)證碼是否正確不皆。然后將收到的用戶名和密碼發(fā)送給 Radius 做測試

API

@app.route('/api/v1/<string:ssid>', methods=['POST'])
def radius1x_api(ssid):
    ssid_config = app.config['SSID_CONFIG']
    if ssid not in ssid_config:
        return "404 page not found",404
        ……
        ……

提供 API 的接口,供程序來調(diào)用熊楼,通過配置文件中的 API_KEY 進(jìn)行鑒權(quán)

部署

flask 內(nèi)置了一個(gè) WSGI 服務(wù)器霹娄,我們測試的時(shí)候可以 python run.py 直接跑起來。然而一來這樣沒法后臺(tái)運(yùn)行鲫骗,二來 flask 內(nèi)置的 WSGI 性能有點(diǎn)寒磣的犬耻,我們得通過其他方法來做生產(chǎn)環(huán)境的部署
我們使用 gunicorn 來進(jìn)行作為生產(chǎn)環(huán)境的 WSGI 服務(wù),然后參考 Open-Falcon 里 Dashboard 的控制方式执泰,我們來抄一下它的 Control 腳本~

#!/bin/bash

WORKSPACE=$(cd $(dirname $0)/; pwd)
cd $WORKSPACE

mkdir -p var

module=1xtest
app=radius-$module
pidfile=var/app.pid
logfile=var/app.log

function check_pid() {
    if [ -f $pidfile ];then
        pid=`cat $pidfile`
        if [ -n $pid ]; then
            running=`ps -p $pid|grep -v "PID TTY" |wc -l`
            return $running
        fi
    fi
    return 0
}

function start() {
    source env/bin/activate
    hash gunicorn 2>&- || { echo >&2 "I require gunicorn but it's not installed.  Aborting."; exit 1; }

    check_pid
    running=$?
    if [ $running -gt 0 ];then
        echo -n "$app now is running already, pid="
        cat $pidfile
        return 1
    fi

    gunicorn -c gunicorn.conf run:app -D -t 6000 --pid $pidfile --error-logfile $logfile --log-level info
    sleep 1
    echo -n "$app started..., pid="
    cat $pidfile
}
……
……

這樣就可以使用 ./control start,./control stop 來進(jìn)行服務(wù)啟停了枕磁。

最后再給他套一層 nginx 作為反向代理,把 ssl 證書部上术吝,提供 https 服務(wù)计济。同時(shí)再做一個(gè) http 到 https 的重寫,這樣就差不多了排苍。

運(yùn)行截圖

image.png
[root@host ~]# curl -H "Content-Type: application/json" -d '{"username":"test01@test.edu.cn","password":"test123","token":"0c8d964e8fbd4cfcd040b5691d119968"}' "https://test.edu.cn/api/v1/eduroam"
{
  "result": {
    "method": "mschapv2", 
    "success": true, 
    "time": 0.36426687240600586, 
    "username": "test01@test.edu.cn"
  }
}

聯(lián)盟監(jiān)控

eduroam 是一個(gè)全球的無線漫游聯(lián)盟沦寂,利用 Radius Proxy 來實(shí)現(xiàn)的。進(jìn)行 eduroam 認(rèn)證的時(shí)候需要講自己的用戶名加上@域名纪岁,例如 username@ecnu.edu.cn凑队,eduroam-sp 則將 radius 認(rèn)證請求轉(zhuǎn)發(fā)給上一級節(jié)點(diǎn)则果,上級節(jié)點(diǎn)再根據(jù)域名轉(zhuǎn)發(fā)給對應(yīng)的節(jié)點(diǎn)/或者再向上級轉(zhuǎn)發(fā)幔翰,和 dns 有點(diǎn)像的漩氨。
中國教科網(wǎng)在 2015 年也加入了 eduroam,目前國內(nèi)有 50 余所高校都提供 eduroam 的全球漫游服務(wù)遗增。想想自己的賬號跑到其他學(xué)校也能直接上網(wǎng)叫惊,這確實(shí)是件蠻爽的事情。
然而對于 eduroam 的聯(lián)盟運(yùn)維而言做修,聯(lián)盟規(guī)模越大霍狰,運(yùn)維越是麻煩。每個(gè)節(jié)點(diǎn)出故障的概率如果是0.1%饰及,50 個(gè)節(jié)點(diǎn)至少有一個(gè)出故障的概率就是4.9%蔗坯,聯(lián)盟規(guī)模越大,出故障的概率越高燎含。對于聯(lián)盟的運(yùn)維而言宾濒,我們需要能夠快速發(fā)現(xiàn)故障,快速定位屏箍,快速修復(fù)绘梦。
因此我們可以考慮從每個(gè) eduroam-idp 討一個(gè)測試賬號。做一個(gè)探針腳本調(diào)用 API 進(jìn)行逐個(gè)測試赴魁,把測試結(jié)果推到監(jiān)控平臺(tái)里面卸奉。
于是我們就可以完整的監(jiān)控到每一個(gè) eduroam-idp 的服務(wù)是否可用,響應(yīng)時(shí)間颖御,并在宕機(jī)的時(shí)候及時(shí)的發(fā)出告警榄棵。從而提升整個(gè)聯(lián)盟的運(yùn)維水平

以上

轉(zhuǎn)載授權(quán)

CC BY-SA

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市潘拱,隨后出現(xiàn)的幾起案子秉继,更是在濱河造成了極大的恐慌,老刑警劉巖泽铛,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尚辑,死亡現(xiàn)場離奇詭異,居然都是意外死亡盔腔,警方通過查閱死者的電腦和手機(jī)杠茬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弛随,“玉大人瓢喉,你說我怎么就攤上這事∫ㄍ福” “怎么了栓票?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我走贪,道長佛猛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任坠狡,我火速辦了婚禮继找,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逃沿。我一直安慰自己婴渡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布凯亮。 她就那樣靜靜地躺著边臼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪假消。 梳的紋絲不亂的頭發(fā)上硼瓣,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機(jī)與錄音置谦,去河邊找鬼堂鲤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛媒峡,可吹牛的內(nèi)容都是我干的瘟栖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谅阿,長吁一口氣:“原來是場噩夢啊……” “哼半哟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起签餐,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寓涨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后氯檐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戒良,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年冠摄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糯崎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡河泳,死狀恐怖沃呢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拆挥,我是刑警寧澤薄霜,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響惰瓜,放射性物質(zhì)發(fā)生泄漏否副。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一鸵熟、第九天 我趴在偏房一處隱蔽的房頂上張望副编。 院中可真熱鬧负甸,春花似錦流强、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚕捉,卻和暖如春奏篙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背迫淹。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工秘通, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敛熬。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓肺稀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親应民。 傳聞我的和親對象是個(gè)殘疾皇子话原,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容