prometheus 添加 login 登錄認證界面(nginx + flask 實現)


前言

    prometheus是現在一個比較主流的監控軟件,隨着容器的普及,prometheus的應用越來越廣泛,前面我也有專門講到prometheus的相關文章。但是跟傳統老牌的zabbix監控不一樣,prometheus的web UI是沒有登錄認證的,有時候顯得沒有安全性,本文就主要講解一下如何為prometheus添加一個登錄認證界面。其實像elasticsearch、consul等一些服務的web UI也是沒有登錄認證的,都可以使用本文的方式進行實現。
 

1. 實現思路

    主要是通過nginx代理轉發進行實現,我們可以在nginx轉發到prometheus之前添加一層認證的過程,從而進行實現。當然,如果有實力的朋友也可以修改prometheus的源碼來添加認證機制。
 

1.1 nginx auth_basic 方式

    nignx的ngx_http_auth_basic_module模塊實現了訪問nignx web頁面必須輸入用戶名和密碼,經過認證后才能進行訪問,我們可以利用這個特性來為prometheus設置代理。
    該實現方式比較簡單,只需要在nginx配置文件里面添加上auth_basic相關的參數即可,網上也有很多資料,這里就不在贅述了。
 

1.2 nginx auth_request 方式

    有時候我們需要自定義一個 web 登錄網頁作為我們的監控系統的登錄入口,這就可以結合 auth_request 模塊來實現。
 
auth_request原理:
(1)當auth_request對應的路由返回401或者403時,nginx會攔截請求,直接返回前端401或者403信息;
(2)當auth_request對應的路由返回2xx狀態碼時,nginx不會攔截請求,而是構建一個subrequest請求,再去請求真實受保護資源的接口;
 
登錄認證實現思路:
(1)通過nginx代理prometheus訪問,初次訪問首頁時,auth_request返回401,讓其強制跳轉到我們自定義的login 登錄界面;
(2)在login登錄認證界面,如果用戶名密碼認證正確,返回一個token,並且重定向到nginx首頁;
(3)此時再次訪問首頁時,是帶着token來進行訪問,驗證token正確,auth_request返回200,就成功轉發 prometheus監控頁面;
(4)如果token過期,登錄首頁時就返回到login頁面,再次進行用戶名密碼認證。
 

2.實現代碼

2.1 nginx配置文件

將如下配置添加到nginx的配置文件的 http{} 部分里面

server {
  listen 0.0.0.0:9190;			 # 訪問首頁入口
  location / {
    proxy_pass http://localhost:9090/;   # prometheus服務監聽端口
    auth_request /auth;
    error_page 401 = @error401;
  }

  location @error401 {               # 401就轉發到登錄頁面
    add_header Set-Cookie "ORIGINURL=$scheme://$http_host;Path=/";
    return 302 /login;
  }

  location /auth {
   # internal;
    proxy_pass http://localhost:5000/auth;	# 后端token認證
    auth_request off;
  }

  location /login {
    proxy_pass http://localhost:5000/login;	# 后端用戶名密碼認證
    auth_request off;
  }

  location /static/rainbowhhy {   # 此處很重要,需要自定義一個靜態文件目錄,本文為rainbowhhy,否則會與prometheus的靜態文件沖突,導致prometheus的頁面加載不完全
  proxy_pass http://localhost:5000/static/rainbowhhy;
  auth_request off;
    }
}

 

2.2 登錄認證

登錄認證部分是通過 flask 實現
代碼目錄結構如下

├── profiles.json
├── readme.md
├── requirements.txt
├── run.py
├── static
│   └── rainbowhhy
│       ├── css
│       │   └── style.css
│       └── js
│           └── jquery-1.8.2.min.js
└── templates
    └── login.html

安裝包准備

pip3 install flask==1.1.1
pip3 install flask-login==0.4.1
pip3 install werkzeug==0.16.0

 

2.2.1 密碼加密文件

profiles.json,采用json格式保存加密后的用戶名和密碼

cat profiles.json 
{"admin": ["pbkdf2:sha256:150000$8J65mjTc$db116dd4d5de7eff899d126bd57b4f73910afb1e57982a9ded6878c547b584c5"]}

生成密碼的方式:

>>> from werkzeug.security import generate_password_hash
>>> generate_password_hash("12345678")
'pbkdf2:sha256:150000$8J65mjTc$db116dd4d5de7eff899d126bd57b4f73910afb1e57982a9ded6878c547b584c5'

 

2.2.2 后端認證服務

run.py,實現了登錄認證過程

from flask import Flask, request, render_template
from flask_login import UserMixin
from werkzeug.security import check_password_hash
import json
import os

app = Flask(__name__)
app.config["SECRET_KEY"] = "123456"
app.secret_key = '123456'

# 存放用戶名和密碼的json文件
PROFILE_PATH = os.path.dirname(os.path.abspath(__file__))
PROFILE_FILE = os.path.join(PROFILE_PATH, "profiles.json")


# 用戶名密碼加密認證
class User(UserMixin):
    def __init__(self, username, password):
        self.username = username
        self.password_hash = self.get_password_hash()

    def verify_password(self, password):
        if self.password_hash is None:
            return False
        return check_password_hash(self.password_hash, password)

    def get_password_hash(self):
        # 從文件中獲取密碼
        try:
            with open(PROFILE_FILE) as f:
                user_profiles = json.load(f)
                user_info = user_profiles.get(self.username, None)
                if user_info is not None:
                    return user_info[0]
        except:
            print("get password error!")


@app.route("/auth", methods=["GET", "POST"])
def auth():
    url = request.cookies.get('ORIGINURL')
    token = request.cookies.get('token')
    if token == "ABCDE":
        return ("success", 200)
    else:
        return ("go to login", 401)


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        user = User(username, password)
        if user.verify_password(password):
            token = "ABCDE"
            return (token, 200)
        else:
            error = "用戶名或密碼錯誤..."
            return (error, 403)
    else:
        return render_template("login.html")


if __name__ == '__main__':
    app.config['JSON_AS_ASCII'] = False
    app.run(host="localhost", port=5000)

 

2.2.3 前端登錄頁面

login.html,簡單實現了登錄認證的前端web

<!DOCTYPE html>
<html>

<head>
        <title>監控系統</title>
        <link type="text/css" rel="stylesheet" href="../static/rainbowhhy/css/style.css">
</head>

<body>
        <div class="head">
                賬號:<input type="text" name="username" id="username" />
                <br />
                密碼:<input type="password" name="password" id="password" />
                <br />
                <input type="button" onclick="token()" value="登錄" />
                <div class="flash error" role="alert"></div>
        </div>


        <script type="text/javascript" src="../static/rainbowhhy/js/jquery-1.8.2.min.js"></script>
        <script type="text/javascript">
                function token() {
                        var url = "http://" + document.domain + ":" + location.port;
                        console.log(url);
                        var username = document.getElementById("username").value;
                        var password = document.getElementById("password").value;
                        var fd = new FormData();
                        fd.append("username", username);
                        fd.append("password", password);
                        xhr = new XMLHttpRequest();
                        xhr.open("POST", "/login");
                        xhr.send(fd);
                        xhr.onreadystatechange = function (res) {
                                if (xhr.readyState == 4 && xhr.status == 200) {
                                        // 登錄成功則成功跳轉
                                        console.log("success");
                                        var token = xhr.response;
                                        setCookie("token", token);
                                        location.href = url;
                                }
                                if (xhr.readyState == 4 && xhr.status == 403) {
                                        // 登錄失敗則重新登錄
                                        var error = xhr.response;
                                        $(".flash")[0].innerHTML = error;
                                        $(".flash").fadeOut(3000);
                                        setTimeout(function () {
                                                location.href = url + "/login";
                                        }, 2000);
                                }
                        }
                }

                function setCookie(name, value) {
                        // 設置token有效期為60min
                        const exp = new Date();
                        exp.setTime(exp.getTime() + 60 * 60 * 1000);
                        document.cookie = name + "=" + value + ";expires=" + exp.toGMTString();
                }
        </script>
</body>

</html>

 
style.css,為了體現出自定義一個靜態文件目錄的重要性,這里特地寫了一個簡單的自定義css

.head {
    width: 500px;
    height: 200px;
    margin: 0 auto;
}

.error {
    color:red;
    font-size: 18px;
    margin: 0 auto;
}

 

3.啟動服務

啟動nginx服務

systemctl start nginx

啟動flask認證服務

python3 run.py
生產上可以做成systemd或者supervisor的方式啟動

 
之后就可以訪問了,實現的效果
 

 
文本實現了一個比較簡單的登錄認證頁面,大家可以根據實際情況進行代碼修改,使得login頁面更加完善美觀,當然如果你的公司沒有要求這么多,就可以直接使用本文的思路中的第一種:nginx auth_basic 方式,更加方便快捷。
 
如下,上一張完善的登錄界面:

 
附完整代碼:https://github.com/Rainbowhhy/prometheus_login_webUI
 

參考文檔

http://nginx.org/en/docs/http/ngx_http_auth_request_module.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM