Fail fast with aggregated config errors after resolving secret files, before services initialize. Add Valkey session config, remove the unused session-secret, and fix the production CSP env key. Retry initial Temporal dials with bounded backoff and gate the test Temporal service on healthy DB and Keycloak dependencies.
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// SecurityHeaders adds security and cache-control headers to all responses
|
|
func SecureHeaders() Middleware {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Caching headers
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
|
|
// XSSProtection provides protection against cross-site scripting attack (XSS)
|
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
|
|
|
// ContentTypeNosniff provides protection against overriding Content-Type
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
|
|
// XFrameOptions prevents the page from being displayed in a frame
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
|
|
// HSTS (HTTP Strict Transport Security) forces the browser to use HTTPS
|
|
w.Header().Set("Strict-Transport-Security", "max-age=3600; includeSubDomains")
|
|
|
|
// ReferrerPolicy sets the referrer information passed during navigation
|
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
|
|
|
// CSP controls the resources the user agent is allowed to load for a page
|
|
cspPolicy := "default-src 'self'; " +
|
|
// Allow HTMX to load from unpkg.com
|
|
"script-src 'self' https://unpkg.com/htmx.org@*; " +
|
|
"style-src 'self'; " +
|
|
"img-src 'self' data:; " +
|
|
"font-src 'self'; " +
|
|
"connect-src 'self'; " +
|
|
"object-src 'none'; " +
|
|
"frame-ancestors 'none'; " +
|
|
"form-action 'self'; " +
|
|
"base-uri 'self';"
|
|
|
|
// Add upgrade-insecure-requests directive only in production
|
|
if viper.GetString("env") == "production" {
|
|
cspPolicy += "upgrade-insecure-requests;"
|
|
}
|
|
|
|
// Set Content-Security-Policy header
|
|
w.Header().Set("Content-Security-Policy", cspPolicy)
|
|
|
|
// Cross-Origin-Embedder-Policy prevents cross-origin resources from being loaded
|
|
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
|
|
|
|
// Cross-Origin-Opener-Policy prevents cross-origin documents from being loaded
|
|
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
|
|
|
// Cross-Origin-Resource-Policy prevents cross-origin resources from being loaded
|
|
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// MaxBodySize limits the maximum size of request bodies
|
|
// size parameter is in bytes
|
|
func MaxBodySize(maxSize int64) Middleware {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Skip restricting GET, HEAD, and OPTIONS requests as they shouldn't have bodies
|
|
if r.Method == http.MethodGet || r.Method == http.MethodHead || r.Method == http.MethodOptions {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
// Check Content-Length header first for efficiency
|
|
if r.ContentLength > maxSize {
|
|
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
|
|
return
|
|
}
|
|
|
|
// If Content-Length is not set or potentially spoofed, use LimitReader
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxSize)
|
|
|
|
// Continue to next middleware/handler
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|