Skip to content
Snippets Groups Projects
Commit 1d049ac2 authored by ale's avatar ale
Browse files

Add Prometheus instrumentation for sso server internals

Metrics cover specifically the authentication workflow.
parent 5d8f8021
No related branches found
No related tags found
No related merge requests found
......@@ -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.",
})
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment