Commit 1d049ac2 authored by ale's avatar ale

Add Prometheus instrumentation for sso server internals

Metrics cover specifically the authentication workflow.
parent 5d8f8021
Pipeline #5432 passed with stages
in 2 minutes and 54 seconds
......@@ -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"})
)
......@@ -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.",
})
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment