backup: pre-hardening baseline
This commit is contained in:
commit
9e4c612dcb
57 changed files with 10393 additions and 0 deletions
164
backend/internal/api/server.go
Normal file
164
backend/internal/api/server.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/traefik/traefik-gui/backend/internal/api/handlers"
|
||||
"github.com/traefik/traefik-gui/backend/internal/api/middleware"
|
||||
"github.com/traefik/traefik-gui/backend/internal/config"
|
||||
"github.com/traefik/traefik-gui/backend/internal/config/file"
|
||||
"github.com/traefik/traefik-gui/backend/internal/database"
|
||||
"github.com/traefik/traefik-gui/backend/internal/database/repositories"
|
||||
"github.com/traefik/traefik-gui/backend/internal/traefik"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
engine *gin.Engine
|
||||
config *config.Config
|
||||
db *database.DB
|
||||
traefik traefik.TraefikClient
|
||||
}
|
||||
|
||||
func NewServer(cfg *config.Config, db *database.DB, traefikClient traefik.TraefikClient) *Server {
|
||||
if !cfg.DevMode {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
engine := gin.New()
|
||||
|
||||
userRepo := repositories.NewUserRepository(db.DB)
|
||||
sessionRepo := repositories.NewSessionRepository(db.DB)
|
||||
|
||||
authMiddleware := middleware.NewAuthMiddleware(sessionRepo, userRepo)
|
||||
|
||||
healthHandler := handlers.NewHealthHandler()
|
||||
authHandler := handlers.NewAuthHandler(
|
||||
userRepo,
|
||||
sessionRepo,
|
||||
cfg.SessionSecret,
|
||||
"", // cookie domain
|
||||
!cfg.DevMode, // cookie secure - true in prod
|
||||
)
|
||||
configHandler := handlers.NewConfigHandler(traefikClient)
|
||||
|
||||
// File-provider service (Phase 2)
|
||||
fileSvc, err := file.NewService(cfg.ConfigDir, db.DB)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Str("configDir", cfg.ConfigDir).Msg("Failed to init file service")
|
||||
}
|
||||
fileHandler := handlers.NewFileConfigHandler(fileSvc)
|
||||
// Enhance health ready to check config dir writable
|
||||
healthHandler.SetConfigDir(cfg.ConfigDir)
|
||||
|
||||
// Middleware
|
||||
engine.Use(middleware.LoggingMiddleware())
|
||||
engine.Use(middleware.SecurityHeadersMiddleware())
|
||||
engine.Use(middleware.CORSMiddleware(cfg.CORSOrigin))
|
||||
engine.Use(gin.Recovery())
|
||||
|
||||
// Health endpoints (no auth)
|
||||
engine.GET("/api/health", healthHandler.Health)
|
||||
engine.GET("/api/ready", healthHandler.Ready)
|
||||
|
||||
// Auth endpoints
|
||||
authGroup := engine.Group("/api/auth")
|
||||
{
|
||||
authGroup.POST("/login", authHandler.Login)
|
||||
authGroup.POST("/logout", authMiddleware.RequireAuth(), authHandler.Logout)
|
||||
authGroup.GET("/me", authMiddleware.RequireAuth(), authHandler.Me)
|
||||
}
|
||||
|
||||
// Protected API endpoints
|
||||
apiGroup := engine.Group("/api")
|
||||
apiGroup.Use(authMiddleware.RequireAuth())
|
||||
apiGroup.Use(authMiddleware.RequireCSRF())
|
||||
{
|
||||
// Config endpoints (file-provider, Phase 2)
|
||||
configGroup := apiGroup.Group("/config")
|
||||
{
|
||||
// File-provider management (secure, atomic, validated)
|
||||
configGroup.GET("/files", fileHandler.ListFiles)
|
||||
configGroup.GET("/files/:name", fileHandler.GetFile)
|
||||
configGroup.GET("/history", fileHandler.History)
|
||||
configGroup.POST("/preview", fileHandler.Preview)
|
||||
configGroup.POST("/validate", fileHandler.Validate)
|
||||
configGroup.POST("/apply", fileHandler.Apply)
|
||||
configGroup.POST("/rollback", fileHandler.Rollback)
|
||||
|
||||
// Legacy mock endpoints (read-only dashboard, kept for compatibility)
|
||||
configGroup.GET("/routers", configHandler.ListRouters)
|
||||
configGroup.GET("/routers/:id", configHandler.GetRouter)
|
||||
configGroup.POST("/routers", configHandler.CreateRouter)
|
||||
configGroup.PUT("/routers/:id", configHandler.UpdateRouter)
|
||||
configGroup.DELETE("/routers/:id", configHandler.DeleteRouter)
|
||||
|
||||
configGroup.GET("/services", configHandler.ListServices)
|
||||
configGroup.GET("/middlewares", configHandler.ListMiddlewares)
|
||||
configGroup.GET("/certificates", configHandler.ListCertificates)
|
||||
configGroup.GET("/entrypoints", configHandler.ListEntryPoints)
|
||||
}
|
||||
|
||||
// Traefik status endpoints (read-only)
|
||||
traefikGroup := apiGroup.Group("/traefik")
|
||||
{
|
||||
traefikGroup.GET("/overview", configHandler.GetOverview)
|
||||
traefikGroup.GET("/routers", configHandler.ListRouters)
|
||||
traefikGroup.GET("/services", configHandler.ListServices)
|
||||
traefikGroup.GET("/middlewares", configHandler.ListMiddlewares)
|
||||
traefikGroup.GET("/certificates", configHandler.ListCertificates)
|
||||
traefikGroup.GET("/entrypoints", configHandler.ListEntryPoints)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve embedded frontend in production
|
||||
if !cfg.DevMode {
|
||||
// TODO: Embed frontend assets
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
})
|
||||
}
|
||||
|
||||
srv := &Server{
|
||||
engine: engine,
|
||||
config: cfg,
|
||||
db: db,
|
||||
traefik: traefikClient,
|
||||
httpServer: &http.Server{
|
||||
Addr: cfg.Addr,
|
||||
Handler: engine,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
log.Info().Str("addr", s.config.Addr).Msg("Starting HTTP server")
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("Shutting down HTTP server")
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
s.httpServer.Shutdown(shutdownCtx)
|
||||
}()
|
||||
|
||||
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.httpServer.Shutdown(ctx)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue