diff --git a/install.sh b/install.sh index 46f4cd6..7093cb4 100755 --- a/install.sh +++ b/install.sh @@ -110,8 +110,10 @@ detect_distro() { fi # Check for Ubuntu 24.04 (Noble Numbat) - if [[ "$VERSION" != "24.04" ]] && [[ "$CODENAME" != "noble" ]]; then - echo -e "${YELLOW}Warning: This installer is optimized for Ubuntu 24.04${NC}" + # SECURITY: Use OR (||) to warn if EITHER version or codename doesn't match + # This ensures we catch all incompatible systems, not just when both are wrong + if [[ "$VERSION" != "24.04" ]] || [[ "$CODENAME" != "noble" ]]; then + echo -e "${YELLOW}Warning: This installer is optimized for Ubuntu 24.04 (Noble Numbat)${NC}" echo " Detected: Ubuntu $VERSION ($CODENAME)" echo " Continuing anyway, but some features may not work correctly" read -p "Continue anyway? (y/n) " -n 1 -r diff --git a/internal/httpapp/audit_middleware.go b/internal/httpapp/audit_middleware.go index b2bc17e..15ed868 100644 --- a/internal/httpapp/audit_middleware.go +++ b/internal/httpapp/audit_middleware.go @@ -16,7 +16,7 @@ func (a *App) auditMiddleware(next http.Handler) http.Handler { } // Skip audit for public endpoints - if a.isPublicEndpoint(r.URL.Path) { + if a.isPublicEndpoint(r.URL.Path, r.Method) { next.ServeHTTP(w, r) return } diff --git a/internal/httpapp/auth_middleware.go b/internal/httpapp/auth_middleware.go index d46fa0f..a18a498 100644 --- a/internal/httpapp/auth_middleware.go +++ b/internal/httpapp/auth_middleware.go @@ -18,7 +18,7 @@ const ( func (a *App) authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Skip auth for public endpoints (includes web UI pages and read-only GET endpoints) - if a.isPublicEndpoint(r.URL.Path) { + if a.isPublicEndpoint(r.URL.Path, r.Method) { next.ServeHTTP(w, r) return } @@ -98,7 +98,9 @@ func (a *App) requireRole(allowedRoles ...models.Role) func(http.Handler) http.H } // isPublicEndpoint checks if an endpoint is public (no auth required) -func (a *App) isPublicEndpoint(path string) bool { +// It validates both path and HTTP method to prevent unauthenticated mutations +func (a *App) isPublicEndpoint(path, method string) bool { + // Always public paths (any method) publicPaths := []string{ "/healthz", "/health", @@ -126,13 +128,14 @@ func (a *App) isPublicEndpoint(path string) bool { } } - // Static files are public + // Static files are public (any method) if strings.HasPrefix(path, "/static/") { return true } - // Make read-only GET endpoints public for web UI (but require auth for mutations) - // This allows the UI to display data without login, but operations require auth + // Read-only GET endpoints are public for web UI (but require auth for mutations) + // SECURITY: Only GET requests are allowed without authentication + // POST, PUT, DELETE, PATCH require authentication publicReadOnlyPaths := []string{ "/api/v1/dashboard", // Dashboard data "/api/v1/disks", // List disks @@ -149,7 +152,9 @@ func (a *App) isPublicEndpoint(path string) bool { for _, publicPath := range publicReadOnlyPaths { if path == publicPath { - return true + // Only allow GET requests without authentication + // All mutation methods (POST, PUT, DELETE, PATCH) require authentication + return method == http.MethodGet } } diff --git a/internal/httpapp/cache_middleware.go b/internal/httpapp/cache_middleware.go index 3d1752e..81c883a 100644 --- a/internal/httpapp/cache_middleware.go +++ b/internal/httpapp/cache_middleware.go @@ -137,7 +137,7 @@ func (a *App) cacheMiddleware(next http.Handler) http.Handler { } // Skip caching for authenticated endpoints that may have user-specific data - if !a.isPublicEndpoint(r.URL.Path) { + if !a.isPublicEndpoint(r.URL.Path, r.Method) { // Check if user is authenticated - if so, skip caching // In production, you might want per-user caching by including user ID in cache key if _, ok := getUserFromContext(r); ok { diff --git a/internal/httpapp/https_middleware.go b/internal/httpapp/https_middleware.go index 3523494..f798f2a 100644 --- a/internal/httpapp/https_middleware.go +++ b/internal/httpapp/https_middleware.go @@ -11,7 +11,7 @@ import ( func (a *App) httpsEnforcementMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Skip HTTPS enforcement for health checks and localhost - if a.isPublicEndpoint(r.URL.Path) || isLocalhost(r) { + if a.isPublicEndpoint(r.URL.Path, r.Method) || isLocalhost(r) { next.ServeHTTP(w, r) return } @@ -53,7 +53,7 @@ func isLocalhost(r *http.Request) bool { func (a *App) requireHTTPSMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Skip for health checks - if a.isPublicEndpoint(r.URL.Path) { + if a.isPublicEndpoint(r.URL.Path, r.Method) { next.ServeHTTP(w, r) return } diff --git a/internal/httpapp/maintenance_middleware.go b/internal/httpapp/maintenance_middleware.go index 6fdd098..49278db 100644 --- a/internal/httpapp/maintenance_middleware.go +++ b/internal/httpapp/maintenance_middleware.go @@ -15,7 +15,7 @@ func (a *App) maintenanceMiddleware(next http.Handler) http.Handler { return } - if a.isPublicEndpoint(r.URL.Path) { + if a.isPublicEndpoint(r.URL.Path, r.Method) { next.ServeHTTP(w, r) return } diff --git a/internal/httpapp/rate_limit.go b/internal/httpapp/rate_limit.go index 68cbbc2..8167098 100644 --- a/internal/httpapp/rate_limit.go +++ b/internal/httpapp/rate_limit.go @@ -134,7 +134,7 @@ func (a *App) rateLimitMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Skip rate limiting for public endpoints - if a.isPublicEndpoint(r.URL.Path) { + if a.isPublicEndpoint(r.URL.Path, r.Method) { next.ServeHTTP(w, r) return } diff --git a/internal/httpapp/security_middleware.go b/internal/httpapp/security_middleware.go index 032dd93..e153d90 100644 --- a/internal/httpapp/security_middleware.go +++ b/internal/httpapp/security_middleware.go @@ -98,7 +98,7 @@ func (a *App) validateContentTypeMiddleware(next http.Handler) http.Handler { } // Skip for public endpoints - if a.isPublicEndpoint(r.URL.Path) { + if a.isPublicEndpoint(r.URL.Path, r.Method) { next.ServeHTTP(w, r) return }