package router import ( "crypto/subtle" "net/http" "strings" ) // AdminAuth demands the admin token for what the admin listener can change // or reveal: its API, which adds models, upgrades nodes and hands out engine // keys, and pprof, whose heap holds the API keys requests presented. Being // on loopback kept browsers out; it did not keep out every other process and // user on the machine. // // The token lives in a file only root and the server can read, so `sudo // llmgw ctl` and the console have it and nobody else does. pprof is served // only when asked for in the configuration, and then also behind the token. // The read-only pages — the cluster dashboard reached by SSH tunnel — and // /metrics and /health are left as they were. func AdminAuth(token string, pprof bool, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case strings.HasPrefix(r.URL.Path, "/debug/"): if !pprof { http.NotFound(w, r) return } case strings.HasPrefix(r.URL.Path, "/api/"): default: next.ServeHTTP(w, r) return } if !tokenMatches(token, r.Header.Get("Authorization")) { w.Header().Set("WWW-Authenticate", `Bearer realm="llmgw-admin"`) writeJSONError(w, http.StatusUnauthorized, "the admin API needs its token: run as root (sudo llmgw ...), or set LLMGW_ADMIN_TOKEN") return } next.ServeHTTP(w, r) }) } func tokenMatches(token, header string) bool { got, ok := strings.CutPrefix(header, "Bearer ") if !ok || token == "" { return false } return subtle.ConstantTimeCompare([]byte(strings.TrimSpace(got)), []byte(token)) == 1 } func writeJSONError(w http.ResponseWriter, code int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _, _ = w.Write([]byte(`{"error":` + quoteJSON(msg) + `}`)) } func quoteJSON(s string) string { var b strings.Builder b.WriteByte('"') for _, c := range s { switch c { case '"', '\\': b.WriteByte('\\') b.WriteRune(c) default: b.WriteRune(c) } } b.WriteByte('"') return b.String() }