From aafc95a5514ab778604ee05d3d44e06e8cfd026c Mon Sep 17 00:00:00 2001 From: Clemens Hering Date: Sun, 1 Mar 2026 11:45:20 +0100 Subject: [PATCH] CSP-Flag --- README.md | 1 + deploy/quadlet/README.md | 4 ++++ deploy/quadlet/kubeviz-traefik.container | 3 ++- deploy/quadlet/kubeviz.container | 1 + internal/config/config.go | 2 ++ internal/httpserver/server.go | 4 +++- 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9402f20..3d0d5a9 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Then open [http://localhost:8080](http://localhost:8080). - `SESSION_TTL` (default `30m`) - `MAX_UPLOAD_SIZE` (bytes, default `5242880`) - `COOKIE_SECURE` (`true`/`false`, default `true`) +- `APP_CSP_ENABLED` (`true`/`false`, default `true`) - disable when CSP is enforced by reverse proxy - `GIT_ALLOWED_HOSTS` (CSV allowlist, default `github.com,gitlab.com,bitbucket.org`) - `LOG_LEVEL` (default `info`) diff --git a/deploy/quadlet/README.md b/deploy/quadlet/README.md index d7e58ba..8dbc797 100644 --- a/deploy/quadlet/README.md +++ b/deploy/quadlet/README.md @@ -86,6 +86,10 @@ Default workflow mode uses user services (`systemctl --user`) and rootless Podma So no root sudo is required for normal deploy runs. +CSP hardening recommendation: +- Keep a single CSP source to avoid policy conflicts. +- In these templates, Traefik sets CSP and app-level CSP is disabled via `APP_CSP_ENABLED=false`. + Required sudo permissions for the Gitea runner user (example): ```text diff --git a/deploy/quadlet/kubeviz-traefik.container b/deploy/quadlet/kubeviz-traefik.container index d98caa5..ced5159 100644 --- a/deploy/quadlet/kubeviz-traefik.container +++ b/deploy/quadlet/kubeviz-traefik.container @@ -16,6 +16,7 @@ Environment=ADDR=:8080 Environment=SESSION_TTL=30m Environment=MAX_UPLOAD_SIZE=5242880 Environment=COOKIE_SECURE=true +Environment=APP_CSP_ENABLED=false Environment=LOG_LEVEL=info Environment=GIT_ALLOWED_HOSTS=github.com,gitlab.com,gitea.smb-corp.de @@ -40,7 +41,7 @@ Label=traefik.http.routers.kubeviz-websecure.tls.certresolver=le Label=traefik.http.routers.kubeviz-websecure.middlewares=kubeviz-sec-headers,kubeviz-auth Label=traefik.http.routers.kubeviz-websecure.service=kubeviz Label=traefik.http.services.kubeviz.loadbalancer.server.port=8080 -Label=traefik.http.middlewares.kubeviz-sec-headers.headers.customResponseHeaders.Content-Security-Policy=default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; script-src-elem 'self' 'unsafe-inline'; connect-src 'self' wss: https:; font-src 'self' data:; worker-src 'self' blob:; +Label=traefik.http.middlewares.kubeviz-sec-headers.headers.contentSecurityPolicy=default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; script-src-elem 'self'; style-src 'self' 'unsafe-inline'; style-src-elem 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; Label="traefik.http.middlewares.kubeviz-auth.basicauth.users=smb:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" [Service] diff --git a/deploy/quadlet/kubeviz.container b/deploy/quadlet/kubeviz.container index 0352766..3e5580d 100644 --- a/deploy/quadlet/kubeviz.container +++ b/deploy/quadlet/kubeviz.container @@ -16,6 +16,7 @@ Environment=ADDR=:8080 Environment=SESSION_TTL=30m Environment=MAX_UPLOAD_SIZE=5242880 Environment=COOKIE_SECURE=true +Environment=APP_CSP_ENABLED=false Environment=LOG_LEVEL=info Environment=GIT_ALLOWED_HOSTS=github.com,gitlab.com,bitbucket.org diff --git a/internal/config/config.go b/internal/config/config.go index 2d5bb95..914606e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,6 +13,7 @@ type Config struct { SessionTTL time.Duration MaxUploadSize int64 CookieSecure bool + AppCSPEnabled bool LogLevel string GitAllowedHosts []string } @@ -23,6 +24,7 @@ func Load() Config { SessionTTL: durationEnvOrDefault("SESSION_TTL", 30*time.Minute), MaxUploadSize: int64(intEnvOrDefault("MAX_UPLOAD_SIZE", 5*1024*1024)), CookieSecure: boolEnvOrDefault("COOKIE_SECURE", true), + AppCSPEnabled: boolEnvOrDefault("APP_CSP_ENABLED", true), LogLevel: envOrDefault("LOG_LEVEL", "info"), GitAllowedHosts: csvEnvOrDefault("GIT_ALLOWED_HOSTS", []string{ "github.com", diff --git a/internal/httpserver/server.go b/internal/httpserver/server.go index 22bb005..31a145f 100644 --- a/internal/httpserver/server.go +++ b/internal/httpserver/server.go @@ -74,7 +74,9 @@ func (s *Server) middleware(next http.Handler) http.Handler { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Referrer-Policy", "same-origin") - w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self';") + if s.cfg.AppCSPEnabled { + w.Header().Set("Content-Security-Policy", "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self'; script-src-elem 'self'; style-src 'self' 'unsafe-inline'; style-src-elem 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:;") + } if r.TLS != nil || strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") { w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") }