outposts: make metrics compliant with Prometheus best-practices (#6398)

web/outpost: make metrics compliant with Prometheus best-practices

Today, all NewHistogramVec store values in nanoseconds without changing
the default histogram bucket, which are made for seconds, making them
a bit useless. In addition, some metrics names are not self-explanatoryand
and do not comply with Prometheus best practices.

This commit tries to fix all of this "issues".

NOTE: I kept old metrics in order to avoid breaking changes with
existing dashboards and metrics.

Signed-off-by: Alexandre NICOLAIE <xunleii@users.noreply.github.com>
This commit is contained in:
Alexandre NICOLAIE 2023-07-27 18:51:08 +02:00 committed by GitHub
parent 5347dd7022
commit a2714ab1f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 235 additions and 16 deletions

View File

@ -8,6 +8,7 @@ import (
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"strings" "strings"
"time"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -21,10 +22,20 @@ import (
var ( var (
FlowTimingGet = promauto.NewHistogramVec(prometheus.HistogramOpts{ FlowTimingGet = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_flow_timing_get_seconds",
Help: "Duration it took to get a challenge in seconds",
}, []string{"stage", "flow"})
FlowTimingPost = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_flow_timing_post_seconds",
Help: "Duration it took to send a challenge in seconds",
}, []string{"stage", "flow"})
// NOTE: the following metrics are kept for compatibility purpose
FlowTimingGetLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_flow_timing_get", Name: "authentik_outpost_flow_timing_get",
Help: "Duration it took to get a challenge", Help: "Duration it took to get a challenge",
}, []string{"stage", "flow"}) }, []string{"stage", "flow"})
FlowTimingPost = promauto.NewHistogramVec(prometheus.HistogramOpts{ FlowTimingPostLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_flow_timing_post", Name: "authentik_outpost_flow_timing_post",
Help: "Duration it took to send a challenge", Help: "Duration it took to send a challenge",
}, []string{"stage", "flow"}) }, []string{"stage", "flow"})
@ -186,6 +197,10 @@ func (fe *FlowExecutor) getInitialChallenge() (*api.ChallengeTypes, error) {
FlowTimingGet.With(prometheus.Labels{ FlowTimingGet.With(prometheus.Labels{
"stage": ch.GetComponent(), "stage": ch.GetComponent(),
"flow": fe.flowSlug, "flow": fe.flowSlug,
}).Observe(float64(gcsp.EndTime.Sub(gcsp.StartTime)) / float64(time.Second))
FlowTimingGetLegacy.With(prometheus.Labels{
"stage": ch.GetComponent(),
"flow": fe.flowSlug,
}).Observe(float64(gcsp.EndTime.Sub(gcsp.StartTime))) }).Observe(float64(gcsp.EndTime.Sub(gcsp.StartTime)))
return challenge, nil return challenge, nil
} }
@ -243,6 +258,10 @@ func (fe *FlowExecutor) solveFlowChallenge(challenge *api.ChallengeTypes, depth
FlowTimingPost.With(prometheus.Labels{ FlowTimingPost.With(prometheus.Labels{
"stage": ch.GetComponent(), "stage": ch.GetComponent(),
"flow": fe.flowSlug, "flow": fe.flowSlug,
}).Observe(float64(scsp.EndTime.Sub(scsp.StartTime)) / float64(time.Second))
FlowTimingPostLegacy.With(prometheus.Labels{
"stage": ch.GetComponent(),
"flow": fe.flowSlug,
}).Observe(float64(scsp.EndTime.Sub(scsp.StartTime))) }).Observe(float64(scsp.EndTime.Sub(scsp.StartTime)))
if depth >= 10 { if depth >= 10 {

View File

@ -2,6 +2,7 @@ package ldap
import ( import (
"net" "net"
"time"
"beryju.io/ldap" "beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
@ -20,6 +21,11 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
"outpost_name": ls.ac.Outpost.Name, "outpost_name": ls.ac.Outpost.Name,
"type": "bind", "type": "bind",
"app": selectedApp, "app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime)) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "bind",
"app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime))) }).Observe(float64(span.EndTime.Sub(span.StartTime)))
req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Bind request") req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Bind request")
}() }()
@ -49,6 +55,12 @@ func (ls *LDAPServer) Bind(bindDN string, bindPW string, conn net.Conn) (ldap.LD
"reason": "no_provider", "reason": "no_provider",
"app": "", "app": "",
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "bind",
"reason": "no_provider",
"app": "",
}).Inc()
return ldap.LDAPResultInsufficientAccessRights, nil return ldap.LDAPResultInsufficientAccessRights, nil
} }

View File

@ -52,6 +52,12 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
"reason": "flow_error", "reason": "flow_error",
"app": db.si.GetAppSlug(), "app": db.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "flow_error",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to execute flow") req.Log().WithError(err).Warning("failed to execute flow")
return ldap.LDAPResultInvalidCredentials, nil return ldap.LDAPResultInvalidCredentials, nil
} }
@ -62,6 +68,12 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
"reason": "invalid_credentials", "reason": "invalid_credentials",
"app": db.si.GetAppSlug(), "app": db.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "invalid_credentials",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().Info("Invalid credentials") req.Log().Info("Invalid credentials")
return ldap.LDAPResultInvalidCredentials, nil return ldap.LDAPResultInvalidCredentials, nil
} }
@ -75,6 +87,12 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
"reason": "access_denied", "reason": "access_denied",
"app": db.si.GetAppSlug(), "app": db.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_denied",
"app": db.si.GetAppSlug(),
}).Inc()
return ldap.LDAPResultInsufficientAccessRights, nil return ldap.LDAPResultInsufficientAccessRights, nil
} }
if err != nil { if err != nil {
@ -84,6 +102,12 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
"reason": "access_check_fail", "reason": "access_check_fail",
"app": db.si.GetAppSlug(), "app": db.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "access_check_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to check access") req.Log().WithError(err).Warning("failed to check access")
return ldap.LDAPResultOperationsError, nil return ldap.LDAPResultOperationsError, nil
} }
@ -98,6 +122,12 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
"reason": "user_info_fail", "reason": "user_info_fail",
"app": db.si.GetAppSlug(), "app": db.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": db.si.GetOutpostName(),
"type": "bind",
"reason": "user_info_fail",
"app": db.si.GetAppSlug(),
}).Inc()
req.Log().WithError(err).Warning("failed to get user info") req.Log().WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil return ldap.LDAPResultOperationsError, nil
} }

View File

@ -15,10 +15,20 @@ import (
var ( var (
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{ Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_ldap_request_duration_seconds",
Help: "LDAP request latencies in seconds",
}, []string{"outpost_name", "type", "app"})
RequestsRejected = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "authentik_outpost_ldap_requests_rejected_total",
Help: "Total number of rejected requests",
}, []string{"outpost_name", "type", "reason", "app"})
// NOTE: the following metrics are kept for compatibility purpose
RequestsLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_ldap_requests", Name: "authentik_outpost_ldap_requests",
Help: "The total number of configured providers", Help: "The total number of configured providers",
}, []string{"outpost_name", "type", "app"}) }, []string{"outpost_name", "type", "app"})
RequestsRejected = promauto.NewCounterVec(prometheus.CounterOpts{ RequestsRejectedLegacy = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "authentik_outpost_ldap_requests_rejected", Name: "authentik_outpost_ldap_requests_rejected",
Help: "Total number of rejected requests", Help: "Total number of rejected requests",
}, []string{"outpost_name", "type", "reason", "app"}) }, []string{"outpost_name", "type", "reason", "app"})

View File

@ -2,6 +2,7 @@ package ldap
import ( import (
"net" "net"
"time"
"beryju.io/ldap" "beryju.io/ldap"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
@ -21,6 +22,11 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
"outpost_name": ls.ac.Outpost.Name, "outpost_name": ls.ac.Outpost.Name,
"type": "search", "type": "search",
"app": selectedApp, "app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime)) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "search",
"app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime))) }).Observe(float64(span.EndTime.Sub(span.StartTime)))
req.Log().WithField("attributes", searchReq.Attributes).WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Search request") req.Log().WithField("attributes", searchReq.Attributes).WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Search request")
}() }()

View File

@ -45,6 +45,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "empty_bind_dn", "reason": "empty_bind_dn",
"app": ds.si.GetAppSlug(), "app": ds.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ds.si.GetOutpostName(),
"type": "search",
"reason": "empty_bind_dn",
"app": ds.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN)
} }
if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) { if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) {
@ -54,6 +60,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "invalid_bind_dn", "reason": "invalid_bind_dn",
"app": ds.si.GetAppSlug(), "app": ds.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ds.si.GetOutpostName(),
"type": "search",
"reason": "invalid_bind_dn",
"app": ds.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", req.BindDN, ds.si.GetBaseDN()) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", req.BindDN, ds.si.GetBaseDN())
} }
@ -66,6 +78,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "user_info_not_cached", "reason": "user_info_not_cached",
"app": ds.si.GetAppSlug(), "app": ds.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ds.si.GetOutpostName(),
"type": "search",
"reason": "user_info_not_cached",
"app": ds.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
} }
accsp.Finish() accsp.Finish()
@ -78,6 +96,12 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "filter_parse_fail", "reason": "filter_parse_fail",
"app": ds.si.GetAppSlug(), "app": ds.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ds.si.GetOutpostName(),
"type": "search",
"reason": "filter_parse_fail",
"app": ds.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter)
} }

View File

@ -62,6 +62,12 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "empty_bind_dn", "reason": "empty_bind_dn",
"app": ms.si.GetAppSlug(), "app": ms.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ms.si.GetOutpostName(),
"type": "search",
"reason": "empty_bind_dn",
"app": ms.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN)
} }
if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) { if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) {
@ -71,6 +77,12 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "invalid_bind_dn", "reason": "invalid_bind_dn",
"app": ms.si.GetAppSlug(), "app": ms.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ms.si.GetOutpostName(),
"type": "search",
"reason": "invalid_bind_dn",
"app": ms.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", req.BindDN, ms.si.GetBaseDN()) return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", req.BindDN, ms.si.GetBaseDN())
} }
@ -83,6 +95,12 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult,
"reason": "user_info_not_cached", "reason": "user_info_not_cached",
"app": ms.si.GetAppSlug(), "app": ms.si.GetAppSlug(),
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ms.si.GetOutpostName(),
"type": "search",
"reason": "user_info_not_cached",
"app": ms.si.GetAppSlug(),
}).Inc()
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
} }
accsp.Finish() accsp.Finish()

View File

@ -2,9 +2,10 @@ package ldap
import ( import (
"net" "net"
"time"
"github.com/getsentry/sentry-go"
"beryju.io/ldap" "beryju.io/ldap"
"github.com/getsentry/sentry-go"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/bind" "goauthentik.io/internal/outpost/ldap/bind"
@ -20,6 +21,11 @@ func (ls *LDAPServer) Unbind(boundDN string, conn net.Conn) (ldap.LDAPResultCode
"outpost_name": ls.ac.Outpost.Name, "outpost_name": ls.ac.Outpost.Name,
"type": "unbind", "type": "unbind",
"app": selectedApp, "app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime)) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "unbind",
"app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime))) }).Observe(float64(span.EndTime.Sub(span.StartTime)))
req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Unbind request") req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Unbind request")
}() }()
@ -49,5 +55,11 @@ func (ls *LDAPServer) Unbind(boundDN string, conn net.Conn) (ldap.LDAPResultCode
"reason": "no_provider", "reason": "no_provider",
"app": "", "app": "",
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": ls.ac.Outpost.Name,
"type": "unbind",
"reason": "no_provider",
"app": "",
}).Inc()
return ldap.LDAPResultOperationsError, nil return ldap.LDAPResultOperationsError, nil
} }

View File

@ -163,13 +163,19 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A
} }
before := time.Now() before := time.Now()
inner.ServeHTTP(rw, r) inner.ServeHTTP(rw, r)
after := time.Since(before) elapsed := time.Since(before)
metrics.Requests.With(prometheus.Labels{ metrics.Requests.With(prometheus.Labels{
"outpost_name": a.outpostName, "outpost_name": a.outpostName,
"type": "app", "type": "app",
"method": r.Method, "method": r.Method,
"host": web.GetHost(r), "host": web.GetHost(r),
}).Observe(float64(after)) }).Observe(float64(elapsed) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": a.outpostName,
"type": "app",
"method": r.Method,
"host": web.GetHost(r),
}).Observe(float64(elapsed))
}) })
}) })
if server.API().GlobalConfig.ErrorReporting.Enabled { if server.API().GlobalConfig.ErrorReporting.Enabled {

View File

@ -55,7 +55,7 @@ func (a *Application) configureProxy() error {
} }
before := time.Now() before := time.Now()
rp.ServeHTTP(rw, r) rp.ServeHTTP(rw, r)
after := time.Since(before) elapsed := time.Since(before)
metrics.UpstreamTiming.With(prometheus.Labels{ metrics.UpstreamTiming.With(prometheus.Labels{
"outpost_name": a.outpostName, "outpost_name": a.outpostName,
@ -63,7 +63,14 @@ func (a *Application) configureProxy() error {
"method": r.Method, "method": r.Method,
"scheme": r.URL.Scheme, "scheme": r.URL.Scheme,
"host": web.GetHost(r), "host": web.GetHost(r),
}).Observe(float64(after)) }).Observe(float64(elapsed) / float64(time.Second))
metrics.UpstreamTimingLegacy.With(prometheus.Labels{
"outpost_name": a.outpostName,
"upstream_host": r.URL.Host,
"method": r.Method,
"scheme": r.URL.Scheme,
"host": web.GetHost(r),
}).Observe(float64(elapsed))
}) })
return nil return nil
} }

View File

@ -19,25 +19,37 @@ import (
func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) { func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) {
before := time.Now() before := time.Now()
rw.WriteHeader(204) rw.WriteHeader(204)
after := time.Since(before) elapsed := time.Since(before)
metrics.Requests.With(prometheus.Labels{ metrics.Requests.With(prometheus.Labels{
"outpost_name": ps.akAPI.Outpost.Name, "outpost_name": ps.akAPI.Outpost.Name,
"method": r.Method, "method": r.Method,
"host": web.GetHost(r), "host": web.GetHost(r),
"type": "ping", "type": "ping",
}).Observe(float64(after)) }).Observe(float64(elapsed) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": ps.akAPI.Outpost.Name,
"method": r.Method,
"host": web.GetHost(r),
"type": "ping",
}).Observe(float64(elapsed))
} }
func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) { func (ps *ProxyServer) HandleStatic(rw http.ResponseWriter, r *http.Request) {
before := time.Now() before := time.Now()
web.DisableIndex(http.StripPrefix("/outpost.goauthentik.io/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r) web.DisableIndex(http.StripPrefix("/outpost.goauthentik.io/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r)
after := time.Since(before) elapsed := time.Since(before)
metrics.Requests.With(prometheus.Labels{ metrics.Requests.With(prometheus.Labels{
"outpost_name": ps.akAPI.Outpost.Name, "outpost_name": ps.akAPI.Outpost.Name,
"method": r.Method, "method": r.Method,
"host": web.GetHost(r), "host": web.GetHost(r),
"type": "static", "type": "static",
}).Observe(float64(after)) }).Observe(float64(elapsed) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": ps.akAPI.Outpost.Name,
"method": r.Method,
"host": web.GetHost(r),
"type": "static",
}).Observe(float64(elapsed))
} }
func (ps *ProxyServer) lookupApp(r *http.Request) (*application.Application, string) { func (ps *ProxyServer) lookupApp(r *http.Request) (*application.Application, string) {

View File

@ -15,10 +15,20 @@ import (
var ( var (
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{ Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_proxy_request_duration_seconds",
Help: "Proxy request latencies in seconds",
}, []string{"outpost_name", "method", "host", "type"})
UpstreamTiming = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_proxy_upstream_response_duration_seconds",
Help: "Proxy upstream response latencies in seconds",
}, []string{"outpost_name", "method", "scheme", "host", "upstream_host"})
// NOTE: the following metric is kept for compatibility purpose
RequestsLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_proxy_requests", Name: "authentik_outpost_proxy_requests",
Help: "The total number of configured providers", Help: "The total number of configured providers",
}, []string{"outpost_name", "method", "host", "type"}) }, []string{"outpost_name", "method", "host", "type"})
UpstreamTiming = promauto.NewHistogramVec(prometheus.HistogramOpts{ UpstreamTimingLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_proxy_upstream_time", Name: "authentik_outpost_proxy_upstream_time",
Help: "A summary of the duration we wait for the upstream reply", Help: "A summary of the duration we wait for the upstream reply",
}, []string{"outpost_name", "method", "scheme", "host", "upstream_host"}) }, []string{"outpost_name", "method", "scheme", "host", "upstream_host"})

View File

@ -32,6 +32,11 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
"reason": "flow_error", "reason": "flow_error",
"app": r.pi.appSlug, "app": r.pi.appSlug,
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "flow_error",
"app": r.pi.appSlug,
}).Inc()
_ = w.Write(r.Response(radius.CodeAccessReject)) _ = w.Write(r.Response(radius.CodeAccessReject))
return return
} }
@ -41,6 +46,11 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
"reason": "invalid_credentials", "reason": "invalid_credentials",
"app": r.pi.appSlug, "app": r.pi.appSlug,
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "invalid_credentials",
"app": r.pi.appSlug,
}).Inc()
_ = w.Write(r.Response(radius.CodeAccessReject)) _ = w.Write(r.Response(radius.CodeAccessReject))
return return
} }
@ -53,6 +63,11 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
"reason": "access_check_fail", "reason": "access_check_fail",
"app": r.pi.appSlug, "app": r.pi.appSlug,
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "access_check_fail",
"app": r.pi.appSlug,
}).Inc()
return return
} }
if !access { if !access {
@ -63,6 +78,11 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
"reason": "access_denied", "reason": "access_denied",
"app": r.pi.appSlug, "app": r.pi.appSlug,
}).Inc() }).Inc()
metrics.RequestsRejectedLegacy.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"reason": "access_denied",
"app": r.pi.appSlug,
}).Inc()
return return
} }
_ = w.Write(r.Response(radius.CodeAccessAccept)) _ = w.Write(r.Response(radius.CodeAccessAccept))

View File

@ -2,6 +2,7 @@ package radius
import ( import (
"crypto/sha512" "crypto/sha512"
"time"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/google/uuid" "github.com/google/uuid"
@ -45,6 +46,10 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
metrics.Requests.With(prometheus.Labels{ metrics.Requests.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name, "outpost_name": rs.ac.Outpost.Name,
"app": selectedApp, "app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime)) / float64(time.Second))
metrics.RequestsLegacy.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime))) }).Observe(float64(span.EndTime.Sub(span.StartTime)))
}() }()

View File

@ -15,10 +15,20 @@ import (
var ( var (
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{ Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_radius_request_duration_seconds",
Help: "RADIUS request latencies in seconds",
}, []string{"outpost_name", "app"})
RequestsRejected = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "authentik_outpost_radius_requests_rejected_total",
Help: "Total number of rejected requests",
}, []string{"outpost_name", "reason", "app"})
// NOTE: the following metric is kept for compatibility purpose
RequestsLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_outpost_radius_requests", Name: "authentik_outpost_radius_requests",
Help: "The total number of successful requests", Help: "The total number of successful requests",
}, []string{"outpost_name", "app"}) }, []string{"outpost_name", "app"})
RequestsRejected = promauto.NewCounterVec(prometheus.CounterOpts{ RequestsRejectedLegacy = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "authentik_outpost_radius_requests_rejected", Name: "authentik_outpost_radius_requests_rejected",
Help: "Total number of rejected requests", Help: "Total number of rejected requests",
}, []string{"outpost_name", "reason", "app"}) }, []string{"outpost_name", "reason", "app"})

View File

@ -15,6 +15,12 @@ import (
var ( var (
Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{ Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_main_request_duration_seconds",
Help: "API request latencies in seconds",
}, []string{"dest"})
// NOTE: the following metric is kept for compatibility purpose
RequestsLegacy = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "authentik_main_requests", Name: "authentik_main_requests",
Help: "The total number of configured providers", Help: "The total number of configured providers",
}, []string{"dest"}) }, []string{"dest"})

View File

@ -34,9 +34,13 @@ func (ws *WebServer) configureProxy() {
if ws.ProxyServer != nil { if ws.ProxyServer != nil {
before := time.Now() before := time.Now()
ws.ProxyServer.Handle(rw, r) ws.ProxyServer.Handle(rw, r)
elapsed := time.Since(before)
Requests.With(prometheus.Labels{ Requests.With(prometheus.Labels{
"dest": "embedded_outpost", "dest": "embedded_outpost",
}).Observe(float64(time.Since(before))) }).Observe(float64(elapsed) / float64(time.Second))
RequestsLegacy.With(prometheus.Labels{
"dest": "embedded_outpost",
}).Observe(float64(elapsed))
return return
} }
ws.proxyErrorHandler(rw, r, fmt.Errorf("proxy not running")) ws.proxyErrorHandler(rw, r, fmt.Errorf("proxy not running"))
@ -52,15 +56,23 @@ func (ws *WebServer) configureProxy() {
before := time.Now() before := time.Now()
if ws.ProxyServer != nil { if ws.ProxyServer != nil {
if ws.ProxyServer.HandleHost(rw, r) { if ws.ProxyServer.HandleHost(rw, r) {
elapsed := time.Since(before)
Requests.With(prometheus.Labels{ Requests.With(prometheus.Labels{
"dest": "embedded_outpost", "dest": "embedded_outpost",
}).Observe(float64(time.Since(before))) }).Observe(float64(elapsed) / float64(time.Second))
RequestsLegacy.With(prometheus.Labels{
"dest": "embedded_outpost",
}).Observe(float64(elapsed))
return return
} }
} }
elapsed := time.Since(before)
Requests.With(prometheus.Labels{ Requests.With(prometheus.Labels{
"dest": "core", "dest": "core",
}).Observe(float64(time.Since(before))) }).Observe(float64(elapsed) / float64(time.Second))
RequestsLegacy.With(prometheus.Labels{
"dest": "core",
}).Observe(float64(elapsed))
r.Body = http.MaxBytesReader(rw, r.Body, 32*1024*1024) r.Body = http.MaxBytesReader(rw, r.Body, 32*1024*1024)
rp.ServeHTTP(rw, r) rp.ServeHTTP(rw, r)
})) }))