mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00
safeweb: add opt-in inline style CSP toggle (#11551)
Allow the use of inline styles with safeweb via an opt-in configuration item. This will append `style-src "self" "unsafe-inline"` to the default CSP. The `style-src` directive will be used in lieu of the fallback `default-src "self"` directive. Updates tailscale/corp#8027 Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
This commit is contained in:
parent
b0941b79d6
commit
af61179c2f
@ -26,6 +26,10 @@
|
|||||||
// - X-Content-Type-Options header on responses set to "nosniff" to prevent MIME type sniffing attacks.
|
// - X-Content-Type-Options header on responses set to "nosniff" to prevent MIME type sniffing attacks.
|
||||||
// - Referer-Policy header set to "same-origin" to prevent leaking referrer information to third parties.
|
// - Referer-Policy header set to "same-origin" to prevent leaking referrer information to third parties.
|
||||||
//
|
//
|
||||||
|
// By default the Content-Security-Policy header will disallow inline styles.
|
||||||
|
// This can be overridden by setting the CSPAllowInlineStyles field to true in
|
||||||
|
// the safeweb.Config struct.
|
||||||
|
//
|
||||||
// # API routes
|
// # API routes
|
||||||
//
|
//
|
||||||
// safeweb inspects the Content-Type header of incoming requests to the API mux
|
// safeweb inspects the Content-Type header of incoming requests to the API mux
|
||||||
@ -118,6 +122,11 @@ type Config struct {
|
|||||||
// If this is not provided, the Server will generate a random CSRF secret on
|
// If this is not provided, the Server will generate a random CSRF secret on
|
||||||
// startup.
|
// startup.
|
||||||
CSRFSecret []byte
|
CSRFSecret []byte
|
||||||
|
|
||||||
|
// CSPAllowInlineStyles specifies whether to include `style-src:
|
||||||
|
// unsafe-inline` in the Content-Security-Policy header to permit the use of
|
||||||
|
// inline CSS.
|
||||||
|
CSPAllowInlineStyles bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) setDefaults() error {
|
func (c *Config) setDefaults() error {
|
||||||
@ -144,6 +153,15 @@ func (c Config) newHandler() http.Handler {
|
|||||||
// as otherwise the browser will reject the cookie
|
// as otherwise the browser will reject the cookie
|
||||||
csrfProtect := csrf.Protect(c.CSRFSecret, csrf.Secure(c.SecureContext))
|
csrfProtect := csrf.Protect(c.CSRFSecret, csrf.Secure(c.SecureContext))
|
||||||
|
|
||||||
|
var csp string
|
||||||
|
if c.CSPAllowInlineStyles {
|
||||||
|
csp = defaultCSP + `; style-src 'self' 'unsafe-inline'`
|
||||||
|
} else {
|
||||||
|
// if no style-src is provided the browser will fallback to the
|
||||||
|
// default-src directive which disallows inline styles.
|
||||||
|
csp = defaultCSP
|
||||||
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if _, p := c.BrowserMux.Handler(r); p == "" {
|
if _, p := c.BrowserMux.Handler(r); p == "" {
|
||||||
// disallow x-www-form-urlencoded requests to the API
|
// disallow x-www-form-urlencoded requests to the API
|
||||||
@ -161,8 +179,7 @@ func (c Config) newHandler() http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(@patrickod) consider templating additions to the CSP header.
|
w.Header().Set("Content-Security-Policy", csp)
|
||||||
w.Header().Set("Content-Security-Policy", defaultCSP)
|
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
w.Header().Set("Referer-Policy", "same-origin")
|
w.Header().Set("Referer-Policy", "same-origin")
|
||||||
csrfProtect(c.BrowserMux).ServeHTTP(w, r)
|
csrfProtect(c.BrowserMux).ServeHTTP(w, r)
|
||||||
|
@ -6,6 +6,8 @@ package safeweb
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
@ -364,3 +366,29 @@ func TestRefererPolicy(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCSPAllowInlineStyles(t *testing.T) {
|
||||||
|
for _, allow := range []bool{false, true} {
|
||||||
|
t.Run(strconv.FormatBool(allow), func(t *testing.T) {
|
||||||
|
h := &http.ServeMux{}
|
||||||
|
h.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}))
|
||||||
|
s, err := NewServer(Config{BrowserMux: h, CSPAllowInlineStyles: allow})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.h.Handler.ServeHTTP(w, req)
|
||||||
|
resp := w.Result()
|
||||||
|
|
||||||
|
csp := resp.Header.Get("Content-Security-Policy")
|
||||||
|
allowsStyles := strings.Contains(csp, "style-src 'self' 'unsafe-inline'")
|
||||||
|
if allowsStyles != allow {
|
||||||
|
t.Fatalf("CSP inline styles want: %v; got: %v", allow, allowsStyles)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user