mirror of
https://github.com/juanfont/headscale
synced 2026-04-25 17:15:33 +02:00
app: add security headers middleware
X-Frame-Options: DENY and frame-ancestors 'none' stop clickjacking of OIDC, register-confirm, and debug HTML pages. nosniff and no-referrer are cheap defence-in-depth for the same surfaces. Updates #3157
This commit is contained in:
@@ -488,6 +488,20 @@ func (h *Headscale) ensureUnixSocketIsAbsent() error {
|
||||
return os.Remove(h.cfg.UnixSocket)
|
||||
}
|
||||
|
||||
// securityHeaders sets baseline response headers on every HTTP response:
|
||||
// deny framing (clickjacking), forbid MIME-type sniffing, drop the Referer
|
||||
// header on outbound navigation. Cheap defense-in-depth for HTML surfaces.
|
||||
func securityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("Content-Security-Policy", "frame-ancestors 'none'")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("Referrer-Policy", "no-referrer")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *chi.Mux {
|
||||
r := chi.NewRouter()
|
||||
r.Use(metrics.Collector(metrics.CollectorOpts{
|
||||
@@ -501,6 +515,7 @@ func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *chi.Mux {
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.RequestLogger(&zerologRequestLogger{}))
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(securityHeaders)
|
||||
|
||||
r.Post(ts2021UpgradePath, h.NoiseUpgradeHandler)
|
||||
|
||||
|
||||
26
hscontrol/app_test.go
Normal file
26
hscontrol/app_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package hscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSecurityHeaders(t *testing.T) {
|
||||
handler := securityHeaders(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil)
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
h := rec.Result().Header
|
||||
assert.Equal(t, "DENY", h.Get("X-Frame-Options"))
|
||||
assert.Equal(t, "frame-ancestors 'none'", h.Get("Content-Security-Policy"))
|
||||
assert.Equal(t, "nosniff", h.Get("X-Content-Type-Options"))
|
||||
assert.Equal(t, "no-referrer", h.Get("Referrer-Policy"))
|
||||
}
|
||||
@@ -379,7 +379,7 @@ func (h *Headscale) debugHTTPServer() *http.Server {
|
||||
|
||||
debugHTTPServer := &http.Server{
|
||||
Addr: h.cfg.MetricsAddr,
|
||||
Handler: debugMux,
|
||||
Handler: securityHeaders(debugMux),
|
||||
ReadTimeout: types.HTTPTimeout,
|
||||
WriteTimeout: 0,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user