mirror of
https://github.com/owncast/owncast.git
synced 2024-11-23 21:28:29 +03:00
Support CSP nonce for webv2. Closes #2127
This commit is contained in:
parent
acc9cd39a5
commit
2fdbb1e482
4 changed files with 18 additions and 20 deletions
|
@ -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()
|
||||||
|
|
|
@ -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, "; "))
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}};
|
||||||
|
|
Loading…
Reference in a new issue