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 (
"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()

View File

@ -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, "; "))

View File

@ -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
}

View File

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