mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
security: Tighten csp connect-src to prevent external websockets (#2705)
This commit is contained in:
106
site/site.go
106
site/site.go
@ -256,48 +256,70 @@ const (
|
|||||||
CSPFrameAncestors = "frame-ancestors"
|
CSPFrameAncestors = "frame-ancestors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func cspHeaders(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 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/
|
||||||
|
// If we ever want to render something like a PDF, we need to adjust "object-src"
|
||||||
|
//
|
||||||
|
// The list of CSP options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
|
||||||
|
cspSrcs := CSPDirectives{
|
||||||
|
// All omitted fetch csp srcs default to this.
|
||||||
|
CSPDirectiveDefaultSrc: {"'self'"},
|
||||||
|
CSPDirectiveConnectSrc: {"'self'"},
|
||||||
|
CSPDirectiveChildSrc: {"'self'"},
|
||||||
|
CSPDirectiveScriptSrc: {"'self'"},
|
||||||
|
CSPDirectiveFontSrc: {"'self'"},
|
||||||
|
CSPDirectiveStyleSrc: {"'self' 'unsafe-inline'"},
|
||||||
|
// object-src is needed to support code-server
|
||||||
|
CSPDirectiveObjectSrc: {"'self'"},
|
||||||
|
// blob: for loading the pwa manifest for code-server
|
||||||
|
CSPDirectiveManifestSrc: {"'self' blob:"},
|
||||||
|
CSPDirectiveFrameSrc: {"'self'"},
|
||||||
|
// data: for loading base64 encoded icons for generic applications.
|
||||||
|
// https: allows loading images from external sources. This is not ideal
|
||||||
|
// but is required for the templates page that renders readmes.
|
||||||
|
// We should find a better solution in the future.
|
||||||
|
CSPDirectiveImgSrc: {"'self' data:"},
|
||||||
|
CSPDirectiveFormAction: {"'self'"},
|
||||||
|
CSPDirectiveMediaSrc: {"'self'"},
|
||||||
|
// Report all violations back to the server to log
|
||||||
|
CSPDirectiveReportURI: {"/api/v2/csp/reports"},
|
||||||
|
CSPFrameAncestors: {"'none'"},
|
||||||
|
|
||||||
|
// Only scripts can manipulate the dom. This prevents someone from
|
||||||
|
// naming themselves something like '<svg onload="alert(/cross-site-scripting/)" />'.
|
||||||
|
// "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
|
||||||
|
for src, vals := range cspSrcs {
|
||||||
|
_, _ = 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.
|
// 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.
|
// It adds various headers to enforce browser security features.
|
||||||
func secureHeaders(next http.Handler) http.Handler {
|
func secureHeaders(next http.Handler) http.Handler {
|
||||||
// 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/
|
|
||||||
// If we ever want to render something like a PDF, we need to adjust "object-src"
|
|
||||||
//
|
|
||||||
// The list of CSP options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
|
|
||||||
cspSrcs := CSPDirectives{
|
|
||||||
// All omitted fetch csp srcs default to this.
|
|
||||||
CSPDirectiveDefaultSrc: {"'self'"},
|
|
||||||
CSPDirectiveConnectSrc: {"'self' ws: wss:"},
|
|
||||||
CSPDirectiveChildSrc: {"'self'"},
|
|
||||||
CSPDirectiveScriptSrc: {"'self'"},
|
|
||||||
CSPDirectiveFontSrc: {"'self'"},
|
|
||||||
CSPDirectiveStyleSrc: {"'self' 'unsafe-inline'"},
|
|
||||||
// object-src is needed to support code-server
|
|
||||||
CSPDirectiveObjectSrc: {"'self'"},
|
|
||||||
// blob: for loading the pwa manifest for code-server
|
|
||||||
CSPDirectiveManifestSrc: {"'self' blob:"},
|
|
||||||
CSPDirectiveFrameSrc: {"'self'"},
|
|
||||||
// data: for loading base64 encoded icons for generic applications.
|
|
||||||
// https: allows loading images from external sources. This is not ideal
|
|
||||||
// but is required for the templates page that renders readmes.
|
|
||||||
// We should find a better solution in the future.
|
|
||||||
CSPDirectiveImgSrc: {"'self' https: https://cdn.coder.com data:"},
|
|
||||||
CSPDirectiveFormAction: {"'self'"},
|
|
||||||
CSPDirectiveMediaSrc: {"'self'"},
|
|
||||||
// Report all violations back to the server to log
|
|
||||||
CSPDirectiveReportURI: {"/api/v2/csp/reports"},
|
|
||||||
CSPFrameAncestors: {"'none'"},
|
|
||||||
|
|
||||||
// Only scripts can manipulate the dom. This prevents someone from
|
|
||||||
// naming themselves something like '<svg onload="alert(/cross-site-scripting/)" />'.
|
|
||||||
// "require-trusted-types-for" : []string{"'script'"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var csp strings.Builder
|
|
||||||
for src, vals := range cspSrcs {
|
|
||||||
_, _ = fmt.Fprintf(&csp, "%s %s; ", src, strings.Join(vals, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
Reference in New Issue
Block a user