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:
Asher
2025-06-17 09:00:32 -08:00
committed by GitHub
parent aee96c9eac
commit 82c14e00ce
8 changed files with 180 additions and 57 deletions

View File

@ -289,3 +289,23 @@ func ExecuteHostnamePattern(pattern *regexp.Regexp, hostname string) (string, bo
return matches[1], true
}
// ConvertAppHostForCSP converts the wildcard host to a format accepted by CSP.
// For example *--apps.coder.com must become *.coder.com. If there is no
// wildcard host, or it cannot be converted, return the base host.
func ConvertAppHostForCSP(host, wildcard string) string {
if wildcard == "" {
return host
}
parts := strings.Split(wildcard, ".")
for i, part := range parts {
if strings.Contains(part, "*") {
// The wildcard can only be in the first section.
if i != 0 {
return host
}
parts[i] = "*"
}
}
return strings.Join(parts, ".")
}

View File

@ -410,3 +410,59 @@ func TestCompileHostnamePattern(t *testing.T) {
})
}
}
func TestConvertAppURLForCSP(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
host string
wildcard string
expected string
}{
{
name: "Empty",
host: "example.com",
wildcard: "",
expected: "example.com",
},
{
name: "NoAsterisk",
host: "example.com",
wildcard: "coder.com",
expected: "coder.com",
},
{
name: "Asterisk",
host: "example.com",
wildcard: "*.coder.com",
expected: "*.coder.com",
},
{
name: "FirstPrefix",
host: "example.com",
wildcard: "*--apps.coder.com",
expected: "*.coder.com",
},
{
name: "FirstSuffix",
host: "example.com",
wildcard: "apps--*.coder.com",
expected: "*.coder.com",
},
{
name: "Middle",
host: "example.com",
wildcard: "apps.*.com",
expected: "example.com",
},
}
for _, c := range testCases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, c.expected, appurl.ConvertAppHostForCSP(c.host, c.wildcard))
})
}
}