Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
168 lines
4.9 KiB
Go
168 lines
4.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// SecurityHeadersConfig holds configuration for security headers.
|
|
type SecurityHeadersConfig struct {
|
|
// X-Content-Type-Options
|
|
ContentTypeOptions string
|
|
|
|
// X-Frame-Options
|
|
FrameOptions string
|
|
|
|
// X-XSS-Protection (legacy but useful for older browsers)
|
|
XSSProtection string
|
|
|
|
// Strict-Transport-Security
|
|
HSTSEnabled bool
|
|
HSTSMaxAge int
|
|
HSTSIncludeSubdomains bool
|
|
HSTSPreload bool
|
|
|
|
// Content-Security-Policy
|
|
CSPEnabled bool
|
|
CSPPolicy string
|
|
|
|
// Referrer-Policy
|
|
ReferrerPolicy string
|
|
|
|
// Permissions-Policy
|
|
PermissionsPolicy string
|
|
|
|
// Cross-Origin headers
|
|
CrossOriginOpenerPolicy string
|
|
CrossOriginResourcePolicy string
|
|
|
|
// Development mode (relaxes some restrictions)
|
|
DevelopmentMode bool
|
|
|
|
// Excluded paths (e.g., health checks)
|
|
ExcludedPaths []string
|
|
}
|
|
|
|
// DefaultSecurityHeadersConfig returns sensible default configuration.
|
|
func DefaultSecurityHeadersConfig() SecurityHeadersConfig {
|
|
env := os.Getenv("ENVIRONMENT")
|
|
isDev := env == "" || strings.ToLower(env) == "development" || strings.ToLower(env) == "dev"
|
|
|
|
return SecurityHeadersConfig{
|
|
ContentTypeOptions: "nosniff",
|
|
FrameOptions: "DENY",
|
|
XSSProtection: "1; mode=block",
|
|
HSTSEnabled: true,
|
|
HSTSMaxAge: 31536000, // 1 year
|
|
HSTSIncludeSubdomains: true,
|
|
HSTSPreload: false,
|
|
CSPEnabled: true,
|
|
CSPPolicy: getDefaultCSP(isDev),
|
|
ReferrerPolicy: "strict-origin-when-cross-origin",
|
|
PermissionsPolicy: "geolocation=(), microphone=(), camera=()",
|
|
CrossOriginOpenerPolicy: "same-origin",
|
|
CrossOriginResourcePolicy: "same-origin",
|
|
DevelopmentMode: isDev,
|
|
ExcludedPaths: []string{"/health", "/metrics", "/api/v1/health"},
|
|
}
|
|
}
|
|
|
|
// getDefaultCSP returns a sensible default CSP for the environment.
|
|
func getDefaultCSP(isDevelopment bool) string {
|
|
if isDevelopment {
|
|
return "default-src 'self' localhost:* ws://localhost:*; " +
|
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
|
|
"style-src 'self' 'unsafe-inline'; " +
|
|
"img-src 'self' data: https: blob:; " +
|
|
"font-src 'self' data:; " +
|
|
"connect-src 'self' localhost:* ws://localhost:* https:; " +
|
|
"frame-ancestors 'self'"
|
|
}
|
|
return "default-src 'self'; " +
|
|
"script-src 'self' 'unsafe-inline'; " +
|
|
"style-src 'self' 'unsafe-inline'; " +
|
|
"img-src 'self' data: https:; " +
|
|
"font-src 'self' data:; " +
|
|
"connect-src 'self' https://breakpilot.app https://*.breakpilot.app; " +
|
|
"frame-ancestors 'none'"
|
|
}
|
|
|
|
// buildHSTSHeader builds the Strict-Transport-Security header value.
|
|
func (c *SecurityHeadersConfig) buildHSTSHeader() string {
|
|
parts := []string{"max-age=" + strconv.Itoa(c.HSTSMaxAge)}
|
|
if c.HSTSIncludeSubdomains {
|
|
parts = append(parts, "includeSubDomains")
|
|
}
|
|
if c.HSTSPreload {
|
|
parts = append(parts, "preload")
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
// isExcludedPath checks if the path should be excluded from security headers.
|
|
func (c *SecurityHeadersConfig) isExcludedPath(path string) bool {
|
|
for _, excluded := range c.ExcludedPaths {
|
|
if path == excluded {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SecurityHeaders returns a middleware that adds security headers to all responses.
|
|
//
|
|
// Usage:
|
|
//
|
|
// r.Use(middleware.SecurityHeaders())
|
|
//
|
|
// // Or with custom config:
|
|
// config := middleware.DefaultSecurityHeadersConfig()
|
|
// config.CSPPolicy = "default-src 'self'"
|
|
// r.Use(middleware.SecurityHeadersWithConfig(config))
|
|
func SecurityHeaders() gin.HandlerFunc {
|
|
return SecurityHeadersWithConfig(DefaultSecurityHeadersConfig())
|
|
}
|
|
|
|
// SecurityHeadersWithConfig returns a security headers middleware with custom configuration.
|
|
func SecurityHeadersWithConfig(config SecurityHeadersConfig) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Skip for excluded paths
|
|
if config.isExcludedPath(c.Request.URL.Path) {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Always add these headers
|
|
c.Header("X-Content-Type-Options", config.ContentTypeOptions)
|
|
c.Header("X-Frame-Options", config.FrameOptions)
|
|
c.Header("X-XSS-Protection", config.XSSProtection)
|
|
c.Header("Referrer-Policy", config.ReferrerPolicy)
|
|
|
|
// HSTS (only in production or if explicitly enabled)
|
|
if config.HSTSEnabled && !config.DevelopmentMode {
|
|
c.Header("Strict-Transport-Security", config.buildHSTSHeader())
|
|
}
|
|
|
|
// Content-Security-Policy
|
|
if config.CSPEnabled && config.CSPPolicy != "" {
|
|
c.Header("Content-Security-Policy", config.CSPPolicy)
|
|
}
|
|
|
|
// Permissions-Policy
|
|
if config.PermissionsPolicy != "" {
|
|
c.Header("Permissions-Policy", config.PermissionsPolicy)
|
|
}
|
|
|
|
// Cross-Origin headers (only in production)
|
|
if !config.DevelopmentMode {
|
|
c.Header("Cross-Origin-Opener-Policy", config.CrossOriginOpenerPolicy)
|
|
c.Header("Cross-Origin-Resource-Policy", config.CrossOriginResourcePolicy)
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|