Serve Prometheus metrics behind SWWAF_METRICS_TOKEN (closes #23)
check / check (push) Successful in 3m21s
check / check (push) Successful in 3m21s
GET /_smallwebwaf/metrics answers in the Prometheus text format for a request carrying SWWAF_METRICS_TOKEN, 401 without it and 404 while it is unset. Every request under /_smallwebwaf/ but the health check now goes through the checks and is answered where it would be forwarded, 404 for any path but the metrics, so none reaches the app. In the client's history a 401 counts as refused, the metrics and the 404s as neither. SWWAF_METRICS_TOP_N bounds the series by country, the rest counted as other. Deviation: go.mod and go.sum written by hand, as go runs only through make. Deviation: no metrics yet for state files read again after an edit or edits set aside; that work is not merged. Model: opus-5-5
This commit was merged in pull request #76.
This commit is contained in:
+28
-2
@@ -8,11 +8,13 @@ import (
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/smallwebwaf/internal/bans"
|
||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||
"sneak.berlin/go/smallwebwaf/internal/lookup"
|
||||
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
||||
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||
)
|
||||
@@ -23,10 +25,18 @@ const (
|
||||
appIdleConnTimeout = 90 * time.Second
|
||||
)
|
||||
|
||||
// adminPrefix starts the path of every request for smallwebwaf itself,
|
||||
// which never reaches the app.
|
||||
const adminPrefix = "/_smallwebwaf/"
|
||||
|
||||
// HealthPath is smallwebwaf's health endpoint, which the container's
|
||||
// health check asks.
|
||||
const HealthPath = "/_smallwebwaf/healthz"
|
||||
|
||||
// MetricsPath is where the metrics are, for a request that carries
|
||||
// SWWAF_METRICS_TOKEN.
|
||||
const MetricsPath = "/_smallwebwaf/metrics"
|
||||
|
||||
// Params are what New needs.
|
||||
type Params struct {
|
||||
Config *config.Config
|
||||
@@ -44,13 +54,14 @@ type Params struct {
|
||||
}
|
||||
|
||||
// Server is the server smallwebwaf runs, with the parts of the proxy
|
||||
// whose state the state files keep.
|
||||
// whose state the state files keep, and the metrics.
|
||||
type Server struct {
|
||||
*http.Server
|
||||
|
||||
Ledger *bans.Ledger
|
||||
Limiter *ratelimit.Limiter
|
||||
GeoJS *lookup.GeoJS
|
||||
Metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// New returns the server smallwebwaf runs: each request it reads passes
|
||||
@@ -61,6 +72,7 @@ type Server struct {
|
||||
// applies the timeouts and size limits from then on.
|
||||
func New(params Params) *Server {
|
||||
errorLog := slog.NewLogLogger(params.ProcessLog.Handler(), slog.LevelWarn)
|
||||
m := metrics.New(params.Config.MetricsTopN)
|
||||
h := &handler{
|
||||
config: params.Config,
|
||||
requestLog: params.RequestLog,
|
||||
@@ -68,6 +80,7 @@ func New(params Params) *Server {
|
||||
errorLog: errorLog,
|
||||
transport: newTransport(),
|
||||
now: params.Now,
|
||||
metrics: m,
|
||||
limiter: ratelimit.New(ratelimit.Limits{
|
||||
PerMinute: params.Config.RateLimitPerMinute,
|
||||
PerHour: params.Config.RateLimitPerHour,
|
||||
@@ -83,8 +96,10 @@ func New(params Params) *Server {
|
||||
URL: params.GeoJSURL,
|
||||
Now: params.Now,
|
||||
ProcessLog: params.ProcessLog,
|
||||
Metrics: m,
|
||||
}),
|
||||
}
|
||||
m.AddBansAndClients(h.ledger, h.limiter, params.Now)
|
||||
|
||||
return &Server{
|
||||
Server: &http.Server{
|
||||
@@ -102,6 +117,7 @@ func New(params Params) *Server {
|
||||
Ledger: h.ledger,
|
||||
Limiter: h.limiter,
|
||||
GeoJS: h.geojs,
|
||||
Metrics: m,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +130,7 @@ type handler struct {
|
||||
errorLog *log.Logger
|
||||
transport http.RoundTripper
|
||||
now func() time.Time
|
||||
metrics *metrics.Metrics
|
||||
limiter *ratelimit.Limiter
|
||||
ledger *bans.Ledger
|
||||
geojs *lookup.GeoJS
|
||||
@@ -133,7 +150,8 @@ func newTransport() *http.Transport {
|
||||
|
||||
// ServeHTTP handles one request: it works out the client, runs the
|
||||
// checks, passes the request to the app and the answer back within the
|
||||
// limits, and writes the request's log line.
|
||||
// limits, or answers it itself if it is for smallwebwaf, and writes the
|
||||
// request's log line.
|
||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
rq := h.newRequest(w, r)
|
||||
defer rq.finish()
|
||||
@@ -157,5 +175,13 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// A request for smallwebwaf itself is answered where another would be
|
||||
// passed to the app, so that it goes through every check first.
|
||||
if strings.HasPrefix(r.URL.Path, adminPrefix) {
|
||||
rq.answerAdmin()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rq.forward(r.Context())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user