Support CSP nonce for webv2. Closes #2127

This commit is contained in:
Gabe Kangas 2022-12-12 16:57:17 -08:00
parent acc9cd39a5
commit 2fdbb1e482
No known key found for this signature in database
GPG key ID: 4345B2060657F330
4 changed files with 18 additions and 20 deletions

View file

@ -2,6 +2,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strings" "strings"
@ -23,21 +24,23 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if isIndexRequest {
renderIndexHtml(w)
return
}
// Set a cache control max-age header // Set a cache control max-age header
middleware.SetCachingHeaders(w, r) middleware.SetCachingHeaders(w, r)
nonceRandom, _ := utils.GenerateRandomString(5)
// Set our global HTTP headers // 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) serveWeb(w, r)
} }
func renderIndexHtml(w http.ResponseWriter) { func renderIndexHtml(w http.ResponseWriter, nonce string) {
type serverSideContent struct { type serverSideContent struct {
Name string Name string
Summary string Summary string
@ -48,6 +51,7 @@ func renderIndexHtml(w http.ResponseWriter) {
Image string Image string
StatusJSON string StatusJSON string
ServerConfigJSON string ServerConfigJSON string
Nonce string
} }
status := getStatusResponse() status := getStatusResponse()
@ -74,6 +78,7 @@ func renderIndexHtml(w http.ResponseWriter) {
Image: "/logo/external", Image: "/logo/external",
StatusJSON: string(sb), StatusJSON: string(sb),
ServerConfigJSON: string(cb), ServerConfigJSON: string(cb),
Nonce: nonce,
} }
index, err := static.GetWebIndexTemplate() index, err := static.GetWebIndexTemplate()

View file

@ -3,22 +3,14 @@ package middleware
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"os"
"strings" "strings"
) )
// SetHeaders will set our global headers for web resources. // SetHeaders will set our global headers for web resources.
func SetHeaders(w http.ResponseWriter) { func SetHeaders(w http.ResponseWriter, nonce string) {
// 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'`
}
// Content security policy // Content security policy
csp := []string{ 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: "worker-src 'self' blob:", // No single quotes around blob:
} }
w.Header().Set("Content-Security-Policy", strings.Join(csp, "; ")) w.Header().Set("Content-Security-Policy", strings.Join(csp, "; "))

View file

@ -9,7 +9,7 @@ const tokenLength = 32
// GenerateAccessToken will generate and return an access token. // GenerateAccessToken will generate and return an access token.
func GenerateAccessToken() (string, error) { func GenerateAccessToken() (string, error) {
return generateRandomString(tokenLength) return GenerateRandomString(tokenLength)
} }
// generateRandomBytes returns securely generated random bytes. // generateRandomBytes returns securely generated random bytes.
@ -27,12 +27,12 @@ func generateRandomBytes(n int) ([]byte, error) {
return b, nil return b, nil
} }
// generateRandomString returns a URL-safe, base64 encoded // GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string. // securely generated random string.
// It will return an error if the system's secure random // It will return an error if the system's secure random
// number generator fails to function correctly, in which // number generator fails to function correctly, in which
// case the caller should not continue. // case the caller should not continue.
func generateRandomString(n int) (string, error) { func GenerateRandomString(n int) (string, error) {
b, err := generateRandomBytes(n) b, err := generateRandomBytes(n)
return base64.URLEncoding.EncodeToString(b), err return base64.URLEncoding.EncodeToString(b), err
} }

View file

@ -4,6 +4,7 @@ import { FC } from 'react';
export const ServerRenderedHydration: FC = () => ( export const ServerRenderedHydration: FC = () => (
<script <script
id="server-side-hydration" id="server-side-hydration"
nonce="{{.Nonce}}"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
window.configHydration = {{.ServerConfigJSON}}; window.configHydration = {{.ServerConfigJSON}};