From 2fdbb1e4827bc8f4f82b469aea5fbcc13d347824 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Mon, 12 Dec 2022 16:57:17 -0800 Subject: [PATCH] Support CSP nonce for webv2. Closes #2127 --- controllers/index.go | 19 ++++++++++++------- router/middleware/headers.go | 12 ++---------- utils/accessTokens.go | 6 +++--- .../ServerRenderedHydration.tsx | 1 + 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/controllers/index.go b/controllers/index.go index 0dba20e92..f2beb3b93 100644 --- a/controllers/index.go +++ b/controllers/index.go @@ -2,6 +2,7 @@ package controllers import ( "encoding/json" + "fmt" "net/http" "path/filepath" "strings" @@ -23,21 +24,23 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { return } - if isIndexRequest { - renderIndexHtml(w) - return - } - // Set a cache control max-age header middleware.SetCachingHeaders(w, r) + nonceRandom, _ := utils.GenerateRandomString(5) + // Set our global HTTP headers - middleware.SetHeaders(w) + middleware.SetHeaders(w, fmt.Sprintf("nonce-%s", nonceRandom)) + + if isIndexRequest { + renderIndexHtml(w, nonceRandom) + return + } serveWeb(w, r) } -func renderIndexHtml(w http.ResponseWriter) { +func renderIndexHtml(w http.ResponseWriter, nonce string) { type serverSideContent struct { Name string Summary string @@ -48,6 +51,7 @@ func renderIndexHtml(w http.ResponseWriter) { Image string StatusJSON string ServerConfigJSON string + Nonce string } status := getStatusResponse() @@ -74,6 +78,7 @@ func renderIndexHtml(w http.ResponseWriter) { Image: "/logo/external", StatusJSON: string(sb), ServerConfigJSON: string(cb), + Nonce: nonce, } index, err := static.GetWebIndexTemplate() diff --git a/router/middleware/headers.go b/router/middleware/headers.go index f228c9b8a..62643d266 100644 --- a/router/middleware/headers.go +++ b/router/middleware/headers.go @@ -3,22 +3,14 @@ package middleware import ( "fmt" "net/http" - "os" "strings" ) // SetHeaders will set our global headers for web resources. -func SetHeaders(w http.ResponseWriter) { - // When running automated browser tests we must allow `unsafe-eval` in our CSP - // so we can explicitly add it only when needed. - inTest := os.Getenv("BROWSER_TEST") == "true" - unsafeEval := "" - if inTest { - unsafeEval = `'unsafe-eval'` - } +func SetHeaders(w http.ResponseWriter, nonce string) { // Content security policy csp := []string{ - fmt.Sprintf("script-src 'self' %s 'sha256-B5bOgtE39ax4J6RqDE93TVYrJeLAdxDOJFtF3hoWYDw=' 'sha256-PzXGlTLvNFZ7et6GkP2nD3XuSaAKQVBSYiHzU2ZKm8o=' 'sha256-/wqazZOqIpFSIrNVseblbKCXrezG73X7CMqRSTf+8zw=' 'sha256-jCj2f+ICtd8fvdb0ngc+Hkr/ZnZOMvNkikno/XR6VZs='", unsafeEval), + fmt.Sprintf("script-src '%s' 'self'", nonce), "worker-src 'self' blob:", // No single quotes around blob: } w.Header().Set("Content-Security-Policy", strings.Join(csp, "; ")) diff --git a/utils/accessTokens.go b/utils/accessTokens.go index 12f8afe90..77a1f06c4 100644 --- a/utils/accessTokens.go +++ b/utils/accessTokens.go @@ -9,7 +9,7 @@ const tokenLength = 32 // GenerateAccessToken will generate and return an access token. func GenerateAccessToken() (string, error) { - return generateRandomString(tokenLength) + return GenerateRandomString(tokenLength) } // generateRandomBytes returns securely generated random bytes. @@ -27,12 +27,12 @@ func generateRandomBytes(n int) ([]byte, error) { return b, nil } -// generateRandomString returns a URL-safe, base64 encoded +// GenerateRandomString returns a URL-safe, base64 encoded // securely generated random string. // It will return an error if the system's secure random // number generator fails to function correctly, in which // case the caller should not continue. -func generateRandomString(n int) (string, error) { +func GenerateRandomString(n int) (string, error) { b, err := generateRandomBytes(n) return base64.URLEncoding.EncodeToString(b), err } diff --git a/web/components/ServerRendered/ServerRenderedHydration.tsx b/web/components/ServerRendered/ServerRenderedHydration.tsx index e0ada02a5..9c84e4cf1 100644 --- a/web/components/ServerRendered/ServerRenderedHydration.tsx +++ b/web/components/ServerRendered/ServerRenderedHydration.tsx @@ -4,6 +4,7 @@ import { FC } from 'react'; export const ServerRenderedHydration: FC = () => (