Files
omnex/control-plane/api/internal/httpx/security.go
T
Nuxgrid 6b4274d12b
docker-publish / publish (push) Failing after 14m14s
feat: add api
2026-07-27 16:06:19 +02:00

82 lines
2.1 KiB
Go

// Package httpx : middlewares transverses (sécurité, CORS, rate-limit).
package httpx
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
// SecurityHeaders ajoute les en-têtes de sécurité recommandés (OWASP).
func SecurityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
h := c.Writer.Header()
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-Frame-Options", "DENY")
h.Set("Referrer-Policy", "no-referrer")
h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
h.Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
c.Next()
}
}
// CORS restreint les origines à une allowlist explicite.
func CORS(allowed []string) gin.HandlerFunc {
set := make(map[string]struct{}, len(allowed))
for _, o := range allowed {
set[o] = struct{}{}
}
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
if _, ok := set[origin]; ok {
h := c.Writer.Header()
h.Set("Access-Control-Allow-Origin", origin)
h.Set("Vary", "Origin")
h.Set("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,OPTIONS")
h.Set("Access-Control-Allow-Headers", "Authorization,Content-Type")
}
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// ipLimiter : rate-limiter par IP (token bucket).
type ipLimiter struct {
mu sync.Mutex
buck map[string]*rate.Limiter
r rate.Limit
burst int
}
func newIPLimiter(r rate.Limit, burst int) *ipLimiter {
return &ipLimiter{buck: make(map[string]*rate.Limiter), r: r, burst: burst}
}
func (l *ipLimiter) get(ip string) *rate.Limiter {
l.mu.Lock()
defer l.mu.Unlock()
lim, ok := l.buck[ip]
if !ok {
lim = rate.NewLimiter(l.r, l.burst)
l.buck[ip] = lim
}
return lim
}
// RateLimit limite chaque IP à r req/s avec un burst donné.
func RateLimit(perSecond float64, burst int) gin.HandlerFunc {
limiter := newIPLimiter(rate.Limit(perSecond), burst)
return func(c *gin.Context) {
if !limiter.get(c.ClientIP()).Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "trop de requêtes"})
return
}
c.Next()
}
}