R-970: the box's hub key opens no route; Data tab leftovers; R-1103: the install form's dead branches
gates / gates (push) Successful in 48s
gates / gates (push) Successful in 48s
R-970: /api/config, /api/config/hash, /api/config/apply removed (they read and replaced controller.yaml with every secret, reachable with the box's own hub API key, unsigned; nothing called them). The hub-key Bearer door on /api/selfupdate/ and /api/geo/ and the matching CSRF exemption are gone; the dashboard keeps using those with a session. The route table is buildMux, so a test drives the real one. Data tab: the export password field is autocomplete=new-password with a non-login name; the 2nd copy's status rows that repeated the form are gone (the no-target reason moves into the form). R-1103: app_fields.html loses its installed-only branches (no page serves them since R-1095); the tests that rendered that dead path are retired, their rules asserted on the settings tab (R-694 moved there). Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -18,7 +18,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"crypto/subtle"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -2190,56 +2189,7 @@ func main() {
|
||||
// the controller no longer runs a DriveMigrator.
|
||||
|
||||
// --- Build HTTP mux ---
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// API routes (no auth for health endpoint, auth for everything else)
|
||||
mux.HandleFunc("/api/health", apiRouter.HealthHandler)
|
||||
// Disk management API — thin proxy to the host agent (slice 8C). The agent owns
|
||||
// disk execution; the controller forwards list/assign/eject/format.
|
||||
mux.Handle("/api/disks", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDiskAPI))))
|
||||
mux.Handle("/api/disks/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDiskAPI))))
|
||||
// Guided storage provisioning (init/attach/eject orchestration over the agent disk API + registry).
|
||||
mux.Handle("/api/storage/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeStorageAPI))))
|
||||
// LAN network-sharing folder picker (R-7 slice 1). Must live here, not in the web ServeHTTP
|
||||
// switch: the /api/ subtree is routed on this mux, so a case there is shadowed and 401s.
|
||||
mux.Handle("/api/sharing/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeSharingAPI))))
|
||||
// Guest RAM resize (v0.143.0, R-24): read current allocation/bounds + apply a bounded resize.
|
||||
mux.Handle("/api/system/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeSystemAPI))))
|
||||
// R-490 (v0.242.0): the web layer's /api/system/ prefix knows only the memory routes and answered
|
||||
// 404 to /api/system/info, so the monitoring page's memory-distribution card never rendered. An
|
||||
// exact pattern wins over the prefix in ServeMux. Pinned by TestR490_SystemInfoIsMountedAheadOfTheWebPrefix.
|
||||
mux.Handle("/api/system/info", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
// Standalone full-server (guest) restart — the "Kiszolgáló újraindítása" maintenance affordance,
|
||||
// a sibling to the controller-only /api/selfrestart. Reuses the agent GuestReboot primitive.
|
||||
mux.Handle("/api/server/reboot", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.HandleServerReboot))))
|
||||
// R-998: „A gép kikapcsolása" — the household turns the whole machine off (agent POST /host/poweroff, doc 14).
|
||||
// Dashboard login only (RequireAuth): family members' app logins never reach the dashboard.
|
||||
mux.Handle("/api/server/poweroff", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.HandleServerPowerOff))))
|
||||
// Whole-guest (appliance) backup visibility + manual trigger. Distinct prefix from apiRouter's
|
||||
// app-data /api/backup/{run,status} (DB dumps) to avoid shadowing the /api/ catch-all subtree.
|
||||
mux.Handle("/api/guest-backup/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeBackupAPI))))
|
||||
// Host metrics API — thin proxy to the host agent (slice 9). Read-only host-wide health +
|
||||
// per-storage capacity for the monitoring view; the de-privileged controller can't read the
|
||||
// host itself. GET only, so no CSRF wrapper needed.
|
||||
mux.Handle("/api/host-metrics", webServer.RequireAuth(http.HandlerFunc(webServer.ServeHostMetricsAPI)))
|
||||
// App export/import API routes handled by web server
|
||||
mux.Handle("/api/export/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeExportAPI))))
|
||||
// Debug API routes handled by web server (debug-mode gating inside handler)
|
||||
mux.Handle("/api/debug/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDebugAPI))))
|
||||
// Escrow ceremony wizard API (v0.127.0) — session auth + CSRF on POSTs; the claim response is
|
||||
// the ONLY surface the recovery code R ever crosses (no-store, never logged).
|
||||
mux.Handle("/api/escrow/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeEscrowAPI))))
|
||||
// Self-update API — accepts session auth OR hub API key (for external triggering)
|
||||
// CsrfProtect exempts Bearer-token requests automatically.
|
||||
mux.Handle("/api/selfupdate/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
// Config API — accepts session auth OR hub API key (for Hub config push)
|
||||
mux.Handle("/api/config/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
// Geo API — accepts session auth OR hub API key (for Hub geo-disable)
|
||||
mux.Handle("/api/geo/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
mux.Handle("/api/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
|
||||
// Web UI routes (auth required)
|
||||
mux.Handle("/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeHTTP))))
|
||||
mux := buildMux(apiRouter, webServer)
|
||||
|
||||
// --- Start HTTP server ---
|
||||
server := &http.Server{
|
||||
@@ -2282,7 +2232,6 @@ func main() {
|
||||
logger.Println("[INFO] felhom-controller stopped")
|
||||
}
|
||||
|
||||
// selfUpdateAuthMiddleware allows access via session auth (normal UI) OR hub API key bearer token (external).
|
||||
// deadAppBootGrace is the startup settle window before fix-3 evaluates deployed-app run states — apps
|
||||
// legitimately take 30–60 s to come up, so a shorter grace would false-alarm during the controller's
|
||||
// own boot. After the grace, an app that still isn't running alerts (the F11 dead-at-boot case).
|
||||
@@ -3011,21 +2960,6 @@ func classifyRunStates(sts []stacks.Stack, quiesced map[string]bool, failedResta
|
||||
return dead, states
|
||||
}
|
||||
|
||||
func selfUpdateAuthMiddleware(cfg *config.Config, webServer *web.Server, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check bearer token first (for external API calls: hub, build scripts)
|
||||
if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
|
||||
token := strings.TrimPrefix(auth, "Bearer ")
|
||||
if token != "" && cfg.Hub.APIKey != "" && subtle.ConstantTimeCompare([]byte(token), []byte(cfg.Hub.APIKey)) == 1 {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Fall back to session auth
|
||||
webServer.RequireAuth(next).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// setupLogger builds the v0.116.0 capture layer: the LogBuffer ring ALWAYS exists
|
||||
// and captures every line (DEBUG included — remote diagnostics need the detail to
|
||||
// exist without a config flip), while stdout (docker logs) keeps respecting
|
||||
@@ -4168,3 +4102,55 @@ func keptSpaceSentence(sm *stacks.Manager, drive, lang string) string {
|
||||
}
|
||||
return util.Text(lang, "kept.diskwarn", strings.Join(parts, ", "))
|
||||
}
|
||||
|
||||
// buildMux is the controller's HTTP route table. One function so a test can drive the REAL table
|
||||
// (cmd/controller r970_no_hub_key_door_test.go) rather than a copy of it.
|
||||
func buildMux(apiRouter *api.Router, webServer *web.Server) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// API routes (no auth for health endpoint, auth for everything else)
|
||||
mux.HandleFunc("/api/health", apiRouter.HealthHandler)
|
||||
// Disk management API — thin proxy to the host agent (slice 8C). The agent owns
|
||||
// disk execution; the controller forwards list/assign/eject/format.
|
||||
mux.Handle("/api/disks", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDiskAPI))))
|
||||
mux.Handle("/api/disks/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDiskAPI))))
|
||||
// Guided storage provisioning (init/attach/eject orchestration over the agent disk API + registry).
|
||||
mux.Handle("/api/storage/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeStorageAPI))))
|
||||
// LAN network-sharing folder picker (R-7 slice 1). Must live here, not in the web ServeHTTP
|
||||
// switch: the /api/ subtree is routed on this mux, so a case there is shadowed and 401s.
|
||||
mux.Handle("/api/sharing/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeSharingAPI))))
|
||||
// Guest RAM resize (v0.143.0, R-24): read current allocation/bounds + apply a bounded resize.
|
||||
mux.Handle("/api/system/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeSystemAPI))))
|
||||
// R-490 (v0.242.0): the web layer's /api/system/ prefix knows only the memory routes and answered
|
||||
// 404 to /api/system/info, so the monitoring page's memory-distribution card never rendered. An
|
||||
// exact pattern wins over the prefix in ServeMux. Pinned by TestR490_SystemInfoIsMountedAheadOfTheWebPrefix.
|
||||
mux.Handle("/api/system/info", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
// Standalone full-server (guest) restart — the "Kiszolgáló újraindítása" maintenance affordance,
|
||||
// a sibling to the controller-only /api/selfrestart. Reuses the agent GuestReboot primitive.
|
||||
mux.Handle("/api/server/reboot", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.HandleServerReboot))))
|
||||
// R-998: „A gép kikapcsolása" — the household turns the whole machine off (agent POST /host/poweroff, doc 14).
|
||||
// Dashboard login only (RequireAuth): family members' app logins never reach the dashboard.
|
||||
mux.Handle("/api/server/poweroff", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.HandleServerPowerOff))))
|
||||
// Whole-guest (appliance) backup visibility + manual trigger. Distinct prefix from apiRouter's
|
||||
// app-data /api/backup/{run,status} (DB dumps) to avoid shadowing the /api/ catch-all subtree.
|
||||
mux.Handle("/api/guest-backup/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeBackupAPI))))
|
||||
// Host metrics API — thin proxy to the host agent (slice 9). Read-only host-wide health +
|
||||
// per-storage capacity for the monitoring view; the de-privileged controller can't read the
|
||||
// host itself. GET only, so no CSRF wrapper needed.
|
||||
mux.Handle("/api/host-metrics", webServer.RequireAuth(http.HandlerFunc(webServer.ServeHostMetricsAPI)))
|
||||
// App export/import API routes handled by web server
|
||||
mux.Handle("/api/export/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeExportAPI))))
|
||||
// Debug API routes handled by web server (debug-mode gating inside handler)
|
||||
mux.Handle("/api/debug/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeDebugAPI))))
|
||||
// Escrow ceremony wizard API (v0.127.0) — session auth + CSRF on POSTs; the claim response is
|
||||
// the ONLY surface the recovery code R ever crosses (no-store, never logged).
|
||||
mux.Handle("/api/escrow/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeEscrowAPI))))
|
||||
// v0.322.0 (R-970): no route accepts the box's hub API key any more. /api/selfupdate/ and /api/geo/ were open to
|
||||
// it as a Bearer token (and /api/config/, now removed); the hub never calls into a box (it disables geo through
|
||||
// Cloudflare directly), so they are served like every other /api/ route below: a dashboard session + CSRF.
|
||||
mux.Handle("/api/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP))))
|
||||
|
||||
// Web UI routes (auth required)
|
||||
mux.Handle("/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(webServer.ServeHTTP))))
|
||||
return mux
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user