Only allow iframes to be loaded from our domain

This commit is contained in:
Manuel Zapf 2021-02-18 14:54:03 +01:00 committed by GitHub
parent 911c439858
commit bae28c5f57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 1 deletions

View file

@ -33,6 +33,13 @@ func (g DashboardHandler) Append(router *mux.Router) {
Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets))) Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets)))
} }
func (g DashboardHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// allow iframes from our domains only
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
http.FileServer(g.Assets).ServeHTTP(w, r)
}
func safePrefix(req *http.Request) string { func safePrefix(req *http.Request) string {
prefix := req.Header.Get("X-Forwarded-Prefix") prefix := req.Header.Get("X-Forwarded-Prefix")
if prefix == "" { if prefix == "" {

View file

@ -1,9 +1,12 @@
package api package api
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -52,3 +55,70 @@ func Test_safePrefix(t *testing.T) {
}) })
} }
} }
func Test_ContentSecurityPolicy(t *testing.T) {
testCases := []struct {
desc string
handler DashboardHandler
expected int
}{
{
desc: "OK",
handler: DashboardHandler{
Assets: &assetfs.AssetFS{
Asset: func(path string) ([]byte, error) {
return []byte{}, nil
},
AssetDir: func(path string) ([]string, error) {
return []string{}, nil
},
},
},
expected: http.StatusOK,
},
{
desc: "Not found",
handler: DashboardHandler{
Assets: &assetfs.AssetFS{
Asset: func(path string) ([]byte, error) {
return []byte{}, fmt.Errorf("not found")
},
AssetDir: func(path string) ([]string, error) {
return []string{}, fmt.Errorf("not found")
},
},
},
expected: http.StatusNotFound,
},
{
desc: "Internal server error",
handler: DashboardHandler{
Assets: &assetfs.AssetFS{
Asset: func(path string) ([]byte, error) {
return []byte{}, fmt.Errorf("oops")
},
AssetDir: func(path string) ([]string, error) {
return []string{}, fmt.Errorf("oops")
},
},
},
expected: http.StatusInternalServerError,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "/foobar.html", nil)
rw := httptest.NewRecorder()
test.handler.ServeHTTP(rw, req)
assert.Equal(t, test.expected, rw.Code)
assert.Equal(t, "frame-src 'self' https://traefik.io https://*.traefik.io;", rw.Result().Header.Get("Content-Security-Policy"))
})
}
}

View file

@ -39,7 +39,7 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
factory.api = api.NewBuilder(staticConfiguration) factory.api = api.NewBuilder(staticConfiguration)
if staticConfiguration.API.Dashboard { if staticConfiguration.API.Dashboard {
factory.dashboardHandler = http.FileServer(staticConfiguration.API.DashboardAssets) factory.dashboardHandler = api.DashboardHandler{Assets: staticConfiguration.API.DashboardAssets}
} }
} }