基於Django設計Kibana用戶認證方案
?前段時間,負責ELK那哥們兒想把Kibana調整成為LDAP內部用戶認證,運維這邊了解到這個需求,著手調研。
解決方案及思路
?首先Kibana現在只是用了一個叫Search guard的插件來控制訪問,但是這個插件需要和JIRA用戶統一認證,需要手動開通賬戶,維護成本高,並且實施起來的效率也很低,所以才打算搭建一個LDAP服務器,然後同步JIRA用戶信息,實現Kibana統一認證。
?經過討論方案,大致的初步方案就是通過結合Nginx的auth_request模塊,當用戶請求一個受特定保護的資源時候,auth_request會將請求轉發到LDAP驗證服務上,然後根據驗證服務返回的狀態碼決定重定向或者允許訪問等進一步的操作。其中在Nginx配置文件中的LDAP認證服務中需要完善LDAP服務器的配置信息。大概的思路就是這樣,想到這裏的時候,又發現一個問題,如何保證LDAP和JIRA用戶能夠及時同步呢,並且各種權限的信息也需要相應的對接。
?這時候負責JIRA的哥們兒,說其實我們那邊有個接口,可以通過POST用戶名和密碼信息,驗證是否能夠登錄。並且驗證通過會返回一個有關用戶信息的JSON,如果想要進行權限控制的話,也可以通過這個JSON。所以現在事情就變得簡單了,重新簡單梳理一下解決方案。
?如下:
- 1、用戶請求受保護的Nginx反向代理資源
- 2、Nginx的auth_request模塊將請求轉發給自己寫的django的驗證服務中。判斷cookie解密後是否正確。如果都符合條件則返回200狀態碼,nginx不會攔截請求,而是構建一個subprocess請求受保護的本地資源。
- 3、如果django驗證服務沒有通過,則會返回一個401請求,nginx對401請求進行攔截並且重定向到登錄頁面,所以就返回到django的login服務中,展示登錄界面。
- 4、在登錄界面中,輸入用戶名和密碼提交,傳到後端時,後端使用內部接口,發送用戶名和密碼,通過返回的狀態碼驗證是否成功,成功之後將用戶名加密之後放入cookie並且重定向到受保護的資源中;驗證失敗則返回提示用戶名或者密碼失敗。
詳細設計
前期準備工作
版本選擇:
Nginx 1.14.1
Django 1.9.13
Kibana 6.5.1
Python 2.7
需要註意事項: 1、Nginx需要註意的點就是,默認yum安裝的Nginx沒有編譯auth_request模塊, 所以需要到官網重新下載源碼,增加--with-http_auth_request_module進行編譯。 2、代碼中需要用到Python的requests、Crypto 模塊需要額外安裝
Nginx 1.4.1下載地址:
http://nginx.org/download/nginx-1.14.1.tar.gz
requests 模塊安裝:
pip install requests
Crypto模塊安裝:
pip install Crypto
安裝成功後,可分別通過nginx -V、pip show requests、pip show Crypto驗證:
代碼分析
?最開始設計後端的驗證服務時參考了Nginx官網上,Nginx結合ldap的身份驗證demo,demo地址:
?https://github.com/nginxinc/nginx-ldap-auth
?demo中大致的思路就是接受請求,首次訪問將會重定向到login頁面上,loging登錄後,將接收到的用戶名和密碼,去查詢ldap服務器上的信息,成功後將把用戶名和密碼以?;?號連接後做 base64 寫入 cookie,下一次訪問受保護的資源時,然後寫?Location?裏寫入?target?的值,來實現重定向跳回。
?但是demo中首先的問題就是用戶和密碼這種敏感的信息不應該放入cookie中,並且在demo中用到了簡單的BaseHTTPServer?,用這個模塊就會出現幾個問題:
- 1、不支持url的解析和轉發,需要用戶自己解析
- 2、回寫的響應需要自己維護格式,容易出錯
- 3、沒有模板支持,如果需要寫HTML頁面,也需要自己維護。
? 所以為了解決以上的缺點,剛好對於Django有一定的了解,就打算基於Django框架實現用戶驗證的服務,以及登錄時模板頁面。廢話不多說來看看代碼。
代碼詳解:首先是驗證服務,通過檢查是否存在cookie,不存在的話就返回狀態碼401;如果存在的話,通過將cookie解密,獲取其中關鍵的字段,判斷是否登錄,如果解密成功的話,返回狀態碼200,解密失敗的話,返回狀態碼401。
class prcrypt(object):
def __init__(self,key):
if len(key) < 16:
key = key+(16-len(key))*‘\0‘
self.key = key
self.mode = AES.MODE_CBC
def encrypt(self,text):
cryptor = AES.new(self.key,self.mode,IV=self.key)
length = 16
count = len(text)
add = count % length
if add :
text = text + (‘\0‘ * (length-add))
self.ciphertext = cryptor.encrypt(text)
return base64.b64encode(self.ciphertext)
def decrypt(self,text):
cryptor = AES.new(self.key,self.mode,IV=self.key)
plain_text = cryptor.decrypt(base64.b64decode(text))
return plain_text.rstrip(‘\0‘)
代碼詳解: 這段加密操作通過 aes 模塊進行加密,大概原理首先傳入一個 key 值,作為實例化 AES對象的密鑰 。然後將需要加密的文本補足成 16的倍數 進行加密操作,最後將加密後的文本 base64 進行轉換。解密的話 逆操作 即可。
代碼詳解:首先前面一部分代碼是接收前端傳來的用戶名和密碼,並且通過requests模塊post到指定的接口地址,然後解析返回的結果,根據返回結果的狀態碼判斷是否能登陸成功。
第二部分通過判斷登錄成功後,將用戶名和登錄狀態加密,設置cookie,指定超時時間一小時,只能由http協議傳輸。然後重定向到受保護的資源,受保護的資源就會再次請求驗證服務,驗證服務對cookie進行判斷正確後就會允許訪問受保護資源;如果登錄失敗則會返回無效用戶名和密碼到登錄界面。
前端登錄界面:
配置分析
http {
include /etc/nginx/mime.types;
upstream backend {
server 192.168.128.5:5601;
}
#需要受保護的Kibana程序
server {
listen 8081;
# nginx服務開放8081端口
location / {
auth_request /auth-proxy;
error_page 401 =200 /login;
proxy_pass http://backend/;
}
#這個路徑下受auth-request保護,所有401的請求都會重定向到login上
location /login {
proxy_pass http://127.0.0.1:8888/login;
proxy_set_header X-Target $request_uri;
}
# 這是我們認證的頁面,指向我們django項目中的login路徑
location /auth-proxy {
internal;
proxy_pass http://127.0.0.1:8888;
}
# 這裏用作auth-request請求的路徑
location /static{
alias /data/htdocs/www/elk/static/;
}
}
}
上面就是主要的Nginx配置文件
urlpatterns = [
url(r‘^$‘,login),
url(r‘^login‘,login),
url(r‘^auth-proxy‘,nginx_auth),
url(r‘^admin/‘, include(admin.site.urls)),
]
對應django項目的urls文件
總結
?其實這個用戶認證的解決方案不難理解,比較麻煩就是最初需求了解確定以及和其他項目負責人溝通的問題。在技術上單純的沒什麽難點。
?通過在網上查資料,還有Nginx官網上的一些demo有助於你迅速了解到模塊的用處,以及對一些問題的解決方案,不少網友也遇到了類似的問題,在他們身上多總結經驗,有助於自己解決問題,所以一句話“多動手,多溝通,知行合一”。
?以下是我參考的一些網站和博客,大家有興趣可以看看:
?Nginx官網上利用auth_request結合LDAP的認證方案:
?https://www.nginx.com/blog/nginx-plus-authenticate-users/?用 Nginx 的 auth_request 模塊集成 LDAP 認證:
?https://www.jianshu.com/p/9f2da3cf5579
?python的aes加密和解密:
?https://my.oschina.net/u/1458120/blog/648350
基於Django設計Kibana用戶認證方案