Skip to content

Commit

Permalink
all: Add CSP Websocket compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
adriansmares committed Nov 16, 2023
1 parent 1b7cd8d commit a7ee4e3
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 14 deletions.
25 changes: 14 additions & 11 deletions pkg/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,25 @@ func path(u string) (string, error) {
}

func generateConsoleCSPString(config *Config, nonce string, others ...webui.ContentSecurityPolicy) string {
baseURLs := webui.RewriteSchemes(
webui.WebsocketSchemeRewrites,
config.UI.StackConfig.GS.BaseURL,
config.UI.StackConfig.IS.BaseURL,
config.UI.StackConfig.JS.BaseURL,
config.UI.StackConfig.NS.BaseURL,
config.UI.StackConfig.AS.BaseURL,
config.UI.StackConfig.EDTC.BaseURL,
config.UI.StackConfig.QRG.BaseURL,
config.UI.StackConfig.GCS.BaseURL,
config.UI.StackConfig.DCS.BaseURL,
)
return webui.ContentSecurityPolicy{
ConnectionSource: []string{
ConnectionSource: append([]string{
"'self'",
config.UI.StackConfig.GS.BaseURL,
config.UI.StackConfig.IS.BaseURL,
config.UI.StackConfig.JS.BaseURL,
config.UI.StackConfig.NS.BaseURL,
config.UI.StackConfig.AS.BaseURL,
config.UI.StackConfig.EDTC.BaseURL,
config.UI.StackConfig.QRG.BaseURL,
config.UI.StackConfig.GCS.BaseURL,
config.UI.StackConfig.DCS.BaseURL,
config.UI.SentryDSN,
"gravatar.com",
"www.gravatar.com",
},
}, baseURLs...),
StyleSource: []string{
"'self'",
config.UI.AssetsBaseURL,
Expand Down
9 changes: 6 additions & 3 deletions pkg/identityserver/identityserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,17 @@ func (is *IdentityServer) configFromContext(ctx context.Context) *Config {
// GenerateCSPString returns a Content-Security-Policy header value
// for OAuth and Account app template.
func GenerateCSPString(config *oauth.Config, nonce string) string {
baseURLs := webui.RewriteSchemes(
webui.WebsocketSchemeRewrites,
config.UI.StackConfig.IS.BaseURL,
)
return webui.ContentSecurityPolicy{
ConnectionSource: []string{
ConnectionSource: append([]string{
"'self'",
config.UI.StackConfig.IS.BaseURL,
config.UI.SentryDSN,
"gravatar.com",
"www.gravatar.com",
},
}, baseURLs...),
StyleSource: []string{
"'self'",
config.UI.AssetsBaseURL,
Expand Down
35 changes: 35 additions & 0 deletions pkg/webui/csp.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ func (csp ContentSecurityPolicy) Clean() ContentSecurityPolicy {
entry = parsed.Host
}
}
if strings.HasPrefix(entry, "ws://") || strings.HasPrefix(entry, "wss://") {
if parsed, err := url.Parse(entry); err == nil {
parsed.Path, parsed.RawPath = "", ""
entry = parsed.String()
}
}
if _, ok := added[entry]; ok {
continue // Skip already added locations.
}
Expand Down Expand Up @@ -112,3 +118,32 @@ func (csp ContentSecurityPolicy) String() string {
result = appendPolicy(result, "frame-ancestors", csp.FrameAncestors)
return strings.Join(result, " ")
}

// RewriteScheme rewrites the scheme of the provided URL if it matches a rewrite rule.
func RewriteScheme(rewrites map[string]string, baseURL string) []string {
u, err := url.Parse(baseURL)
if err != nil {
return []string{baseURL}
}
rewrite, ok := rewrites[u.Scheme]
if !ok {
return []string{baseURL}
}
u.Scheme = rewrite
return append(make([]string, 0, 2), baseURL, u.String())
}

// RewriteSchemes rewrites the scheme of the provided URLs if they match a rewrite rule.
func RewriteSchemes(rewrites map[string]string, baseURLs ...string) []string {
urls := make([]string, 0, 2*len(baseURLs))
for _, baseURL := range baseURLs {
urls = append(urls, RewriteScheme(rewrites, baseURL)...)
}
return urls
}

// WebsocketSchemeRewrites contains the rewrite rules for websocket schemes.
var WebsocketSchemeRewrites = map[string]string{
"http": "ws",
"https": "wss",
}
95 changes: 95 additions & 0 deletions pkg/webui/csp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package webui_test

import (
"testing"

"github.com/smarty/assertions"
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
"go.thethings.network/lorawan-stack/v3/pkg/webui"
)

func TestRewriteScheme(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
baseURL string
rewrites map[string]string
expected []string
}{
{
name: "no match",
baseURL: "https://example.com",
rewrites: map[string]string{
"http": "ws",
},
expected: []string{"https://example.com"},
},
{
name: "match",
baseURL: "https://example.com",
rewrites: map[string]string{
"https": "wss",
},
expected: []string{"https://example.com", "wss://example.com"},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
a := assertions.New(t)
actual := webui.RewriteScheme(tc.rewrites, tc.baseURL)
a.So(actual, should.Resemble, tc.expected)
})
}
}

func TestRewriteSchemes(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
baseURLs []string
rewrites map[string]string
expected []string
}{
{
name: "no match",
baseURLs: []string{"https://foo.example.com", "https://bar.example.com"},
rewrites: map[string]string{
"http": "ws",
},
expected: []string{"https://foo.example.com", "https://bar.example.com"},
},
{
name: "match",
baseURLs: []string{"https://foo.example.com", "https://bar.example.com"},
rewrites: map[string]string{
"https": "wss",
},
expected: []string{
"https://foo.example.com", "wss://foo.example.com", "https://bar.example.com", "wss://bar.example.com",
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
a := assertions.New(t)
actual := webui.RewriteSchemes(tc.rewrites, tc.baseURLs...)
a.So(actual, should.Resemble, tc.expected)
})
}
}

0 comments on commit a7ee4e3

Please sign in to comment.