 a2714ab1f1
			
		
	
	a2714ab1f1
	
	
	
		
			
			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>
		
			
				
	
	
		
			143 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package proxyv2
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/prometheus/client_golang/prometheus"
 | |
| 	"goauthentik.io/api/v3"
 | |
| 	"goauthentik.io/internal/outpost/proxyv2/application"
 | |
| 	"goauthentik.io/internal/outpost/proxyv2/metrics"
 | |
| 	sentryutils "goauthentik.io/internal/utils/sentry"
 | |
| 	"goauthentik.io/internal/utils/web"
 | |
| 	staticWeb "goauthentik.io/web"
 | |
| )
 | |
| 
 | |
| func (ps *ProxyServer) HandlePing(rw http.ResponseWriter, r *http.Request) {
 | |
| 	before := time.Now()
 | |
| 	rw.WriteHeader(204)
 | |
| 	elapsed := time.Since(before)
 | |
| 	metrics.Requests.With(prometheus.Labels{
 | |
| 		"outpost_name": ps.akAPI.Outpost.Name,
 | |
| 		"method":       r.Method,
 | |
| 		"host":         web.GetHost(r),
 | |
| 		"type":         "ping",
 | |
| 	}).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) {
 | |
| 	before := time.Now()
 | |
| 	web.DisableIndex(http.StripPrefix("/outpost.goauthentik.io/static/dist", staticWeb.StaticHandler)).ServeHTTP(rw, r)
 | |
| 	elapsed := time.Since(before)
 | |
| 	metrics.Requests.With(prometheus.Labels{
 | |
| 		"outpost_name": ps.akAPI.Outpost.Name,
 | |
| 		"method":       r.Method,
 | |
| 		"host":         web.GetHost(r),
 | |
| 		"type":         "static",
 | |
| 	}).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) {
 | |
| 	host := web.GetHost(r)
 | |
| 	// Try to find application by directly looking up host first (proxy, forward_auth_single)
 | |
| 	a, ok := ps.apps[host]
 | |
| 	if ok {
 | |
| 		ps.log.WithField("host", host).WithField("app", a.ProxyConfig().Name).Trace("Found app based direct host match")
 | |
| 		return a, host
 | |
| 	}
 | |
| 	// For forward_auth_domain, we don't have a direct app to domain relationship
 | |
| 	// Check through all apps, and check how much of their cookie domain matches the host
 | |
| 	// Return the application that has the longest match
 | |
| 	var longestMatch *application.Application
 | |
| 	longestMatchLength := 0
 | |
| 	for _, app := range ps.apps {
 | |
| 		if app.Mode() != api.PROXYMODE_FORWARD_DOMAIN {
 | |
| 			continue
 | |
| 		}
 | |
| 		// Check if the cookie domain has a leading period for a wildcard
 | |
| 		// This will decrease the weight of a wildcard domain, but a request to example.com
 | |
| 		// with the cookie domain set to example.com will still be routed correctly.
 | |
| 		cd := strings.TrimPrefix(*app.ProxyConfig().CookieDomain, ".")
 | |
| 		if !strings.HasSuffix(host, cd) {
 | |
| 			continue
 | |
| 		}
 | |
| 		if len(cd) < longestMatchLength {
 | |
| 			continue
 | |
| 		}
 | |
| 		longestMatch = app
 | |
| 		longestMatchLength = len(cd)
 | |
| 		// Also for forward_auth_domain, we need to respond on the external domain
 | |
| 		if app.ProxyConfig().ExternalHost == host {
 | |
| 			ps.log.WithField("host", host).WithField("app", app.ProxyConfig().Name).Debug("Found app based on external_host")
 | |
| 			return app, host
 | |
| 		}
 | |
| 	}
 | |
| 	// Check if our longes match is 0, in which case we didn't match, so we
 | |
| 	// manually return no app
 | |
| 	if longestMatchLength == 0 {
 | |
| 		return nil, host
 | |
| 	}
 | |
| 	ps.log.WithField("host", host).WithField("app", longestMatch.ProxyConfig().Name).Debug("Found app based on cookie domain")
 | |
| 	return longestMatch, host
 | |
| }
 | |
| 
 | |
| func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
 | |
| 	if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/static") {
 | |
| 		ps.HandleStatic(rw, r)
 | |
| 		return
 | |
| 	}
 | |
| 	if strings.HasPrefix(r.URL.Path, "/outpost.goauthentik.io/ping") {
 | |
| 		sentryutils.SentryNoSample(ps.HandlePing)(rw, r)
 | |
| 		return
 | |
| 	}
 | |
| 	a, host := ps.lookupApp(r)
 | |
| 	if a == nil {
 | |
| 		// If we only have one handler, host name switching doesn't matter
 | |
| 		if len(ps.apps) == 1 {
 | |
| 			ps.log.WithField("host", host).Trace("passing to single app mux")
 | |
| 			for k := range ps.apps {
 | |
| 				ps.apps[k].ServeHTTP(rw, r)
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		ps.log.WithField("headers", r.Header).Trace("tracing headers for no hostname match")
 | |
| 		ps.log.WithField("host", host).Warning("no app for hostname")
 | |
| 
 | |
| 		rw.Header().Set("Content-Type", "application/json")
 | |
| 		rw.WriteHeader(http.StatusBadRequest)
 | |
| 		j := json.NewEncoder(rw)
 | |
| 		j.SetIndent("", "\t")
 | |
| 		err := j.Encode(struct {
 | |
| 			Message string
 | |
| 			Host    string
 | |
| 			Detail  string
 | |
| 		}{
 | |
| 			Message: "no app for hostname",
 | |
| 			Host:    host,
 | |
| 			Detail:  fmt.Sprintf("Check the outpost settings and make sure '%s' is included.", host),
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			ps.log.WithError(err).Warning("Failed to write error body")
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	ps.log.WithField("host", host).Trace("passing to application mux")
 | |
| 	a.ServeHTTP(rw, r)
 | |
| }
 |