security: Tighten csp connect-src to prevent external websockets (#2705)

This commit is contained in:
Steven Masley
2022-06-29 11:42:17 -05:00
committed by GitHub
parent ea7f9e2d47
commit 889e2e68ea

View File

@ -256,9 +256,8 @@ const (
CSPFrameAncestors = "frame-ancestors" CSPFrameAncestors = "frame-ancestors"
) )
// secureHeaders is only needed for statically served files. We do not need this for api endpoints. func cspHeaders(next http.Handler) http.Handler {
// It adds various headers to enforce browser security features. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
func secureHeaders(next http.Handler) http.Handler {
// Content-Security-Policy disables loading certain content types and can prevent XSS injections. // Content-Security-Policy disables loading certain content types and can prevent XSS injections.
// This site helps eval your policy for syntax and other common issues: https://csp-evaluator.withgoogle.com/ // This site helps eval your policy for syntax and other common issues: https://csp-evaluator.withgoogle.com/
// If we ever want to render something like a PDF, we need to adjust "object-src" // If we ever want to render something like a PDF, we need to adjust "object-src"
@ -267,7 +266,7 @@ func secureHeaders(next http.Handler) http.Handler {
cspSrcs := CSPDirectives{ cspSrcs := CSPDirectives{
// All omitted fetch csp srcs default to this. // All omitted fetch csp srcs default to this.
CSPDirectiveDefaultSrc: {"'self'"}, CSPDirectiveDefaultSrc: {"'self'"},
CSPDirectiveConnectSrc: {"'self' ws: wss:"}, CSPDirectiveConnectSrc: {"'self'"},
CSPDirectiveChildSrc: {"'self'"}, CSPDirectiveChildSrc: {"'self'"},
CSPDirectiveScriptSrc: {"'self'"}, CSPDirectiveScriptSrc: {"'self'"},
CSPDirectiveFontSrc: {"'self'"}, CSPDirectiveFontSrc: {"'self'"},
@ -281,7 +280,7 @@ func secureHeaders(next http.Handler) http.Handler {
// https: allows loading images from external sources. This is not ideal // https: allows loading images from external sources. This is not ideal
// but is required for the templates page that renders readmes. // but is required for the templates page that renders readmes.
// We should find a better solution in the future. // We should find a better solution in the future.
CSPDirectiveImgSrc: {"'self' https: https://cdn.coder.com data:"}, CSPDirectiveImgSrc: {"'self' data:"},
CSPDirectiveFormAction: {"'self'"}, CSPDirectiveFormAction: {"'self'"},
CSPDirectiveMediaSrc: {"'self'"}, CSPDirectiveMediaSrc: {"'self'"},
// Report all violations back to the server to log // Report all violations back to the server to log
@ -293,11 +292,34 @@ func secureHeaders(next http.Handler) http.Handler {
// "require-trusted-types-for" : []string{"'script'"}, // "require-trusted-types-for" : []string{"'script'"},
} }
// This extra connect-src addition is required to support old webkit
// based browsers (Safari).
// See issue: https://github.com/w3c/webappsec-csp/issues/7
// Once webkit browsers support 'self' on connect-src, we can remove this.
// When we remove this, the csp header can be static, as opposed to being
// dynamically generated for each request.
host := r.Host
// It is important r.Host is not an empty string.
if host != "" {
// We can add both ws:// and wss:// as browsers do not let https
// pages to connect to non-tls websocket connections. So this
// supports both http & https webpages.
cspSrcs.Append(CSPDirectiveConnectSrc, fmt.Sprintf("wss://%[1]s ws://%[1]s", host))
}
var csp strings.Builder var csp strings.Builder
for src, vals := range cspSrcs { for src, vals := range cspSrcs {
_, _ = fmt.Fprintf(&csp, "%s %s; ", src, strings.Join(vals, " ")) _, _ = fmt.Fprintf(&csp, "%s %s; ", src, strings.Join(vals, " "))
} }
w.Header().Set("Content-Security-Policy", csp.String())
next.ServeHTTP(w, r)
})
}
// secureHeaders is only needed for statically served files. We do not need this for api endpoints.
// It adds various headers to enforce browser security features.
func secureHeaders(next http.Handler) http.Handler {
// Permissions-Policy can be used to disabled various browser features that we do not use. // Permissions-Policy can be used to disabled various browser features that we do not use.
// This can prevent an embedded iframe from accessing these features. // This can prevent an embedded iframe from accessing these features.
// If we support arbitrary iframes such as generic applications, we might need to add permissions // If we support arbitrary iframes such as generic applications, we might need to add permissions
@ -322,15 +344,11 @@ func secureHeaders(next http.Handler) http.Handler {
}, ", ") }, ", ")
return secure.New(secure.Options{ return secure.New(secure.Options{
// Set to ContentSecurityPolicyReportOnly for testing, as all errors are printed to the console log
// but are not enforced.
ContentSecurityPolicy: csp.String(),
PermissionsPolicy: permissions, PermissionsPolicy: permissions,
// Prevent the browser from sending Referer header with requests // Prevent the browser from sending Referer header with requests
ReferrerPolicy: "no-referrer", ReferrerPolicy: "no-referrer",
}).Handler(next) }).Handler(cspHeaders(next))
} }
// htmlFiles recursively walks the file system passed finding all *.html files. // htmlFiles recursively walks the file system passed finding all *.html files.