P2.6: закрыть /metrics Bearer admin-токеном
/metrics отдаёт per-tenant данные (accessKey, число очередей и сообщений), что является утечкой наружу на публичном endpoint. Теперь /metrics защищён тем же Bearer admin-токеном (timing-safe сравнение). Скрепперу VM токен задаётся в конфиге.
This commit is contained in:
+21
-2
@@ -4,6 +4,7 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -41,8 +42,10 @@ func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler {
|
|||||||
// /static — логотип, favicon для публичных страниц
|
// /static — логотип, favicon для публичных страниц
|
||||||
r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET")
|
r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET")
|
||||||
|
|
||||||
// /metrics — Prometheus endpoint для Victoria Metrics scrape
|
// /metrics — Prometheus endpoint для Victoria Metrics scrape.
|
||||||
r.Handle("/metrics", promhttp.Handler()).Methods("GET")
|
// Защищён Bearer admin-токеном: метрики содержат per-tenant данные
|
||||||
|
// (accessKey/число очередей и сообщений), которые нельзя светить наружу.
|
||||||
|
r.Handle("/metrics", metricsAuth(adminToken, promhttp.Handler())).Methods("GET")
|
||||||
|
|
||||||
// Страница примеров команд — публично
|
// Страница примеров команд — публично
|
||||||
r.HandleFunc("/examples", func(w http.ResponseWriter, req *http.Request) {
|
r.HandleFunc("/examples", func(w http.ResponseWriter, req *http.Request) {
|
||||||
@@ -181,6 +184,22 @@ func health(w http.ResponseWriter, req *http.Request) {
|
|||||||
fmt.Fprintf(w, `{"status":"ok","version":%q}`, models.Version)
|
fmt.Fprintf(w, `{"status":"ok","version":%q}`, models.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// metricsAuth — оборачивает /metrics в проверку Bearer admin-токена.
|
||||||
|
// В отличие от публичного /health, метрики содержат per-tenant данные
|
||||||
|
// (accessKey, число очередей/сообщений) и не должны быть доступны без auth.
|
||||||
|
func metricsAuth(adminToken string, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
expected := "Bearer " + adminToken
|
||||||
|
if len(authHeader) != len(expected) ||
|
||||||
|
subtle.ConstantTimeCompare([]byte(authHeader), []byte(expected)) != 1 {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func actionHandler(w http.ResponseWriter, req *http.Request) {
|
func actionHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
action := extractAction(req)
|
action := extractAction(req)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|||||||
Reference in New Issue
Block a user