feat(admin-console): Stage 1 — pipe + skeleton behind the gateway
Tests · Go / test (push) Successful in 2m0s
Tests · Go / test (push) Successful in 2m0s
Add the server-rendered operator console at /_gm, exposed publicly through the gateway behind the existing admin_accounts Basic Auth. Backend: - new internal/adminconsole package (html/template Renderer, stateless HMAC CSRF signer, embedded stylesheet) - /_gm route group reusing basicauth.Middleware(admin.Service) + a CSRF guard (per-operator token + same-origin check); dashboard landing page - BACKEND_ADMIN_CONSOLE_CSRF_KEY config (per-process random fallback) Gateway: - new "admin" public route class (per-IP rate limit, body + GET/HEAD/POST method limits) classifying /_gm traffic - reverse proxy to the backend /_gm surface, preserving Host and relaying the backend 401 Basic Auth challenge; 502 when the backend is unreachable - GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_ADMIN_* config dev-deploy: - Caddy routes /_gm/* to the gateway - bootstrap admin + stable CSRF key; enable Prometheus /metrics exporters on backend and gateway (forward-compat for a future Prometheus/Grafana stack) Docs: ARCHITECTURE 14.1/16, FUNCTIONAL 10.2.1 (+ru mirror), backend and gateway READMEs, new backend/docs/admin-console.md. Tests: renderer + CSRF unit tests; backend router auth/render/asset/CSRF; gateway classifier, proxy forwarding/Host/401/405/413/429/502.
This commit is contained in:
@@ -234,6 +234,8 @@ func publicRoutePolicyForClass(policy config.PublicHTTPAntiAbuseConfig, class Pu
|
||||
return policy.BrowserBootstrap
|
||||
case PublicRouteClassBrowserAsset:
|
||||
return policy.BrowserAsset
|
||||
case PublicRouteClassAdmin:
|
||||
return policy.Admin
|
||||
default:
|
||||
return policy.PublicMisc
|
||||
}
|
||||
@@ -252,6 +254,8 @@ func publicAuthIdentityPolicyForPath(requestPath string, policy config.PublicHTT
|
||||
|
||||
func allowedMethodsForRequestShape(r *http.Request) []string {
|
||||
switch {
|
||||
case isAdminConsolePath(r.URL.Path):
|
||||
return []string{http.MethodGet, http.MethodHead, http.MethodPost}
|
||||
case isPublicAuthPath(r.URL.Path):
|
||||
return []string{http.MethodPost}
|
||||
case isProbePath(r.URL.Path):
|
||||
@@ -284,6 +288,17 @@ func isPublicAuthPath(requestPath string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// isAdminConsoleRequest reports whether r targets the operator console surface.
|
||||
func isAdminConsoleRequest(r *http.Request) bool {
|
||||
return isAdminConsolePath(r.URL.Path)
|
||||
}
|
||||
|
||||
// isAdminConsolePath reports whether requestPath is the admin console root
|
||||
// (`/_gm`) or any path beneath it (`/_gm/...`).
|
||||
func isAdminConsolePath(requestPath string) bool {
|
||||
return requestPath == "/_gm" || strings.HasPrefix(requestPath, "/_gm/")
|
||||
}
|
||||
|
||||
func isProbePath(requestPath string) bool {
|
||||
switch requestPath {
|
||||
case "/healthz", "/readyz":
|
||||
|
||||
Reference in New Issue
Block a user