diff --git a/lib/httpserver/httpserver.go b/lib/httpserver/httpserver.go index e7f6157a5..c8b40a5e1 100644 --- a/lib/httpserver/httpserver.go +++ b/lib/httpserver/httpserver.go @@ -372,6 +372,7 @@ func CheckAuthFlag(w http.ResponseWriter, r *http.Request, flagValue string, fla return CheckBasicAuth(w, r) } if r.FormValue("authKey") != flagValue { + authKeyRequestErrors.Inc() http.Error(w, fmt.Sprintf("The provided authKey doesn't match -%s", flagName), http.StatusUnauthorized) return false } @@ -386,9 +387,13 @@ func CheckBasicAuth(w http.ResponseWriter, r *http.Request) bool { return true } username, password, ok := r.BasicAuth() - if ok && username == *httpAuthUsername && password == *httpAuthPassword { - return true + if ok { + if username == *httpAuthUsername && password == *httpAuthPassword { + return true + } + authBasicRequestErrors.Inc() } + w.Header().Set("WWW-Authenticate", `Basic realm="VictoriaMetrics"`) http.Error(w, "", http.StatusUnauthorized) return false @@ -442,6 +447,8 @@ var ( pprofDefaultRequests = metrics.NewCounter(`vm_http_requests_total{path="/debug/pprof/default"}`) faviconRequests = metrics.NewCounter(`vm_http_requests_total{path="/favicon.ico"}`) + authBasicRequestErrors = metrics.NewCounter(`vm_http_request_errors_total{path="*", reason="wrong basic auth creds"}`) + authKeyRequestErrors = metrics.NewCounter(`vm_http_request_errors_total{path="*", reason="wrong auth key"}`) unsupportedRequestErrors = metrics.NewCounter(`vm_http_request_errors_total{path="*", reason="unsupported"}`) requestsTotal = metrics.NewCounter(`vm_http_requests_all_total`) diff --git a/lib/httpserver/httpserver_test.go b/lib/httpserver/httpserver_test.go index f3bf43668..e71f92263 100644 --- a/lib/httpserver/httpserver_test.go +++ b/lib/httpserver/httpserver_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strings" "testing" ) @@ -36,6 +37,93 @@ func TestGetQuotedRemoteAddr(t *testing.T) { f("1.2\n\"3.4", "foo\nb\"ar", `"1.2\n\"3.4, X-Forwarded-For: foo\nb\"ar"`) } +func TestBasicAuthMetrics(t *testing.T) { + origUsername := *httpAuthUsername + origPasswd := *httpAuthPassword + defer func() { + *httpAuthPassword = origPasswd + *httpAuthUsername = origUsername + }() + + f := func(user, pass string, expCode int) { + t.Helper() + req := httptest.NewRequest(http.MethodGet, "/metrics", nil) + req.SetBasicAuth(user, pass) + + w := httptest.NewRecorder() + CheckBasicAuth(w, req) + + res := w.Result() + _ = res.Body.Close() + if expCode != res.StatusCode { + t.Fatalf("wanted status code: %d, got: %d\n", res.StatusCode, expCode) + } + } + + *httpAuthUsername = "test" + *httpAuthPassword = "pass" + f("test", "pass", 200) + f("test", "wrong", 401) + f("wrong", "pass", 401) + f("wrong", "wrong", 401) + + *httpAuthUsername = "" + *httpAuthPassword = "" + f("test", "pass", 200) + f("test", "wrong", 200) + f("wrong", "pass", 200) + f("wrong", "wrong", 200) +} + +func TestAuthKeyMetrics(t *testing.T) { + origUsername := *httpAuthUsername + origPasswd := *httpAuthPassword + defer func() { + *httpAuthPassword = origPasswd + *httpAuthUsername = origUsername + }() + + tstWithAuthKey := func(key string, expCode int) { + t.Helper() + req := httptest.NewRequest(http.MethodPost, "/metrics", strings.NewReader("authKey="+key)) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value") + w := httptest.NewRecorder() + + CheckAuthFlag(w, req, "rightKey", "metricsAuthkey") + + res := w.Result() + defer res.Body.Close() + if expCode != res.StatusCode { + t.Fatalf("Unexpected status code: %d, Expected code is: %d\n", res.StatusCode, expCode) + } + } + + tstWithAuthKey("rightKey", 200) + tstWithAuthKey("wrongKey", 401) + + tstWithOutAuthKey := func(user, pass string, expCode int) { + t.Helper() + req := httptest.NewRequest(http.MethodGet, "/metrics", nil) + req.SetBasicAuth(user, pass) + + w := httptest.NewRecorder() + CheckAuthFlag(w, req, "", "metricsAuthkey") + + res := w.Result() + _ = res.Body.Close() + if expCode != res.StatusCode { + t.Fatalf("wanted status code: %d, got: %d\n", res.StatusCode, expCode) + } + } + + *httpAuthUsername = "test" + *httpAuthPassword = "pass" + tstWithOutAuthKey("test", "pass", 200) + tstWithOutAuthKey("test", "wrong", 401) + tstWithOutAuthKey("wrong", "pass", 401) + tstWithOutAuthKey("wrong", "wrong", 401) +} + func TestHandlerWrapper(t *testing.T) { *headerHSTS = "foo" *headerFrameOptions = "bar"