From 1d049ac2f0f4c2fe516636d29572f6818fe2b69b Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Fri, 20 Dec 2019 17:20:22 +0000 Subject: [PATCH] Add Prometheus instrumentation for sso server internals Metrics cover specifically the authentication workflow. --- server/http.go | 24 ++++++++++++++++++++++++ server/login/login.go | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/server/http.go b/server/http.go index 7d293c6..f0f5282 100644 --- a/server/http.go +++ b/server/http.go @@ -17,6 +17,7 @@ import ( assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/gorilla/csrf" + "github.com/prometheus/client_golang/prometheus" "github.com/rs/cors" "git.autistici.org/id/auth" @@ -219,6 +220,7 @@ func (h *Server) loginCallback(ctx context.Context, username, password string, u // used to authenticate. decrypted, shard, err := h.maybeUnlockKeystore(ctx, username, password, userinfo) if err != nil { + keystoreCounter.WithLabelValues("error", shard).Inc() return fmt.Errorf("failed to unlock keystore for user %s: %v", username, err) } @@ -229,6 +231,7 @@ func (h *Server) loginCallback(ctx context.Context, username, password string, u kmsg += fmt.Sprintf(", shard %s", shard) } kmsg += ")" + keystoreCounter.WithLabelValues("ok", shard).Inc() } log.Printf("successful login for user %s%s", username, kmsg) return nil @@ -305,10 +308,12 @@ func (h *Server) handleGrantTicket(w http.ResponseWriter, req *http.Request) { if err != nil { log.Printf("auth error: %v: user=%s service=%s destination=%s nonce=%s groups=%s", err, username, service, destination, nonce, groupsStr) http.Error(w, err.Error(), http.StatusBadRequest) + errorsCounter.WithLabelValues(service, "false").Inc() return } log.Printf("authorized %s for %s (ttl=%ds)", username, service, int(ttl.Seconds())) + grantsCounter.WithLabelValues(service, "false").Inc() // Record the service in the session. auth.AddService(service) @@ -377,13 +382,16 @@ func (h *Server) handleExchange(w http.ResponseWriter, req *http.Request) { case err == ErrUnauthorized: log.Printf("unauthorized exchange request (%s -> %s)", curService, newService) http.Error(w, "Forbidden", http.StatusForbidden) + errorsCounter.WithLabelValues(newService, "true").Inc() return case err != nil: log.Printf("exchange error (%s -> %s): %v", curService, newService, err) http.Error(w, err.Error(), http.StatusBadRequest) + errorsCounter.WithLabelValues(newService, "true").Inc() return } + grantsCounter.WithLabelValues(newService, "true").Inc() w.Header().Set("Content-Type", "text/plain") io.WriteString(w, token) // nolint } @@ -472,3 +480,19 @@ func sriIntegrity(uri string) template.HTML { } return template.HTML(fmt.Sprintf(" integrity=\"%s\"", sri)) } + +// Prometheus instrumentation. +var ( + grantsCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "sso_grants_total", + Help: "Counter of ticket grants by service.", + }, []string{"service", "exchange"}) + errorsCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "sso_grant_errors_total", + Help: "Counter of authorization errors by service.", + }, []string{"service", "exchange"}) + keystoreCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "sso_keystore_unlocks_total", + Help: "Counter of keystore unlocks.", + }, []string{"status", "shard"}) +) diff --git a/server/login/login.go b/server/login/login.go index c7162a3..0303021 100644 --- a/server/login/login.go +++ b/server/login/login.go @@ -12,6 +12,7 @@ import ( "git.autistici.org/id/auth" authclient "git.autistici.org/id/auth/client" + "github.com/prometheus/client_golang/prometheus" "github.com/tstranex/u2f" "go.opencensus.io/trace" @@ -274,6 +275,7 @@ func (l *Login) handleLogin(w http.ResponseWriter, req *http.Request, sess *logi switch resp.Status { case auth.StatusOK: l.loginOk(w, req, sess, password, resp.UserInfo) + loginCounter.WithLabelValues("ok", "password").Inc() return case auth.StatusInsufficientCredentials: sess.Password = password @@ -290,6 +292,7 @@ func (l *Login) handleLogin(w http.ResponseWriter, req *http.Request, sess *logi return } env["Error"] = true + loginCounter.WithLabelValues("error", "password").Inc() } l.renderer.Render(w, req, "login_password.html", env) @@ -323,8 +326,10 @@ func (l *Login) handleLoginOTP(w http.ResponseWriter, req *http.Request, sess *l } if resp.Status == auth.StatusOK { l.loginOk(w, req, sess, sess.Password, resp.UserInfo) + loginCounter.WithLabelValues("ok", "otp").Inc() return } + loginCounter.WithLabelValues("error", "otp").Inc() env["Error"] = true sess.Failures++ if sess.Failures >= maxFailures { @@ -373,8 +378,10 @@ func (l *Login) handleLoginU2F(w http.ResponseWriter, req *http.Request, sess *l } if resp.Status == auth.StatusOK { l.loginOk(w, req, sess, sess.Password, resp.UserInfo) + loginCounter.WithLabelValues("ok", "u2f").Inc() return } + loginCounter.WithLabelValues("error", "u2f").Inc() env["Error"] = true sess.Failures++ if sess.Failures >= maxFailures { @@ -419,6 +426,7 @@ func (l *Login) makeAuthRequest(w http.ResponseWriter, req *http.Request, userna // Record the authentication response status in the trace. if err != nil { + authclientErrors.Inc() span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()}) } else if resp.Status == auth.StatusOK { span.SetStatus(trace.Status{Code: trace.StatusCodeOK, Message: "OK"}) @@ -433,3 +441,14 @@ func (l *Login) makeAuthRequest(w http.ResponseWriter, req *http.Request, userna func u2fAppIDFromRequest(r *http.Request) string { return fmt.Sprintf("https://%s", r.Host) } + +var ( + loginCounter = prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "sso_logins_total", + Help: "Counter of logins by status and method.", + }, []string{"status", "method"}) + authclientErrors = prometheus.NewCounter(prometheus.CounterOpts{ + Name: "sso_auth_client_errors_total", + Help: "Counter for auth_client errors.", + }) +) -- GitLab