mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
feat: add csp headers for embedded apps (#18374)
I modified the proxy host cache we already had and were using for websocket csp headers to also include the wildcard app host, then used those for frame-src policies. I did not add frame-ancestors, since if I understand correctly, those would go on the app, and this middleware does not come into play there. Maybe we will want to add it on workspace apps like we do with cors, if we find apps are setting it to `none` or something. Closes https://github.com/coder/internal/issues/684
This commit is contained in:
@ -1,28 +1,59 @@
|
||||
package httpmw_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/proxyhealth"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestCSPConnect(t *testing.T) {
|
||||
func TestCSP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expected := []string{"example.com", "coder.com"}
|
||||
proxyHosts := []*proxyhealth.ProxyHost{
|
||||
{
|
||||
Host: "test.com",
|
||||
AppHost: "*.test.com",
|
||||
},
|
||||
{
|
||||
Host: "coder.com",
|
||||
AppHost: "*.coder.com",
|
||||
},
|
||||
{
|
||||
// Host is not added because it duplicates the host header.
|
||||
Host: "example.com",
|
||||
AppHost: "*.coder2.com",
|
||||
},
|
||||
}
|
||||
expectedMedia := []string{"media.com", "media2.com"}
|
||||
|
||||
expected := []string{
|
||||
"frame-src 'self' *.test.com *.coder.com *.coder2.com",
|
||||
"media-src 'self' media.com media2.com",
|
||||
strings.Join([]string{
|
||||
"connect-src", "'self'",
|
||||
// Added from host header.
|
||||
"wss://example.com", "ws://example.com",
|
||||
// Added via proxy hosts.
|
||||
"wss://test.com", "ws://test.com", "https://test.com", "http://test.com",
|
||||
"wss://coder.com", "ws://coder.com", "https://coder.com", "http://coder.com",
|
||||
}, " "),
|
||||
}
|
||||
|
||||
// When the host is empty, it uses example.com.
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
httpmw.CSPHeaders(codersdk.Experiments{}, false, func() []string {
|
||||
return expected
|
||||
httpmw.CSPHeaders(codersdk.Experiments{
|
||||
codersdk.ExperimentAITasks,
|
||||
}, false, func() []*proxyhealth.ProxyHost {
|
||||
return proxyHosts
|
||||
}, map[httpmw.CSPFetchDirective][]string{
|
||||
httpmw.CSPDirectiveMediaSrc: expectedMedia,
|
||||
})(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
@ -31,10 +62,6 @@ func TestCSPConnect(t *testing.T) {
|
||||
|
||||
require.NotEmpty(t, rw.Header().Get("Content-Security-Policy"), "Content-Security-Policy header should not be empty")
|
||||
for _, e := range expected {
|
||||
require.Containsf(t, rw.Header().Get("Content-Security-Policy"), fmt.Sprintf("ws://%s", e), "Content-Security-Policy header should contain ws://%s", e)
|
||||
require.Containsf(t, rw.Header().Get("Content-Security-Policy"), fmt.Sprintf("wss://%s", e), "Content-Security-Policy header should contain wss://%s", e)
|
||||
}
|
||||
for _, e := range expectedMedia {
|
||||
require.Containsf(t, rw.Header().Get("Content-Security-Policy"), e, "Content-Security-Policy header should contain %s", e)
|
||||
require.Contains(t, rw.Header().Get("Content-Security-Policy"), e)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user