check / check (push) Successful in 4m12s
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. SWWAF_METRICS_TOP_N bounds the series by country, the rest counted as other. Judgement call: a request answered at smallwebwaf's own endpoints is neither forwarded nor refused in the client's history. Deviation: go.mod and go.sum written by hand from the module proxy and sum.golang.org, 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
41 lines
1.2 KiB
Go
41 lines
1.2 KiB
Go
package proxy
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
)
|
|
|
|
// answerAdmin answers a request for smallwebwaf itself, under
|
|
// /_smallwebwaf/, once it has passed the checks: GET MetricsPath with
|
|
// SWWAF_METRICS_TOKEN gets the metrics, and without it 401. Any other
|
|
// request gets 404, as the metrics do while SWWAF_METRICS_TOKEN is unset.
|
|
func (rq *request) answerAdmin() {
|
|
rq.line.Action = requestlog.ActionAdmin
|
|
rq.startClientResponseTimeout()
|
|
|
|
token := rq.h.config.MetricsToken
|
|
|
|
switch {
|
|
case token == "" || rq.in.Method != http.MethodGet || rq.in.URL.Path != MetricsPath:
|
|
http.Error(rq.out, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
case !hasToken(rq.in, token):
|
|
rq.out.Header().Set("WWW-Authenticate", "Bearer")
|
|
http.Error(rq.out, http.StatusText(http.StatusUnauthorized),
|
|
http.StatusUnauthorized)
|
|
default:
|
|
rq.h.metrics.ServeHTTP(rq.out, rq.in)
|
|
}
|
|
}
|
|
|
|
// hasToken reports whether r carries token, as Authorization: Bearer
|
|
// <token>.
|
|
func hasToken(r *http.Request, token string) bool {
|
|
scheme, sent, _ := strings.Cut(r.Header.Get("Authorization"), " ")
|
|
|
|
return strings.EqualFold(scheme, "Bearer") &&
|
|
subtle.ConstantTimeCompare([]byte(sent), []byte(token)) == 1
|
|
}
|