From b7f79b388f635f287aba9d6bdcd11e9f4fa6ce26 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Tue, 6 Oct 2026 18:16:27 +0300 Subject: [PATCH] =?UTF-8?q?P2.6:=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D1=8C=20/metrics=20Bearer=20admin-=D1=82=D0=BE=D0=BA=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /metrics отдаёт per-tenant данные (accessKey, число очередей и сообщений), что является утечкой наружу на публичном endpoint. Теперь /metrics защищён тем же Bearer admin-токеном (timing-safe сравнение). Скрепперу VM токен задаётся в конфиге. --- app/router/router.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/app/router/router.go b/app/router/router.go index 3d54fa5..8d9105e 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -4,6 +4,7 @@ package router import ( + "crypto/subtle" "encoding/json" "encoding/xml" "fmt" @@ -41,8 +42,10 @@ func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler { // /static — логотип, favicon для публичных страниц r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET") - // /metrics — Prometheus endpoint для Victoria Metrics scrape - r.Handle("/metrics", promhttp.Handler()).Methods("GET") + // /metrics — Prometheus endpoint для Victoria Metrics scrape. + // Защищён Bearer admin-токеном: метрики содержат per-tenant данные + // (accessKey/число очередей и сообщений), которые нельзя светить наружу. + r.Handle("/metrics", metricsAuth(adminToken, promhttp.Handler())).Methods("GET") // Страница примеров команд — публично 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) } +// 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) { action := extractAction(req) start := time.Now()