Closes #155
Reviewed by pr-gatekeeper: #181 (comment) (block, round 1) and re-review at e05a13a (approve).
Co-authored-by: Mathias <mthbqv@gmail.com>
91 lines
3.8 KiB
Go
91 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/metrics"
|
|
)
|
|
|
|
// wrongTokSentinel is a presented credential distinctive enough that any
|
|
// occurrence in a log line is the raw token, not a coincidence.
|
|
const wrongTokSentinel = "wrong-tok-sentinel-q7zx"
|
|
|
|
// hyperguild#155: since #149 every REST route sits behind BearerMiddleware,
|
|
// and chassis v0.2.0 refused without logging, so an unknown caller got
|
|
// silent 401s. The chassis now writes an `mcp auth rejected` line, and the
|
|
// presented token appears nowhere in the log as a bare value (PR #181
|
|
// review: asserting on the "Bearer " header form could never fail, since
|
|
// the chassis logs fields, not headers).
|
|
func TestBuildMuxRejectionLogLineOmitsTheBarePresentedToken(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
prev := slog.Default()
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, nil)))
|
|
t.Cleanup(func() { slog.SetDefault(prev) })
|
|
|
|
code, _ := do(t, testMux(t), "POST", "/query", "Bearer "+wrongTokSentinel)
|
|
require.Equal(t, http.StatusUnauthorized, code)
|
|
assert.Contains(t, buf.String(), "mcp auth rejected")
|
|
assert.Contains(t, buf.String(), "path=/query")
|
|
assert.NotContains(t, buf.String(), wrongTokSentinel, "the rejected token is logged as a bare value")
|
|
assert.NotContains(t, buf.String(), routesTok, "the valid static token is logged")
|
|
}
|
|
|
|
// The `mcp auth accepted` line leaves out the valid static token.
|
|
func TestBuildMuxAcceptedLogLineOmitsTheBareStaticToken(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
prev := slog.Default()
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, nil)))
|
|
t.Cleanup(func() { slog.SetDefault(prev) })
|
|
|
|
code, _ := do(t, testMux(t), "GET", "/pending", "Bearer "+routesTok)
|
|
require.Equal(t, http.StatusOK, code)
|
|
assert.Contains(t, buf.String(), "mcp auth accepted")
|
|
assert.NotContains(t, buf.String(), routesTok, "the accepted static token is logged as a bare value")
|
|
}
|
|
|
|
// An authenticated REST request is labelled with the REST mux's pattern:
|
|
// the inner mux sets it on the same *http.Request the outer middleware reads.
|
|
func TestMetricsLabelAnAuthenticatedRESTRequestWithItsRoutePattern(t *testing.T) {
|
|
reg := metrics.New()
|
|
code, _ := do(t, reg.Middleware(testMux(t)), "GET", "/pending", "Bearer "+routesTok)
|
|
require.Equal(t, http.StatusOK, code)
|
|
|
|
rec := httptest.NewRecorder()
|
|
reg.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/metrics", nil))
|
|
assert.Contains(t, rec.Body.String(), `path="GET /pending"`)
|
|
assert.NotContains(t, rec.Body.String(), `path="/"`)
|
|
}
|
|
|
|
// hyperguild#155: a request refused by REST auth never reaches the REST mux,
|
|
// so it was labelled path="/" and a 4xx on /query could not be told from one
|
|
// on /write. It is now labelled with the REST pattern it was addressed to.
|
|
func TestMetricsLabelARejectedRESTRequestWithItsRoutePattern(t *testing.T) {
|
|
reg := metrics.New()
|
|
code, _ := do(t, reg.Middleware(testMux(t)), "POST", "/query", "")
|
|
require.Equal(t, http.StatusUnauthorized, code)
|
|
|
|
rec := httptest.NewRecorder()
|
|
reg.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/metrics", nil))
|
|
assert.Contains(t, rec.Body.String(), `path="POST /query",status="4xx"`)
|
|
}
|
|
|
|
// A path no REST route matches keeps the catch-all label, so an attacker
|
|
// probing random URLs cannot grow the series set.
|
|
func TestMetricsLabelARejectedUnknownPathWithTheCatchAll(t *testing.T) {
|
|
reg := metrics.New()
|
|
code, _ := do(t, reg.Middleware(testMux(t)), "POST", "/no-such-route-zq", "")
|
|
require.Equal(t, http.StatusUnauthorized, code)
|
|
|
|
rec := httptest.NewRecorder()
|
|
reg.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/metrics", nil))
|
|
assert.Contains(t, rec.Body.String(), `path="/",status="4xx"`)
|
|
assert.NotContains(t, rec.Body.String(), "no-such-route-zq")
|
|
}
|