Cybersecurity-Projects/PROJECTS/advanced/honeypot-network/internal/api/router.go

180 lines
3.5 KiB
Go

/*
©AngelaMos | 2026
router.go
Chi-based HTTP router for the hive dashboard API
Mounts all REST endpoints and the WebSocket handler behind a
middleware chain of panic recovery, CORS, and request logging.
Implements the types.Service interface so the API server
participates in the errgroup-managed lifecycle alongside the
honeypot services.
*/
package api
import (
"context"
"fmt"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog"
"github.com/CarterPerez-dev/hive/internal/config"
"github.com/CarterPerez-dev/hive/internal/event"
"github.com/CarterPerez-dev/hive/internal/mitre"
"github.com/CarterPerez-dev/hive/internal/session"
"github.com/CarterPerez-dev/hive/internal/store"
"github.com/CarterPerez-dev/hive/pkg/types"
)
const (
defaultPageLimit = 50
maxPageLimit = 500
defaultStatsDuration = 24 * time.Hour
defaultCredentialTop = 20
defaultAttackerTop = 50
corsMaxAgeSeconds = "86400"
wsEventBuffer = 256
)
type Server struct {
store store.Store
bus *event.Bus
mitreIdx *mitre.Index
tracker *session.Tracker
cfg *config.Config
logger *zerolog.Logger
router *chi.Mux
replayDir string
startedAt time.Time
}
func New(
db store.Store,
bus *event.Bus,
mitreIdx *mitre.Index,
tracker *session.Tracker,
cfg *config.Config,
logger *zerolog.Logger,
) *Server {
s := &Server{
store: db,
bus: bus,
mitreIdx: mitreIdx,
tracker: tracker,
cfg: cfg,
logger: logger,
replayDir: cfg.Log.ReplayDir,
startedAt: time.Now().UTC(),
}
s.router = s.buildRouter()
return s
}
func (s *Server) Name() string {
return "api"
}
func (s *Server) Start(ctx context.Context) error {
addr := s.cfg.Addr(s.cfg.API.Port)
srv := &http.Server{
Addr: addr,
Handler: s.router,
ReadTimeout: s.cfg.API.ReadTimeout,
WriteTimeout: s.cfg.API.WriteTimeout,
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(
context.Background(),
config.DefaultShutdownTimeout,
)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}()
s.logger.Info().
Str("addr", addr).
Msg("api server listening")
if err := srv.ListenAndServe(); err != nil &&
err != http.ErrServerClosed {
return fmt.Errorf("api server: %w", err)
}
return nil
}
func (s *Server) buildRouter() *chi.Mux {
r := chi.NewRouter()
r.Use(recoverer(s.logger))
r.Use(corsMiddleware(s.cfg.API.CORSOrigins))
r.Use(requestLogger(s.logger))
r.Route("/api", func(r chi.Router) {
r.Get("/health", s.handleHealth)
r.Route("/stats", func(r chi.Router) {
r.Get("/overview", s.handleStatsOverview)
r.Get("/countries", s.handleStatsCountries)
r.Get(
"/credentials",
s.handleStatsCredentials,
)
})
r.Route("/events", func(r chi.Router) {
r.Get("/", s.handleEvents)
})
r.Route("/sessions", func(r chi.Router) {
r.Get("/", s.handleSessions)
r.Get("/{id}", s.handleSessionByID)
r.Get(
"/{id}/replay",
s.handleSessionReplay,
)
})
r.Route("/attackers", func(r chi.Router) {
r.Get("/", s.handleAttackers)
r.Get("/{id}", s.handleAttackerByID)
})
r.Route("/mitre", func(r chi.Router) {
r.Get(
"/techniques",
s.handleMITRETechniques,
)
r.Get("/heatmap", s.handleMITREHeatmap)
})
r.Route("/iocs", func(r chi.Router) {
r.Get("/", s.handleIOCs)
r.Get(
"/export/stix",
s.handleIOCExportSTIX,
)
r.Get(
"/export/blocklist",
s.handleIOCExportBlocklist,
)
})
r.Get("/sensors", s.handleSensors)
})
r.Get("/ws/events", s.handleWebSocket)
return r
}
var _ types.Service = (*Server)(nil)