Skip to content

Security Overview

SLOzy implements defense-in-depth security with multiple layers of protection.

Authentication

JWT-based authentication is enforced via internal/middleware/auth.go:

go
type AuthMiddleware struct {
    authService *auth.AuthenticationService
}

func (m *AuthMiddleware) Authenticate(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authHeader := r.Header.Get("Authorization")
        // Validate Bearer token format
        parts := strings.SplitN(authHeader, " ", 2)
        token := parts[1]
        claims, err := m.authService.ValidateToken(token)
        // Add user context to request
        ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
        // ...
    })
}

Tokens are signed using HS256 and include user_id, organization_id, email, and name claims. The JWT_SECRET must be at least 32 characters and kept confidential.

RBAC (Role-Based Access Control)

Authorization is handled by internal/middleware/rbac.go and internal/rbac/service.go:

go
func (m *RBACMiddleware) RequirePermission(permission string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        // Check user has permission via check_permission() DB function
        hasPermission, err := m.rbacService.CheckPermission(ctx, userID, organizationID, permission)
        if !hasPermission {
            http.Error(w, "Permission denied", http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    }
}

Four default roles are defined:

  • admin — Full access (read, write, delete, manage users/teams/organizations)
  • manager — Team and SLO management (read, write, manage teams, view analytics)
  • developer — SLO and metrics access (read, write, view analytics)
  • viewer — Read-only access (read, view analytics)

Rate Limiting

Token bucket rate limiter (internal/middleware/security.go) with configurable global and per-IP limits:

go
type RateLimiter struct {
    visitors map[string]*Visitor
    rate     int           // tokens per interval
    burst   int           // bucket size
    window  time.Duration // interval duration
}
  • Global: 1000 requests/minute, burst 50
  • Per-IP: 100 requests/minute, burst 10
  • Whitelist/blacklist support for specific IPs
  • Cleanup goroutine removes stale visitors every minute

Security Headers

Applied by SecurityHeadersMiddleware() (internal/middleware/security.go:197):

HeaderValue
X-Frame-OptionsDENY
X-Content-Type-Optionsnosniff
X-XSS-Protection1; mode=block
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policydefault-src 'self'
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preload
Content-Security-Policydefault-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; ...
Cross-Origin-Opener-Policysame-origin
Cross-Origin-Embedder-Policyrequire-corp

CORS Middleware

Configurable CORS middleware (internal/middleware/security.go:218):

go
func CORSMiddleware(allowedOrigins []string) gin.HandlerFunc {
    // Validates origin against allowed list
    // Sets Access-Control-Allow-Origin, Methods, Headers, Credentials
    // Handles OPTIONS preflight requests
}

Default allowed origins: http://localhost:3000, http://localhost:8080. Override via CORS_ORIGINS environment variable for production.

Prometheus TLS

Connections to Prometheus data sources use TLS with InsecureSkipVerify: false by default (internal/prometheus/client.go). Configure trusted CA certificates and TLS settings per data source for secure metric collection.